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