KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > util > xml > RuleDirectedParser


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 JavaDoc;
18 import java.io.InputStream JavaDoc;
19 import java.net.URL JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Map JavaDoc;
24
25 import javax.xml.parsers.ParserConfigurationException JavaDoc;
26 import javax.xml.parsers.SAXParser JavaDoc;
27 import javax.xml.parsers.SAXParserFactory JavaDoc;
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 JavaDoc;
39 import org.xml.sax.InputSource JavaDoc;
40 import org.xml.sax.Locator JavaDoc;
41 import org.xml.sax.SAXException JavaDoc;
42 import org.xml.sax.SAXParseException JavaDoc;
43 import org.xml.sax.helpers.DefaultHandler JavaDoc;
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 JavaDoc
65 {
66     private static final Log LOG = LogFactory.getLog(RuleDirectedParser.class);
67
68     private Resource _documentLocation;
69
70     private List JavaDoc _ruleStack = new ArrayList JavaDoc();
71
72     private List JavaDoc _objectStack = new ArrayList JavaDoc();
73
74     private Object JavaDoc _documentObject;
75
76     private Locator JavaDoc _locator;
77
78     private int _line = -1;
79
80     private int _column = -1;
81
82     private Location _location;
83
84     private static SAXParserFactory JavaDoc _parserFactory;
85
86     private SAXParser JavaDoc _parser;
87
88     private RegexpMatcher _matcher;
89
90     private String JavaDoc _uri;
91
92     private String JavaDoc _localName;
93
94     private String JavaDoc _qName;
95
96     /**
97      * Map of {@link IRule}keyed on the local name of the element.
98      */

99     private Map JavaDoc _ruleMap = new HashMap JavaDoc();
100
101     /**
102      * Used to accumlate content provided by
103      * {@link org.xml.sax.ContentHandler#characters(char[], int, int)}.
104      */

105
106     private StringBuffer JavaDoc _contentBuffer = new StringBuffer JavaDoc();
107
108     /**
109      * Map of paths to external entities (such as the DTD) keyed on public id.
110      */

111
112     private Map JavaDoc _entities = new HashMap JavaDoc();
113
114     public Object JavaDoc parse(Resource documentLocation)
115     {
116         if (LOG.isDebugEnabled())
117             LOG.debug("Parsing: " + documentLocation);
118
119         try
120         {
121             _documentLocation = documentLocation;
122
123             URL JavaDoc url = documentLocation.getResourceURL();
124
125             if (url == null)
126                 throw new DocumentParseException(Tapestry.format(
127                         "RuleDrivenParser.resource-missing",
128                         documentLocation), documentLocation, null);
129
130             return parse(url);
131         }
132         finally
133         {
134             _documentLocation = null;
135             _ruleStack.clear();
136             _objectStack.clear();
137             _documentObject = null;
138
139             _uri = null;
140             _localName = null;
141             _qName = null;
142
143             _line = -1;
144             _column = -1;
145             _location = null;
146             _locator = null;
147
148             _contentBuffer.setLength(0);
149         }
150     }
151
152     protected Object JavaDoc parse(URL JavaDoc url)
153     {
154         if (_parser == null)
155             _parser = constructParser();
156
157         InputStream JavaDoc stream = null;
158
159         try
160         {
161             stream = url.openStream();
162         }
163         catch (IOException JavaDoc ex)
164         {
165             throw new DocumentParseException(Tapestry.format(
166                     "RuleDrivenParser.unable-to-open-resource",
167                     url), _documentLocation, ex);
168         }
169
170         InputSource JavaDoc source = new InputSource JavaDoc(stream);
171
172         try
173         {
174             _parser.parse(source, this);
175
176             stream.close();
177         }
178         catch (Exception JavaDoc ex)
179         {
180             throw new DocumentParseException(Tapestry.format(
181                     "RuleDrivenParser.parse-error",
182                     url,
183                     ex.getMessage()), getLocation(), ex);
184         }
185
186         if (LOG.isDebugEnabled())
187             LOG.debug("Document parsed as: " + _documentObject);
188
189         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     public Location getLocation()
198     {
199         if (_locator == null)
200             return null;
201
202         int line = _locator.getLineNumber();
203         int column = _locator.getColumnNumber();
204
205         if (_line != line || _column != column)
206         {
207             _location = null;
208             _line = line;
209             _column = column;
210         }
211
212         if (_location == null)
213             _location = new LocationImpl(_documentLocation, _line, _column);
214
215         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     public void push(Object JavaDoc object)
223     {
224         if (_documentObject == null)
225             _documentObject = object;
226
227         push(_objectStack, object, "object stack");
228     }
229
230     /**
231      * Returns the top object on the object stack.
232      */

233     public Object JavaDoc peek()
234     {
235         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     public Object JavaDoc peek(int depth)
244     {
245         return peek(_objectStack, depth);
246     }
247
248     /**
249      * Removes and returns the top object on the object stack.
250      */

251     public Object JavaDoc pop()
252     {
253         return pop(_objectStack, "object stack");
254     }
255
256     private Object JavaDoc pop(List JavaDoc list, String JavaDoc name)
257     {
258         Object JavaDoc result = list.remove(list.size() - 1);
259
260         if (LOG.isDebugEnabled())
261             LOG.debug("Popped " + result + " off " + name + " (at " + getLocation() + ")");
262
263         return result;
264     }
265
266     private Object JavaDoc peek(List JavaDoc list, int depth)
267     {
268         return list.get(list.size() - 1 - depth);
269     }
270
271     private void push(List JavaDoc list, Object JavaDoc object, String JavaDoc name)
272     {
273         if (LOG.isDebugEnabled())
274             LOG.debug("Pushing " + object + " onto " + name + " (at " + getLocation() + ")");
275
276         list.add(object);
277     }
278
279     /**
280      * Pushes a new rule onto the rule stack.
281      */

282
283     protected void pushRule(IRule rule)
284     {
285         push(_ruleStack, rule, "rule stack");
286     }
287
288     /**
289      * Returns the top rule on the stack.
290      */

291
292     protected IRule peekRule()
293     {
294         return (IRule) peek(_ruleStack, 0);
295     }
296
297     protected IRule popRule()
298     {
299         return (IRule) pop(_ruleStack, "rule stack");
300     }
301
302     public void addRule(String JavaDoc localElementName, IRule rule)
303     {
304         _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     public void registerEntity(String JavaDoc publicId, String JavaDoc entityPath)
321     {
322         if (LOG.isDebugEnabled())
323             LOG.debug("Registering " + publicId + " as " + entityPath);
324
325         if (_entities == null)
326             _entities = new HashMap JavaDoc();
327
328         _entities.put(publicId, entityPath);
329     }
330
331     protected IRule selectRule(String JavaDoc localName, Attributes JavaDoc attributes)
332     {
333         IRule rule = (IRule) _ruleMap.get(localName);
334
335         if (rule == null)
336             throw new DocumentParseException(Tapestry.format(
337                     "RuleDrivenParser.no-rule-for-element",
338                     localName), getLocation(), null);
339
340         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     public void setDocumentLocator(Locator JavaDoc locator)
351     {
352         _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     public void characters(char[] ch, int start, int length) throws SAXException JavaDoc
360     {
361         _contentBuffer.append(ch, start, length);
362     }
363
364     /**
365      * Pops the top rule off the stack and invokes {@link IRule#endElementt(RuleDirectedParser)}.
366      */

367     public void endElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName) throws SAXException JavaDoc
368     {
369         fireContentRule();
370
371         _uri = uri;
372         _localName = localName;
373         _qName = qName;
374
375         popRule().endElement(this);
376     }
377
378     /**
379      * Ignorable content is ignored.
380      */

381     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException JavaDoc
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     public void startElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc attributes)
390             throws SAXException JavaDoc
391     {
392         fireContentRule();
393
394         _uri = uri;
395         _localName = localName;
396         _qName = qName;
397
398         String JavaDoc name = extractName(uri, localName, qName);
399
400         IRule newRule = selectRule(name, attributes);
401
402         pushRule(newRule);
403
404         newRule.startElement(this, attributes);
405     }
406
407     private String JavaDoc extractName(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName)
408     {
409         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     protected synchronized SAXParser JavaDoc constructParser()
417     {
418         if (_parserFactory == null)
419         {
420             _parserFactory = SAXParserFactory.newInstance();
421             configureParserFactory(_parserFactory);
422         }
423
424         try
425         {
426             return _parserFactory.newSAXParser();
427         }
428         catch (SAXException JavaDoc ex)
429         {
430             throw new ApplicationRuntimeException(ex);
431         }
432         catch (ParserConfigurationException JavaDoc ex)
433         {
434             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     protected void configureParserFactory(SAXParserFactory JavaDoc factory)
445     {
446         factory.setValidating(true);
447         factory.setNamespaceAware(false);
448     }
449
450     /**
451      * Throws the exception.
452      */

453     public void error(SAXParseException JavaDoc ex) throws SAXException JavaDoc
454     {
455         fatalError(ex);
456     }
457
458     /**
459      * Throws the exception.
460      */

461     public void fatalError(SAXParseException JavaDoc ex) throws SAXException JavaDoc
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         _parser = null;
468
469         throw ex;
470     }
471
472     /**
473      * Throws the exception.
474      */

475     public void warning(SAXParseException JavaDoc ex) throws SAXException JavaDoc
476     {
477         fatalError(ex);
478     }
479
480     public InputSource JavaDoc resolveEntity(String JavaDoc publicId, String JavaDoc systemId) throws SAXException JavaDoc
481     {
482         String JavaDoc entityPath = null;
483
484         if (LOG.isDebugEnabled())
485             LOG.debug("Attempting to resolve entity; publicId = " + publicId + " systemId = "
486                     + systemId);
487
488         if (_entities != null)
489             entityPath = (String JavaDoc) _entities.get(publicId);
490
491         if (entityPath == null)
492         {
493             if (LOG.isDebugEnabled())
494                 LOG.debug("Entity not found, using " + systemId);
495
496             return null;
497         }
498
499         InputStream JavaDoc stream = getClass().getResourceAsStream(entityPath);
500
501         InputSource JavaDoc result = new InputSource JavaDoc(stream);
502
503         if (result != null && LOG.isDebugEnabled())
504             LOG.debug("Resolved " + publicId + " as " + result + " (for " + entityPath + ")");
505
506         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     public void validate(String JavaDoc value, String JavaDoc pattern, String JavaDoc errorKey)
516             throws DocumentParseException
517     {
518         if (_matcher == null)
519             _matcher = new RegexpMatcher();
520
521         if (_matcher.matches(pattern, value))
522             return;
523
524         throw new InvalidStringException(Tapestry.format(errorKey, value), value, getLocation());
525     }
526
527     public Resource getDocumentLocation()
528     {
529         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     public String JavaDoc getLocalName()
539     {
540         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     public String JavaDoc getQName()
550     {
551         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     public String JavaDoc getUri()
561     {
562         return _uri;
563     }
564
565     private void fireContentRule()
566     {
567         String JavaDoc content = _contentBuffer.toString();
568         _contentBuffer.setLength(0);
569
570         if (!_ruleStack.isEmpty())
571             peekRule().content(this, content);
572     }
573
574 }
Popular Tags