Clover coverage report - Code Coverage for tapestry release 4.0-alpha-3
Coverage timestamp: Mon May 16 2005 09:05:49 EDT
file stats: LOC: 574   Methods: 34
NCLOC: 317   Classes: 1
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
RuleDirectedParser.java 63% 82% 82.4% 77.9%
coverage coverage
 1   
 // Copyright 2004, 2005 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.tapestry.util.xml;
 16   
 
 17   
 import java.io.IOException;
 18   
 import java.io.InputStream;
 19   
 import java.net.URL;
 20   
 import java.util.ArrayList;
 21   
 import java.util.HashMap;
 22   
 import java.util.List;
 23   
 import java.util.Map;
 24   
 
 25   
 import javax.xml.parsers.ParserConfigurationException;
 26   
 import javax.xml.parsers.SAXParser;
 27   
 import javax.xml.parsers.SAXParserFactory;
 28   
 
 29   
 import org.apache.commons.logging.Log;
 30   
 import org.apache.commons.logging.LogFactory;
 31   
 import org.apache.hivemind.ApplicationRuntimeException;
 32   
 import org.apache.hivemind.HiveMind;
 33   
 import org.apache.hivemind.Location;
 34   
 import org.apache.hivemind.Resource;
 35   
 import org.apache.hivemind.impl.LocationImpl;
 36   
 import org.apache.tapestry.Tapestry;
 37   
 import org.apache.tapestry.util.RegexpMatcher;
 38   
 import org.xml.sax.Attributes;
 39   
 import org.xml.sax.InputSource;
 40   
 import org.xml.sax.Locator;
 41   
 import org.xml.sax.SAXException;
 42   
 import org.xml.sax.SAXParseException;
 43   
 import org.xml.sax.helpers.DefaultHandler;
 44   
 
 45   
 /**
 46   
  * A simplified version of {@link org.apache.commons.digester.Digester}. This version is without as
 47   
  * many bells and whistles but has some key features needed when parsing a document (rather than a
 48   
  * configuration file): <br>
 49   
  * <ul>
 50   
  * <li>Notifications for each bit of text
 51   
  * </ul>
 52   
  * <li>Tracking of exact location within the document.</li>
 53   
  * </ul>
 54   
  * <p>
 55   
  * Like Digester, there's an object stack and a rule stack. The rules are much simpler (more
 56   
  * coding), in that there's a one-to-one relationship between an element and a rule.
 57   
  * <p>
 58   
  * Based on SAX2.
 59   
  * 
 60   
  * @author Howard Lewis Ship
 61   
  * @since 3.0
 62   
  */
 63   
 
 64   
 public class RuleDirectedParser extends DefaultHandler
 65   
 {
 66   
     private static final Log LOG = LogFactory.getLog(RuleDirectedParser.class);
 67   
 
 68   
     private Resource _documentLocation;
 69   
 
 70   
     private List _ruleStack = new ArrayList();
 71   
 
 72   
     private List _objectStack = new ArrayList();
 73   
 
 74   
     private Object _documentObject;
 75   
 
 76   
     private Locator _locator;
 77   
 
 78   
     private int _line = -1;
 79   
 
 80   
     private int _column = -1;
 81   
 
 82   
     private Location _location;
 83   
 
 84   
     private static SAXParserFactory _parserFactory;
 85   
 
 86   
     private SAXParser _parser;
 87   
 
 88   
     private RegexpMatcher _matcher;
 89   
 
 90   
     private String _uri;
 91   
 
 92   
     private String _localName;
 93   
 
 94   
     private String _qName;
 95   
 
 96   
     /**
 97   
      * Map of {@link IRule}keyed on the local name of the element.
 98   
      */
 99   
     private Map _ruleMap = new HashMap();
 100   
 
 101   
     /**
 102   
      * Used to accumlate content provided by
 103   
      * {@link org.xml.sax.ContentHandler#characters(char[], int, int)}.
 104   
      */
 105   
 
 106   
     private StringBuffer _contentBuffer = new StringBuffer();
 107   
 
 108   
     /**
 109   
      * Map of paths to external entities (such as the DTD) keyed on public id.
 110   
      */
 111   
 
 112   
     private Map _entities = new HashMap();
 113   
 
 114  18
     public Object parse(Resource documentLocation)
 115   
     {
 116  18
         if (LOG.isDebugEnabled())
 117  0
             LOG.debug("Parsing: " + documentLocation);
 118   
 
 119  18
         try
 120   
         {
 121  18
             _documentLocation = documentLocation;
 122   
 
 123  18
             URL url = documentLocation.getResourceURL();
 124   
 
 125  18
             if (url == null)
 126  0
                 throw new DocumentParseException(Tapestry.format(
 127   
                         "RuleDrivenParser.resource-missing",
 128   
                         documentLocation), documentLocation, null);
 129   
 
 130  18
             return parse(url);
 131   
         }
 132   
         finally
 133   
         {
 134  18
             _documentLocation = null;
 135  18
             _ruleStack.clear();
 136  18
             _objectStack.clear();
 137  18
             _documentObject = null;
 138   
 
 139  18
             _uri = null;
 140  18
             _localName = null;
 141  18
             _qName = null;
 142   
 
 143  18
             _line = -1;
 144  18
             _column = -1;
 145  18
             _location = null;
 146  18
             _locator = null;
 147   
 
 148  18
             _contentBuffer.setLength(0);
 149   
         }
 150   
     }
 151   
 
 152  18
     protected Object parse(URL url)
 153   
     {
 154  18
         if (_parser == null)
 155  18
             _parser = constructParser();
 156   
 
 157  18
         InputStream stream = null;
 158   
 
 159  18
         try
 160   
         {
 161  18
             stream = url.openStream();
 162   
         }
 163   
         catch (IOException ex)
 164   
         {
 165  0
             throw new DocumentParseException(Tapestry.format(
 166   
                     "RuleDrivenParser.unable-to-open-resource",
 167   
                     url), _documentLocation, ex);
 168   
         }
 169   
 
 170  18
         InputSource source = new InputSource(stream);
 171   
 
 172  18
         try
 173   
         {
 174  18
             _parser.parse(source, this);
 175   
 
 176  15
             stream.close();
 177   
         }
 178   
         catch (Exception ex)
 179   
         {
 180  3
             throw new DocumentParseException(Tapestry.format(
 181   
                     "RuleDrivenParser.parse-error",
 182   
                     url,
 183   
                     ex.getMessage()), getLocation(), ex);
 184   
         }
 185   
 
 186  15
         if (LOG.isDebugEnabled())
 187  0
             LOG.debug("Document parsed as: " + _documentObject);
 188   
 
 189  15
         return _documentObject;
 190   
     }
 191   
 
 192   
     /**
 193   
      * Returns an {@link ILocation}representing the current position within the document (depending
 194   
      * on the parser, this may be accurate to column number level).
 195   
      */
 196   
 
 197  116
     public Location getLocation()
 198   
     {
 199  116
         if (_locator == null)
 200  0
             return null;
 201   
 
 202  116
         int line = _locator.getLineNumber();
 203  116
         int column = _locator.getColumnNumber();
 204   
 
 205  116
         if (_line != line || _column != column)
 206   
         {
 207  102
             _location = null;
 208  102
             _line = line;
 209  102
             _column = column;
 210   
         }
 211   
 
 212  116
         if (_location == null)
 213  102
             _location = new LocationImpl(_documentLocation, _line, _column);
 214   
 
 215  116
         return _location;
 216   
     }
 217   
 
 218   
     /**
 219   
      * Pushes an object onto the object stack. The first object pushed is the "document object", the
 220   
      * root object returned by the parse.
 221   
      */
 222  49
     public void push(Object object)
 223   
     {
 224  49
         if (_documentObject == null)
 225  17
             _documentObject = object;
 226   
 
 227  49
         push(_objectStack, object, "object stack");
 228   
     }
 229   
 
 230   
     /**
 231   
      * Returns the top object on the object stack.
 232   
      */
 233  94
     public Object peek()
 234   
     {
 235  94
         return peek(_objectStack, 0);
 236   
     }
 237   
 
 238   
     /**
 239   
      * Returns an object within the object stack, at depth. Depth 0 is the top object, depth 1 is
 240   
      * the next-to-top object, etc.
 241   
      */
 242   
 
 243  0
     public Object peek(int depth)
 244   
     {
 245  0
         return peek(_objectStack, depth);
 246   
     }
 247   
 
 248   
     /**
 249   
      * Removes and returns the top object on the object stack.
 250   
      */
 251  47
     public Object pop()
 252   
     {
 253  47
         return pop(_objectStack, "object stack");
 254   
     }
 255   
 
 256  114
     private Object pop(List list, String name)
 257   
     {
 258  114
         Object result = list.remove(list.size() - 1);
 259   
 
 260  114
         if (LOG.isDebugEnabled())
 261  0
             LOG.debug("Popped " + result + " off " + name + " (at " + getLocation() + ")");
 262   
 
 263  114
         return result;
 264   
     }
 265   
 
 266  215
     private Object peek(List list, int depth)
 267   
     {
 268  215
         return list.get(list.size() - 1 - depth);
 269   
     }
 270   
 
 271  120
     private void push(List list, Object object, String name)
 272   
     {
 273  120
         if (LOG.isDebugEnabled())
 274  0
             LOG.debug("Pushing " + object + " onto " + name + " (at " + getLocation() + ")");
 275   
 
 276  120
         list.add(object);
 277   
     }
 278   
 
 279   
     /**
 280   
      * Pushes a new rule onto the rule stack.
 281   
      */
 282   
 
 283  71
     protected void pushRule(IRule rule)
 284   
     {
 285  71
         push(_ruleStack, rule, "rule stack");
 286   
     }
 287   
 
 288   
     /**
 289   
      * Returns the top rule on the stack.
 290   
      */
 291   
 
 292  121
     protected IRule peekRule()
 293   
     {
 294  121
         return (IRule) peek(_ruleStack, 0);
 295   
     }
 296   
 
 297  67
     protected IRule popRule()
 298   
     {
 299  67
         return (IRule) pop(_ruleStack, "rule stack");
 300   
     }
 301   
 
 302  216
     public void addRule(String localElementName, IRule rule)
 303   
     {
 304  216
         _ruleMap.put(localElementName, rule);
 305   
     }
 306   
 
 307   
     /**
 308   
      * Registers a public id and corresponding input source. Generally, the source is a wrapper
 309   
      * around an input stream to a package resource.
 310   
      * 
 311   
      * @param publicId
 312   
      *            the public identifier to be registerred, generally the publicId of a DTD related
 313   
      *            to the document being parsed
 314   
      * @param entityPath
 315   
      *            the resource path of the entity, typically a DTD file. Relative files names are
 316   
      *            expected to be stored in the same package as the class file, otherwise a leading
 317   
      *            slash is an absolute pathname within the classpath.
 318   
      */
 319   
 
 320  72
     public void registerEntity(String publicId, String entityPath)
 321   
     {
 322  72
         if (LOG.isDebugEnabled())
 323  0
             LOG.debug("Registering " + publicId + " as " + entityPath);
 324   
 
 325  72
         if (_entities == null)
 326  0
             _entities = new HashMap();
 327   
 
 328  72
         _entities.put(publicId, entityPath);
 329   
     }
 330   
 
 331  71
     protected IRule selectRule(String localName, Attributes attributes)
 332   
     {
 333  71
         IRule rule = (IRule) _ruleMap.get(localName);
 334   
 
 335  71
         if (rule == null)
 336  0
             throw new DocumentParseException(Tapestry.format(
 337   
                     "RuleDrivenParser.no-rule-for-element",
 338   
                     localName), getLocation(), null);
 339   
 
 340  71
         return rule;
 341   
     }
 342   
 
 343   
     /**
 344   
      * Uses the {@link Locator}to track the position in the document as a {@link ILocation}. This
 345   
      * is invoked once (before the initial element is parsed) and the Locator is retained and
 346   
      * queried as to the current file location.
 347   
      * 
 348   
      * @see #getLocation()
 349   
      */
 350  18
     public void setDocumentLocator(Locator locator)
 351   
     {
 352  18
         _locator = locator;
 353   
     }
 354   
 
 355   
     /**
 356   
      * Accumulates the content in a buffer; the concatinated content is provided to the top rule
 357   
      * just before any start or end tag.
 358   
      */
 359  98
     public void characters(char[] ch, int start, int length) throws SAXException
 360   
     {
 361  98
         _contentBuffer.append(ch, start, length);
 362   
     }
 363   
 
 364   
     /**
 365   
      * Pops the top rule off the stack and invokes {@link IRule#endElementt(RuleDirectedParser)}.
 366   
      */
 367  67
     public void endElement(String uri, String localName, String qName) throws SAXException
 368   
     {
 369  67
         fireContentRule();
 370   
 
 371  67
         _uri = uri;
 372  67
         _localName = localName;
 373  67
         _qName = qName;
 374   
 
 375  67
         popRule().endElement(this);
 376   
     }
 377   
 
 378   
     /**
 379   
      * Ignorable content is ignored.
 380   
      */
 381  61
     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
 382   
     {
 383   
     }
 384   
 
 385   
     /**
 386   
      * Invokes {@link #selectRule(String, Attributes)}to choose a new rule, which is pushed onto
 387   
      * the rule stack, then invokes {@link IRule#startElement(RuleDirectedParser, Attributes)}.
 388   
      */
 389  71
     public void startElement(String uri, String localName, String qName, Attributes attributes)
 390   
             throws SAXException
 391   
     {
 392  71
         fireContentRule();
 393   
 
 394  71
         _uri = uri;
 395  71
         _localName = localName;
 396  71
         _qName = qName;
 397   
 
 398  71
         String name = extractName(uri, localName, qName);
 399   
 
 400  71
         IRule newRule = selectRule(name, attributes);
 401   
 
 402  71
         pushRule(newRule);
 403   
 
 404  71
         newRule.startElement(this, attributes);
 405   
     }
 406   
 
 407  71
     private String extractName(String uri, String localName, String qName)
 408   
     {
 409  71
         return HiveMind.isBlank(localName) ? qName : localName;
 410   
     }
 411   
 
 412   
     /**
 413   
      * Uses {@link javax.xml.parsers.SAXParserFactory}to create a instance of a validation SAX2
 414   
      * parser.
 415   
      */
 416  18
     protected synchronized SAXParser constructParser()
 417   
     {
 418  18
         if (_parserFactory == null)
 419   
         {
 420  1
             _parserFactory = SAXParserFactory.newInstance();
 421  1
             configureParserFactory(_parserFactory);
 422   
         }
 423   
 
 424  18
         try
 425   
         {
 426  18
             return _parserFactory.newSAXParser();
 427   
         }
 428   
         catch (SAXException ex)
 429   
         {
 430  0
             throw new ApplicationRuntimeException(ex);
 431   
         }
 432   
         catch (ParserConfigurationException ex)
 433   
         {
 434  0
             throw new ApplicationRuntimeException(ex);
 435   
         }
 436   
 
 437   
     }
 438   
 
 439   
     /**
 440   
      * Configures a {@link SAXParserFactory}before {@link SAXParserFactory#newSAXParser()}is
 441   
      * invoked. The default implementation sets validating to true and namespaceAware to false,
 442   
      */
 443   
 
 444  1
     protected void configureParserFactory(SAXParserFactory factory)
 445   
     {
 446  1
         factory.setValidating(true);
 447  1
         factory.setNamespaceAware(false);
 448   
     }
 449   
 
 450   
     /**
 451   
      * Throws the exception.
 452   
      */
 453  1
     public void error(SAXParseException ex) throws SAXException
 454   
     {
 455  1
         fatalError(ex);
 456   
     }
 457   
 
 458   
     /**
 459   
      * Throws the exception.
 460   
      */
 461  1
     public void fatalError(SAXParseException ex) throws SAXException
 462   
     {
 463   
         // Sometimes, a bad parse "corrupts" a parser so that it doesn't
 464   
         // work properly for future parses (of valid documents),
 465   
         // so discard it here.
 466   
 
 467  1
         _parser = null;
 468   
 
 469  1
         throw ex;
 470   
     }
 471   
 
 472   
     /**
 473   
      * Throws the exception.
 474   
      */
 475  0
     public void warning(SAXParseException ex) throws SAXException
 476   
     {
 477  0
         fatalError(ex);
 478   
     }
 479   
 
 480  17
     public InputSource resolveEntity(String publicId, String systemId) throws SAXException
 481   
     {
 482  17
         String entityPath = null;
 483   
 
 484  17
         if (LOG.isDebugEnabled())
 485  0
             LOG.debug("Attempting to resolve entity; publicId = " + publicId + " systemId = "
 486   
                     + systemId);
 487   
 
 488  17
         if (_entities != null)
 489  17
             entityPath = (String) _entities.get(publicId);
 490   
 
 491  17
         if (entityPath == null)
 492   
         {
 493  0
             if (LOG.isDebugEnabled())
 494  0
                 LOG.debug("Entity not found, using " + systemId);
 495   
 
 496  0
             return null;
 497   
         }
 498   
 
 499  17
         InputStream stream = getClass().getResourceAsStream(entityPath);
 500   
 
 501  17
         InputSource result = new InputSource(stream);
 502   
 
 503  17
         if (result != null && LOG.isDebugEnabled())
 504  0
             LOG.debug("Resolved " + publicId + " as " + result + " (for " + entityPath + ")");
 505   
 
 506  17
         return result;
 507   
     }
 508   
 
 509   
     /**
 510   
      * Validates that the input value matches against the specified Perl5 pattern. If valid, the
 511   
      * method simply returns. If not a match, then an error message is generated (using the errorKey
 512   
      * and the input value) and a {@link InvalidStringException}is thrown.
 513   
      */
 514   
 
 515  31
     public void validate(String value, String pattern, String errorKey)
 516   
             throws DocumentParseException
 517   
     {
 518  31
         if (_matcher == null)
 519  12
             _matcher = new RegexpMatcher();
 520   
 
 521  31
         if (_matcher.matches(pattern, value))
 522  29
             return;
 523   
 
 524  2
         throw new InvalidStringException(Tapestry.format(errorKey, value), value, getLocation());
 525   
     }
 526   
 
 527  0
     public Resource getDocumentLocation()
 528   
     {
 529  0
         return _documentLocation;
 530   
     }
 531   
 
 532   
     /**
 533   
      * Returns the localName for the current element.
 534   
      * 
 535   
      * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String,
 536   
      *      java.lang.String, org.xml.sax.Attributes)
 537   
      */
 538  0
     public String getLocalName()
 539   
     {
 540  0
         return _localName;
 541   
     }
 542   
 
 543   
     /**
 544   
      * Returns the qualified name for the current element.
 545   
      * 
 546   
      * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String,
 547   
      *      java.lang.String, org.xml.sax.Attributes)
 548   
      */
 549  0
     public String getQName()
 550   
     {
 551  0
         return _qName;
 552   
     }
 553   
 
 554   
     /**
 555   
      * Returns the URI for the current element.
 556   
      * 
 557   
      * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String,
 558   
      *      java.lang.String, org.xml.sax.Attributes)
 559   
      */
 560  0
     public String getUri()
 561   
     {
 562  0
         return _uri;
 563   
     }
 564   
 
 565  138
     private void fireContentRule()
 566   
     {
 567  138
         String content = _contentBuffer.toString();
 568  138
         _contentBuffer.setLength(0);
 569   
 
 570  138
         if (!_ruleStack.isEmpty())
 571  121
             peekRule().content(this, content);
 572   
     }
 573   
 
 574   
 }