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, 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 }