001: /* ========================================================================
002: * JCommon : a free general purpose class library for the Java(tm) platform
003: * ========================================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jcommon/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * ------------------------
028: * AbstractModelReader.java
029: * ------------------------
030: * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
031: *
032: * Original Author: Thomas Morgner;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: AbstractModelReader.java,v 1.8 2005/10/18 13:33:53 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 12-Nov-2003 : Initial version
040: * 25-Nov-2003 : Updated header (DG);
041: *
042: */
043:
044: package org.jfree.xml.util;
045:
046: import java.io.BufferedInputStream;
047: import java.io.InputStream;
048: import java.net.URL;
049: import java.util.Stack;
050:
051: import javax.xml.parsers.SAXParser;
052: import javax.xml.parsers.SAXParserFactory;
053:
054: import org.jfree.util.Log;
055: import org.jfree.util.ObjectUtilities;
056: import org.jfree.xml.CommentHandler;
057: import org.jfree.xml.ElementDefinitionException;
058: import org.xml.sax.Attributes;
059: import org.xml.sax.InputSource;
060: import org.xml.sax.SAXException;
061: import org.xml.sax.XMLReader;
062: import org.xml.sax.helpers.DefaultHandler;
063:
064: /**
065: * Loads the class model from an previously written xml file set.
066: * This class provides abstract methods which get called during the parsing
067: * (similiar to the SAX parsing, but slightly easier to code).
068: *
069: * This will need a rewrite in the future, when the structure is finished.
070: */
071: public abstract class AbstractModelReader {
072:
073: /** The 'START' state. */
074: private static final int STATE_START = 0;
075:
076: /** The 'IN_OBJECT' state. */
077: private static final int IN_OBJECT = 1;
078:
079: /** The 'IGNORE_OBJECT' state. */
080: private static final int IGNORE_OBJECT = 2;
081:
082: /** The 'MAPPING' state. */
083: private static final int MAPPING_STATE = 3;
084:
085: /** The 'CONSTRUCTOR' state. */
086: private static final int CONSTRUCTOR_STATE = 4;
087:
088: /**
089: * The SAX2 callback implementation used for parsing the model xml files.
090: */
091: private class SAXModelHandler extends DefaultHandler {
092:
093: /** The resource URL. */
094: private URL resource;
095:
096: /** The current state. */
097: private int state;
098:
099: /** Open comments. */
100: private Stack openComments;
101:
102: /** Flag to track includes. */
103: private boolean isInclude;
104:
105: /**
106: * Creates a new SAX handler for parsing the model.
107: *
108: * @param resource the resource URL.
109: * @param isInclude an include?
110: */
111: public SAXModelHandler(final URL resource,
112: final boolean isInclude) {
113: if (resource == null) {
114: throw new NullPointerException();
115: }
116: this .resource = resource;
117: this .openComments = new Stack();
118: this .isInclude = isInclude;
119: }
120:
121: /**
122: * Receive notification of the start of an element.
123: *
124: * @param uri The Namespace URI, or the empty string if the
125: * element has no Namespace URI or if Namespace
126: * processing is not being performed.
127: * @param localName The local name (without prefix), or the
128: * empty string if Namespace processing is not being
129: * performed.
130: * @param qName The qualified name (with prefix), or the
131: * empty string if qualified names are not available.
132: * @param attributes The attributes attached to the element. If
133: * there are no attributes, it shall be an empty
134: * Attributes object.
135: * @exception SAXException Any SAX exception, possibly
136: * wrapping another exception.
137: *
138: * @see org.xml.sax.ContentHandler#startElement
139: */
140: public void startElement(final String uri,
141: final String localName, final String qName,
142: final Attributes attributes) throws SAXException {
143:
144: setOpenComment(getCommentHandler().getComments());
145: this .openComments.push(getOpenComment());
146: setCloseComment(null);
147:
148: try {
149:
150: if (!this .isInclude
151: && qName.equals(ClassModelTags.OBJECTS_TAG)) {
152: //Log.debug ("Open Comments: " + openComment);
153: startRootDocument();
154: return;
155: }
156:
157: if (getState() == STATE_START) {
158: startRootElement(qName, attributes);
159: } else if (getState() == IGNORE_OBJECT) {
160: return;
161: } else if (getState() == IN_OBJECT) {
162: startObjectElement(qName, attributes);
163: } else if (getState() == MAPPING_STATE) {
164: if (!qName.equals(ClassModelTags.TYPE_TAG)) {
165: throw new SAXException("Expected 'type' tag");
166: }
167: final String name = attributes
168: .getValue(ClassModelTags.NAME_ATTR);
169: final String target = attributes
170: .getValue(ClassModelTags.CLASS_ATTR);
171: handleMultiplexMapping(name, target);
172: } else if (getState() == CONSTRUCTOR_STATE) {
173: if (!qName.equals(ClassModelTags.PARAMETER_TAG)) {
174: throw new SAXException(
175: "Expected 'parameter' tag");
176: }
177: final String parameterClass = attributes
178: .getValue(ClassModelTags.CLASS_ATTR);
179: final String tagName = attributes
180: .getValue(ClassModelTags.PROPERTY_ATTR); // optional
181: handleConstructorDefinition(tagName, parameterClass);
182: }
183: } catch (ObjectDescriptionException e) {
184: throw new SAXException(e);
185: } finally {
186: getCommentHandler().clearComments();
187: }
188: }
189:
190: /**
191: * Receive notification of the end of an element.
192: *
193: * @param uri The Namespace URI, or the empty string if the
194: * element has no Namespace URI or if Namespace
195: * processing is not being performed.
196: * @param localName The local name (without prefix), or the
197: * empty string if Namespace processing is not being
198: * performed.
199: * @param qName The qualified name (with prefix), or the
200: * empty string if qualified names are not available.
201: * @exception SAXException Any SAX exception, possibly
202: * wrapping another exception.
203: * @see org.xml.sax.ContentHandler#endElement
204: */
205: public void endElement(final String uri,
206: final String localName, final String qName)
207: throws SAXException {
208:
209: setOpenComment((String[]) this .openComments.pop());
210: setCloseComment(getCommentHandler().getComments());
211:
212: try {
213: if (!this .isInclude
214: && qName.equals(ClassModelTags.OBJECTS_TAG)) {
215: endRootDocument();
216: return;
217: }
218:
219: if (qName.equals(ClassModelTags.OBJECT_TAG)) {
220: if (getState() != IGNORE_OBJECT) {
221: endObjectDefinition();
222: }
223: setState(STATE_START);
224: } else if (qName.equals(ClassModelTags.MAPPING_TAG)) {
225: setState(STATE_START);
226: endMultiplexMapping();
227: } else if (qName.equals(ClassModelTags.CONSTRUCTOR_TAG)) {
228: if (getState() != IGNORE_OBJECT) {
229: setState(IN_OBJECT);
230: }
231: }
232: } catch (ObjectDescriptionException e) {
233: throw new SAXException(e);
234: } finally {
235: getCommentHandler().clearComments();
236: }
237: }
238:
239: /**
240: * Handles the start of an element within an object definition.
241: *
242: * @param qName The qualified name (with prefix), or the
243: * empty string if qualified names are not available.
244: * @param attributes The attributes attached to the element. If
245: * there are no attributes, it shall be an empty
246: * Attributes object.
247: * @throws ObjectDescriptionException if an error occured while
248: * handling this tag
249: */
250: private void startObjectElement(final String qName,
251: final Attributes attributes)
252: throws ObjectDescriptionException {
253: if (qName.equals(ClassModelTags.CONSTRUCTOR_TAG)) {
254: setState(CONSTRUCTOR_STATE);
255: } else if (qName.equals(ClassModelTags.LOOKUP_PROPERTY_TAG)) {
256: final String name = attributes
257: .getValue(ClassModelTags.NAME_ATTR);
258: final String lookupKey = attributes
259: .getValue(ClassModelTags.LOOKUP_ATTR);
260: handleLookupDefinition(name, lookupKey);
261: } else if (qName
262: .equals(ClassModelTags.IGNORED_PROPERTY_TAG)) {
263: final String name = attributes
264: .getValue(ClassModelTags.NAME_ATTR);
265: handleIgnoredProperty(name);
266: } else if (qName
267: .equals(ClassModelTags.ELEMENT_PROPERTY_TAG)) {
268: final String elementAtt = attributes
269: .getValue(ClassModelTags.ELEMENT_ATTR);
270: final String name = attributes
271: .getValue(ClassModelTags.NAME_ATTR);
272: handleElementDefinition(name, elementAtt);
273: } else if (qName
274: .equals(ClassModelTags.ATTRIBUTE_PROPERTY_TAG)) {
275: final String name = attributes
276: .getValue(ClassModelTags.NAME_ATTR);
277: final String attribName = attributes
278: .getValue(ClassModelTags.ATTRIBUTE_ATTR);
279: final String handler = attributes
280: .getValue(ClassModelTags.ATTRIBUTE_HANDLER_ATTR);
281: handleAttributeDefinition(name, attribName, handler);
282: }
283: }
284:
285: /**
286: * Handles the include or object tag.
287: *
288: * @param qName The qualified name (with prefix), or the
289: * empty string if qualified names are not available.
290: * @param attributes The attributes attached to the element. If
291: * there are no attributes, it shall be an empty
292: * Attributes object.
293: * @throws SAXException if an parser error occured
294: * @throws ObjectDescriptionException if an object model related
295: * error occured.
296: */
297: private void startRootElement(final String qName,
298: final Attributes attributes) throws SAXException,
299: ObjectDescriptionException {
300:
301: if (qName.equals(ClassModelTags.INCLUDE_TAG)) {
302: if (this .isInclude) {
303: Log.warn("Ignored nested include tag.");
304: return;
305: }
306: final String src = attributes
307: .getValue(ClassModelTags.SOURCE_ATTR);
308: try {
309: final URL url = new URL(this .resource, src);
310: startIncludeHandling(url);
311: parseXmlDocument(url, true);
312: endIncludeHandling();
313: } catch (Exception ioe) {
314: throw new ElementDefinitionException(ioe,
315: "Unable to include file from " + src);
316: }
317: } else if (qName.equals(ClassModelTags.OBJECT_TAG)) {
318: setState(IN_OBJECT);
319: final String className = attributes
320: .getValue(ClassModelTags.CLASS_ATTR);
321: String register = attributes
322: .getValue(ClassModelTags.REGISTER_NAMES_ATTR);
323: if (register != null && register.length() == 0) {
324: register = null;
325: }
326: final boolean ignored = "true".equals(attributes
327: .getValue(ClassModelTags.IGNORE_ATTR));
328: if (!startObjectDefinition(className, register, ignored)) {
329: setState(IGNORE_OBJECT);
330: }
331: } else if (qName.equals(ClassModelTags.MANUAL_TAG)) {
332: final String className = attributes
333: .getValue(ClassModelTags.CLASS_ATTR);
334: final String readHandler = attributes
335: .getValue(ClassModelTags.READ_HANDLER_ATTR);
336: final String writeHandler = attributes
337: .getValue(ClassModelTags.WRITE_HANDLER_ATTR);
338: handleManualMapping(className, readHandler,
339: writeHandler);
340: } else if (qName.equals(ClassModelTags.MAPPING_TAG)) {
341: setState(MAPPING_STATE);
342: final String typeAttr = attributes
343: .getValue(ClassModelTags.TYPE_ATTR);
344: final String baseClass = attributes
345: .getValue(ClassModelTags.BASE_CLASS_ATTR);
346: startMultiplexMapping(baseClass, typeAttr);
347: }
348: }
349:
350: /**
351: * Returns the current state.
352: *
353: * @return the state.
354: */
355: private int getState() {
356: return this .state;
357: }
358:
359: /**
360: * Sets the current state.
361: *
362: * @param state the state.
363: */
364: private void setState(final int state) {
365: this .state = state;
366: }
367: }
368:
369: /** The comment handler. */
370: private CommentHandler commentHandler;
371:
372: /** The close comments. */
373: private String[] closeComment;
374:
375: /** The open comments. */
376: private String[] openComment;
377:
378: /**
379: * Default Constructor.
380: */
381: public AbstractModelReader() {
382: this .commentHandler = new CommentHandler();
383: }
384:
385: /**
386: * Returns the comment handler.
387: *
388: * @return The comment handler.
389: */
390: protected CommentHandler getCommentHandler() {
391: return this .commentHandler;
392: }
393:
394: /**
395: * Returns the close comment.
396: *
397: * @return The close comment.
398: */
399: protected String[] getCloseComment() {
400: return this .closeComment;
401: }
402:
403: /**
404: * Returns the open comment.
405: *
406: * @return The open comment.
407: */
408: protected String[] getOpenComment() {
409: return this .openComment;
410: }
411:
412: /**
413: * Sets the close comment.
414: *
415: * @param closeComment the close comment.
416: */
417: protected void setCloseComment(final String[] closeComment) {
418: this .closeComment = closeComment;
419: }
420:
421: /**
422: * Sets the open comment.
423: *
424: * @param openComment the open comment.
425: */
426: protected void setOpenComment(final String[] openComment) {
427: this .openComment = openComment;
428: }
429:
430: /**
431: * Parses an XML document at the given URL.
432: *
433: * @param resource the document URL.
434: *
435: * @throws ObjectDescriptionException ??
436: */
437: protected void parseXml(final URL resource)
438: throws ObjectDescriptionException {
439: parseXmlDocument(resource, false);
440: }
441:
442: /**
443: * Parses the given specification and loads all includes specified in the files.
444: * This implementation does not check for loops in the include files.
445: *
446: * @param resource the url of the xml specification.
447: * @param isInclude an include?
448: *
449: * @throws org.jfree.xml.util.ObjectDescriptionException if an error occured which prevented the
450: * loading of the specifications.
451: */
452: protected void parseXmlDocument(final URL resource,
453: final boolean isInclude) throws ObjectDescriptionException {
454:
455: try {
456: final InputStream in = new BufferedInputStream(resource
457: .openStream());
458: final SAXParserFactory factory = SAXParserFactory
459: .newInstance();
460: final SAXParser saxParser = factory.newSAXParser();
461: final XMLReader reader = saxParser.getXMLReader();
462:
463: final SAXModelHandler handler = new SAXModelHandler(
464: resource, isInclude);
465: try {
466: reader
467: .setProperty(
468: "http://xml.org/sax/properties/lexical-handler",
469: getCommentHandler());
470: } catch (SAXException se) {
471: Log
472: .debug("Comments are not supported by this SAX implementation.");
473: }
474: reader.setContentHandler(handler);
475: reader.setDTDHandler(handler);
476: reader.setErrorHandler(handler);
477: reader.parse(new InputSource(in));
478: in.close();
479: } catch (Exception e) {
480: // unable to init
481: Log.warn("Unable to load factory specifications", e);
482: throw new ObjectDescriptionException(
483: "Unable to load object factory specs.", e);
484: }
485: }
486:
487: /**
488: * Start the root document.
489: */
490: protected void startRootDocument() {
491: // nothing required
492: }
493:
494: /**
495: * End the root document.
496: */
497: protected void endRootDocument() {
498: // nothing required
499: }
500:
501: /**
502: * Start handling an include.
503: *
504: * @param resource the URL.
505: */
506: protected void startIncludeHandling(final URL resource) {
507: // nothing required
508: }
509:
510: /**
511: * End handling an include.
512: */
513: protected void endIncludeHandling() {
514: // nothing required
515: }
516:
517: /**
518: * Callback method for ignored properties. Such properties get marked so that
519: * the information regarding these properties won't get lost.
520: *
521: * @param name the name of the ignored property.
522: */
523: protected void handleIgnoredProperty(final String name) {
524: // nothing required
525: }
526:
527: /**
528: * Handles a manual mapping definition. The manual mapping maps specific
529: * read and write handlers to a given base class. Manual mappings always
530: * override any other definition.
531: *
532: * @param className the base class name
533: * @param readHandler the class name of the read handler
534: * @param writeHandler the class name of the write handler
535: * @return true, if the mapping was accepted, false otherwise.
536: * @throws ObjectDescriptionException if an unexpected error occured.
537: */
538: protected abstract boolean handleManualMapping(String className,
539: String readHandler, String writeHandler)
540: throws ObjectDescriptionException;
541:
542: /**
543: * Starts a object definition. The object definition collects all properties of
544: * an bean-class and defines, which constructor should be used when creating the
545: * class.
546: *
547: * @param className the class name of the defined object
548: * @param register the (optional) register name, to lookup and reference the object
549: * later.
550: * @param ignored ??.
551: *
552: * @return true, if the definition was accepted, false otherwise.
553: * @throws ObjectDescriptionException if an unexpected error occured.
554: */
555: protected abstract boolean startObjectDefinition(String className,
556: String register, boolean ignored)
557: throws ObjectDescriptionException;
558:
559: /**
560: * Handles an attribute definition. This method gets called after the object definition
561: * was started. The method will be called for every defined attribute property.
562: *
563: * @param name the name of the property
564: * @param attribName the xml-attribute name to use later.
565: * @param handlerClass the attribute handler class.
566: * @throws ObjectDescriptionException if an error occured.
567: */
568: protected abstract void handleAttributeDefinition(String name,
569: String attribName, String handlerClass)
570: throws ObjectDescriptionException;
571:
572: /**
573: * Handles an element definition. This method gets called after the object definition
574: * was started. The method will be called for every defined element property. Element
575: * properties are used to describe complex objects.
576: *
577: * @param name the name of the property
578: * @param element the xml-tag name for the child element.
579: * @throws ObjectDescriptionException if an error occurs.
580: */
581: protected abstract void handleElementDefinition(String name,
582: String element) throws ObjectDescriptionException;
583:
584: /**
585: * Handles an lookup definition. This method gets called after the object definition
586: * was started. The method will be called for every defined lookup property. Lookup properties
587: * reference previously created object using the object's registry name.
588: *
589: * @param name the property name of the base object
590: * @param lookupKey the register key of the referenced object
591: * @throws ObjectDescriptionException if an error occured.
592: */
593: protected abstract void handleLookupDefinition(String name,
594: String lookupKey) throws ObjectDescriptionException;
595:
596: /**
597: * Finializes the object definition.
598: *
599: * @throws ObjectDescriptionException if an error occures.
600: */
601: protected abstract void endObjectDefinition()
602: throws ObjectDescriptionException;
603:
604: /**
605: * Starts a multiplex mapping. Multiplex mappings are used to define polymorphic
606: * argument handlers. The mapper will collect all derived classes of the given
607: * base class and will select the corresponding mapping based on the given type
608: * attribute.
609: *
610: * @param className the base class name
611: * @param typeAttr the xml-attribute name containing the mapping key
612: */
613: protected abstract void startMultiplexMapping(String className,
614: String typeAttr);
615:
616: /**
617: * Defines an entry for the multiplex mapping. The new entry will be activated
618: * when the base mappers type attribute contains this <code>typename</code> and
619: * will resolve to the handler for the given classname.
620: *
621: * @param typeName the type value for this mapping.
622: * @param className the class name to which this mapping resolves.
623: * @throws ObjectDescriptionException if an error occurs.
624: */
625: protected abstract void handleMultiplexMapping(String typeName,
626: String className) throws ObjectDescriptionException;
627:
628: /**
629: * Finializes the multiplexer mapping.
630: *
631: * @throws ObjectDescriptionException if an error occurs.
632: */
633: protected abstract void endMultiplexMapping()
634: throws ObjectDescriptionException;
635:
636: /**
637: * Handles a constructor definition. Only one constructor can be defined for
638: * a certain object type. The constructor will be filled using the given properties.
639: *
640: * @param propertyName the property name of the referenced local property
641: * @param parameterClass the parameter class for the parameter.
642: * @throws ObjectDescriptionException if an error occured.
643: */
644: protected abstract void handleConstructorDefinition(
645: String propertyName, String parameterClass)
646: throws ObjectDescriptionException;
647:
648: /**
649: * Loads the given class, and ignores all exceptions which may occur
650: * during the loading. If the class was invalid, null is returned instead.
651: *
652: * @param className the name of the class to be loaded.
653: * @return the class or null.
654: */
655: protected Class loadClass(final String className) {
656: if (className == null) {
657: return null;
658: }
659: if (className.startsWith("::")) {
660: return BasicTypeSupport.getClassRepresentation(className);
661: }
662: try {
663: return ObjectUtilities.getClassLoader(getClass())
664: .loadClass(className);
665: } catch (Exception e) {
666: // ignore buggy classes for now ..
667: Log.warn("Unable to load class", e);
668: return null;
669: }
670: }
671:
672: }
|