Clover coverage report - Code Coverage for hivemind release 1.0-rc-1
Coverage timestamp: Wed Aug 25 2004 13:06:02 EDT
file stats: LOC: 407   Methods: 27
NCLOC: 201   Classes: 2
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
AbstractParser.java 90.9% 93.3% 81.5% 90.3%
coverage coverage
 1   
 //  Copyright 2004 The Apache Software Foundation
 2   
 //
 3   
 // Licensed under the Apache License, Version 2.0 (the "License");
 4   
 // you may not use this file except in compliance with the License.
 5   
 // You may obtain a copy of the License at
 6   
 //
 7   
 //     http://www.apache.org/licenses/LICENSE-2.0
 8   
 //
 9   
 // Unless required by applicable law or agreed to in writing, software
 10   
 // distributed under the License is distributed on an "AS IS" BASIS,
 11   
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12   
 // See the License for the specific language governing permissions and
 13   
 // limitations under the License.
 14   
 
 15   
 package org.apache.hivemind.parse;
 16   
 
 17   
 import java.util.ArrayList;
 18   
 import java.util.HashMap;
 19   
 import java.util.List;
 20   
 import java.util.Map;
 21   
 
 22   
 import org.apache.hivemind.ApplicationRuntimeException;
 23   
 import org.apache.hivemind.HiveMind;
 24   
 import org.apache.hivemind.Location;
 25   
 import org.apache.hivemind.Resource;
 26   
 import org.apache.hivemind.impl.LocationImpl;
 27   
 import org.xml.sax.Attributes;
 28   
 import org.xml.sax.Locator;
 29   
 import org.xml.sax.SAXException;
 30   
 import org.xml.sax.SAXParseException;
 31   
 import org.xml.sax.helpers.DefaultHandler;
 32   
 
 33   
 /**
 34   
  * Abstract super-class for parsers based around the SAX event model.
 35   
  * This class provides support for managing a stack of elements, making it
 36   
  * reasonable to establish relationships between elements. It also assists in
 37   
  * setting the {@link org.apache.hivemind.Location} of elements as they are created.
 38   
  * 
 39   
  * <p>
 40   
  * This support is structured around both XML but is suited towards configuration files rather 
 41   
  * than documents, in that the <em>content</em> (parsable character data) within an element
 42   
  * is concatinated together and tracked as a single blob.
 43   
  *
 44   
  * @author Howard Lewis Ship
 45   
  */
 46   
 public abstract class AbstractParser extends DefaultHandler
 47   
 {
 48   
 
 49   
     /**
 50   
      * The parser is built around a stack of these Items.  This used to figure
 51   
      * out the current state, the element being processed, and the matching descriptor
 52   
      * object.
 53   
      */
 54   
     private static class Item
 55   
     {
 56   
         StringBuffer _buffer;
 57   
         String _elementName;
 58   
         boolean _ignoreCharacterData;
 59   
         Object _object;
 60   
 
 61   
         /**
 62   
          * Prior state of the parser before this item was pushed.
 63   
          */
 64   
         int _priorState;
 65   
 
 66  28921
         Item(String elementName, Object object, int priorState, boolean ignoreCharacterData)
 67   
         {
 68  28921
             _elementName = elementName;
 69  28921
             _object = object;
 70  28921
             _priorState = priorState;
 71  28921
             _ignoreCharacterData = ignoreCharacterData;
 72   
         }
 73   
 
 74  68382
         void addContent(char[] buffer, int start, int length)
 75   
         {
 76  68382
             if (_ignoreCharacterData)
 77  66710
                 return;
 78   
 
 79  1672
             if (_buffer == null)
 80  835
                 _buffer = new StringBuffer(length);
 81   
 
 82  1672
             _buffer.append(buffer, start, length);
 83   
         }
 84   
 
 85  3899
         String getContent()
 86   
         {
 87  3899
             if (_buffer != null)
 88  835
                 return _buffer.toString().trim();
 89   
 
 90  3064
             return null;
 91   
         }
 92   
     }
 93   
 
 94   
     private int _currentColumn;
 95   
     private int _currentLine;
 96   
     private Location _location;
 97   
     private Locator _locator;
 98   
     private Resource _resource;
 99   
     private List _stack;
 100   
     private int _state;
 101   
     private Item _top;
 102   
 
 103   
     /**
 104   
      * Accepts parseable character data from within an element and applies it to the
 105   
      * top stack element. This may be invoked multiple times by the parser, and the
 106   
      * overall data will accumulate. This content can be
 107   
      * retrieved via {@link #peekContent()}.
 108   
      */
 109  68382
     public void characters(char[] ch, int start, int length) throws SAXException
 110   
     {
 111  68382
         _top.addContent(ch, start, length);
 112   
     }
 113   
 
 114   
     /**
 115   
      * Invokes {@link #fatalError(SAXParseException)}.
 116   
      */
 117  0
     public void error(SAXParseException ex) throws SAXException
 118   
     {
 119  0
         fatalError(ex);
 120   
     }
 121   
 
 122   
     /**
 123   
      * @param ex exception to be thrown
 124   
      * @throws SAXParseException 
 125   
      */
 126  0
     public void fatalError(SAXParseException ex) throws SAXException
 127   
     {
 128  0
         throw ex;
 129   
     }
 130   
 
 131   
     /**
 132   
      * Returns a "path" to the current element, as a series of element names
 133   
      * seperated by slashes, i.e., "top/middle/leaf".
 134   
      */
 135  6
     protected String getElementPath()
 136   
     {
 137  6
         StringBuffer buffer = new StringBuffer();
 138   
 
 139  6
         int count = _stack.size();
 140  6
         for (int i = 0; i < count; i++)
 141   
         {
 142  12
             if (i > 0)
 143  6
                 buffer.append('/');
 144   
 
 145  12
             Item item = (Item) _stack.get(i);
 146   
 
 147  12
             buffer.append(item._elementName);
 148   
         }
 149   
 
 150  6
         return buffer.toString();
 151   
     }
 152   
 
 153   
     /**
 154   
      * Returns the current lcoation, as reported by the parser.
 155   
      */
 156  29479
     protected Location getLocation()
 157   
     {
 158  29479
         int line = _locator.getLineNumber();
 159  29479
         int column = _locator.getColumnNumber();
 160   
 
 161  29479
         if (line != _currentLine || column != _currentColumn)
 162  28922
             _location = null;
 163   
 
 164  29479
         if (_location == null)
 165  28922
             _location = new LocationImpl(_resource, line, column);
 166   
 
 167  29479
         _currentLine = line;
 168  29479
         _currentColumn = column;
 169   
         
 170  29479
         return _location;
 171   
     }
 172   
 
 173   
     /**
 174   
      * Returns the {@link Resource} being parsed (as set
 175   
      * by {@link #initializeParser(Resource, int)}.
 176   
      */
 177   
 
 178  2
     protected Resource getResource()
 179   
     {
 180  2
         return _resource;
 181   
     }
 182   
 
 183   
     /**
 184   
      * Returns the current state of the parser. State is initially
 185   
      * set by {@link #initializeParser(Resource, int) and is later updated
 186   
      * by {@link #push(String, Object, int)} and {@link #pop()}.
 187   
      */
 188  57832
     protected int getState()
 189   
     {
 190  57832
         return _state;
 191   
     }
 192   
 
 193   
     /**
 194   
      * Initializes the parser; this should be called before any SAX parse events
 195   
      * are received.
 196   
      * 
 197   
      * @param resource the resource being parsed (used for some error messages)
 198   
      * @param startState the initial state of the parser (the interpretation of state
 199   
      * is determined by subclasses)
 200   
      */
 201  216
     protected void initializeParser(Resource resource, int startState)
 202   
     {
 203  216
         _resource = resource;
 204  216
         _stack = new ArrayList();
 205   
 
 206  216
         _location = null;
 207  216
         _state = startState;
 208   
     }
 209   
 
 210   
     /**
 211   
      * Peeks at the top element on the stack, and returns
 212   
      * its content (the accumuulated parseable character data directly
 213   
      * enclosed by its start/end tags.
 214   
      */
 215  3899
     protected String peekContent()
 216   
     {
 217  3899
         return _top.getContent();
 218   
     }
 219   
 
 220   
     /**
 221   
      * Peeks at the top element on the stack and returns its element name.
 222   
      */
 223  22049
     protected String peekElementName()
 224   
     {
 225  22049
         return _top._elementName;
 226   
     }
 227   
 
 228   
     /**
 229   
      * Peeks at the top element on the stack and returns the object for that element.
 230   
      */
 231   
 
 232  32829
     protected Object peekObject()
 233   
     {
 234  32829
         return _top._object;
 235   
     }
 236   
 
 237   
     /**
 238   
      * Invoked when the closing tag for an element is enountered
 239   
      * {i.e, from {@link #endElement(String)}). This removes the corresponding
 240   
      * item from the stack, and sets the parser state back to the (new) top element's
 241   
      * state.
 242   
      */
 243  28910
     protected void pop()
 244   
     {
 245  28910
         int count = _stack.size();
 246   
 
 247  28910
         _state = _top._priorState;
 248   
 
 249  28910
         _stack.remove(count - 1);
 250   
 
 251  28910
         if (count == 1)
 252  213
             _top = null;
 253   
         else
 254  28697
             _top = (Item) _stack.get(count - 2);
 255   
     }
 256   
 
 257   
     /**
 258   
      * Enters a new state, pushing an object onto the stack.
 259   
      * Invokes {@link #push(String, Object, int, boolean), and ignores
 260   
      * character data within the element.
 261   
      * 
 262   
      * @param elementName the element whose start tag was just parsed
 263   
      * @param object the object created to represent the new object
 264   
      * @param state the new state for the parse
 265   
      */
 266  25022
     protected void push(String elementName, Object object, int state)
 267   
     {
 268  25022
         push(elementName, object, state, true);
 269   
     }
 270   
 
 271   
     /**
 272   
      * Enters a new state, pushing an object onto the stack. If the object
 273   
      * implements {@link ILocationHolder} then its location property
 274   
      * is set to the current location.
 275   
      * 
 276   
      * @param elementName the element whose start tag was just parsed
 277   
      * @param object the object created to represent the new object
 278   
      * @param state the new state for the parse
 279   
      * @param ignoreCharacterData if true, then any character data (typically whitespace)
 280   
      * directly enclosed by the element is ignored
 281   
      */
 282  28921
     protected void push(String elementName, Object object, int state, boolean ignoreCharacterData)
 283   
     {
 284  28921
         HiveMind.setLocation(object, getLocation());
 285   
 
 286  28921
         Item item = new Item(elementName, object, _state, ignoreCharacterData);
 287   
 
 288  28921
         _stack.add(item);
 289   
 
 290  28921
         _top = item;
 291  28921
         _state = state;
 292   
     }
 293   
 
 294   
     /**
 295   
      * Resets all state after a parse. 
 296   
      */
 297  216
     protected void resetParser()
 298   
     {
 299  216
         _resource = null;
 300  216
         _locator = null;
 301  216
         _stack = null;
 302  216
         _location = null;
 303   
     }
 304   
 
 305   
     /**
 306   
      * Invoked by the parser, the locator is stored and later used
 307   
      * by {@link #getLocation()}.
 308   
      */
 309  216
     public void setDocumentLocator(Locator locator)
 310   
     {
 311  216
         _locator = locator;
 312   
     }
 313   
 
 314   
     /**
 315   
      * Forces a change to a specific state.
 316   
      */
 317  0
     protected void setState(int state)
 318   
     {
 319  0
         _state = state;
 320   
     }
 321   
 
 322   
     /**
 323   
      * Invoked when an unexpected element is parsed (useful for
 324   
      * parses that don't perform validation, or when there's no DTD).
 325   
      * 
 326   
      * @throws ApplicationRuntimeException describing the situation
 327   
      */
 328  1
     protected void unexpectedElement(String elementName)
 329   
     {
 330  1
         throw new ApplicationRuntimeException(
 331   
             ParseMessages.unexpectedElement(elementName, getElementPath()),
 332   
             getLocation(),
 333   
             null);
 334   
     }
 335   
 
 336   
     /**
 337   
      * Ocassionaly it is necessary to "change our mind" about what's
 338   
      * on the top of the stack.
 339   
      * 
 340   
      * @param object the new object for the top stack element
 341   
      */
 342  0
     protected void updateObject(Object object)
 343   
     {
 344  0
         _top._object = object;
 345   
     }
 346   
 
 347   
     /**
 348   
      * Invokes {@link #fatalError(SAXParseException)},
 349   
      */
 350  0
     public void warning(SAXParseException ex) throws SAXException
 351   
     {
 352  0
         fatalError(ex);
 353   
     }
 354   
 
 355  28922
     private Map constructAttributesMap(Attributes attributes)
 356   
     {
 357  28922
         Map result = new HashMap();
 358  28922
         int count = attributes.getLength();
 359   
 
 360  28922
         for (int i = 0; i < count; i++)
 361   
         {
 362  40610
             String key = attributes.getLocalName(i);
 363   
 
 364  40610
             if (HiveMind.isBlank(key))
 365  40610
                 key = attributes.getQName(i);
 366   
 
 367  40610
             String value = attributes.getValue(i);
 368   
 
 369  40610
             result.put(key, value);
 370   
         }
 371   
 
 372  28922
         return result;
 373   
     }
 374   
 
 375   
     /**
 376   
      * Invoked when an element's start tag is recognized. The element and attributes are provided to the subclass
 377   
      * for further processing.
 378   
      */
 379   
     protected abstract void begin(String elementName, Map attributes);
 380   
 
 381   
     /**
 382   
      * Invoked when an element's close tag is recognized. The element is provided. The content of the
 383   
      * element (the unparsed whitespace within the element's tags) is available via
 384   
      * {@link #peekContent()}.
 385   
      */
 386   
 
 387   
     protected abstract void end(String elementName);
 388   
 
 389  28910
     public void endElement(String uri, String localName, String qName) throws SAXException
 390   
     {
 391  28910
         end(getElementName(localName, qName));
 392   
     }
 393   
 
 394  28922
     public void startElement(String uri, String localName, String qName, Attributes attributes)
 395   
         throws SAXException
 396   
     {
 397  28922
         String elementName = getElementName(localName, qName);
 398   
 
 399  28922
         begin(elementName, constructAttributesMap(attributes));
 400   
     }
 401   
 
 402  57832
     private String getElementName(String localName, String qName)
 403   
     {
 404  57832
         return qName != null ? qName : localName;
 405   
     }
 406   
 }
 407