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 * RootXmlReadHandler.java 029 * ----------------------- 030 * (C)opyright 2003, 2004, by Thomas Morgner and Contributors. 031 * 032 * Original Author: Thomas Morgner; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * $Id: RootXmlReadHandler.java,v 1.9 2008/09/10 09:20:16 mungady Exp $ 036 * 037 * Changes (from 25-Nov-2003) 038 * -------------------------- 039 * 25-Nov-2003 : Added Javadocs (DG); 040 * 22-Feb-2005 : Fixed a bug when ending nested tags with the same tagname. 041 */ 042package org.jfree.xml.parser; 043 044import java.awt.BasicStroke; 045import java.awt.Color; 046import java.awt.Font; 047import java.awt.GradientPaint; 048import java.awt.Insets; 049import java.awt.Paint; 050import java.awt.RenderingHints; 051import java.awt.Stroke; 052import java.awt.geom.Point2D; 053import java.awt.geom.Rectangle2D; 054import java.util.ArrayList; 055import java.util.HashMap; 056import java.util.LinkedList; 057import java.util.List; 058import java.util.Stack; 059import java.util.Vector; 060 061import org.jfree.util.ObjectUtilities; 062import org.jfree.xml.FrontendDefaultHandler; 063import org.jfree.xml.ParseException; 064import org.jfree.xml.ElementDefinitionException; 065import org.jfree.xml.parser.coretypes.BasicStrokeReadHandler; 066import org.jfree.xml.parser.coretypes.ColorReadHandler; 067import org.jfree.xml.parser.coretypes.FontReadHandler; 068import org.jfree.xml.parser.coretypes.GenericReadHandler; 069import org.jfree.xml.parser.coretypes.GradientPaintReadHandler; 070import org.jfree.xml.parser.coretypes.InsetsReadHandler; 071import org.jfree.xml.parser.coretypes.ListReadHandler; 072import org.jfree.xml.parser.coretypes.Point2DReadHandler; 073import org.jfree.xml.parser.coretypes.Rectangle2DReadHandler; 074import org.jfree.xml.parser.coretypes.RenderingHintsReadHandler; 075import org.jfree.xml.parser.coretypes.StringReadHandler; 076import org.jfree.xml.util.ManualMappingDefinition; 077import org.jfree.xml.util.MultiplexMappingDefinition; 078import org.jfree.xml.util.MultiplexMappingEntry; 079import org.jfree.xml.util.ObjectFactory; 080import org.jfree.xml.util.SimpleObjectFactory; 081import org.xml.sax.Attributes; 082import org.xml.sax.SAXException; 083 084/** 085 * A base root SAX handler. 086 */ 087public abstract class RootXmlReadHandler extends FrontendDefaultHandler { 088 089 /** The current handlers. */ 090 private Stack currentHandlers; 091 092 /** ??. */ 093 private Stack outerScopes; 094 095 /** The root handler. */ 096 private XmlReadHandler rootHandler; 097 098 /** The object registry. */ 099 private HashMap objectRegistry; 100 101 /** Maps classes to handlers. */ 102 private SimpleObjectFactory classToHandlerMapping; 103 104 private boolean rootHandlerInitialized; 105 106 /** 107 * Creates a new root SAX handler. 108 */ 109 public RootXmlReadHandler() { 110 this.objectRegistry = new HashMap(); 111 this.classToHandlerMapping = new SimpleObjectFactory(); 112 } 113 114 /** 115 * Adds the default mappings. 116 */ 117 protected void addDefaultMappings () { 118 119 final MultiplexMappingEntry[] paintEntries = new MultiplexMappingEntry[2]; 120 paintEntries[0] = new MultiplexMappingEntry("color", Color.class.getName()); 121 paintEntries[1] = new MultiplexMappingEntry("gradientPaint", GradientPaint.class.getName()); 122 addMultiplexMapping(Paint.class, "type", paintEntries); 123 addManualMapping(Color.class, ColorReadHandler.class); 124 addManualMapping(GradientPaint.class, GradientPaintReadHandler.class); 125 126 final MultiplexMappingEntry[] point2DEntries = new MultiplexMappingEntry[2]; 127 point2DEntries[0] = new MultiplexMappingEntry("float", Point2D.Float.class.getName()); 128 point2DEntries[1] = new MultiplexMappingEntry("double", Point2D.Double.class.getName()); 129 addMultiplexMapping(Point2D.class, "type", point2DEntries); 130 addManualMapping(Point2D.Float.class, Point2DReadHandler.class); 131 addManualMapping(Point2D.Double.class, Point2DReadHandler.class); 132 133 final MultiplexMappingEntry[] rectangle2DEntries = new MultiplexMappingEntry[2]; 134 rectangle2DEntries[0] = new MultiplexMappingEntry( 135 "float", Rectangle2D.Float.class.getName() 136 ); 137 rectangle2DEntries[1] = new MultiplexMappingEntry( 138 "double", Rectangle2D.Double.class.getName() 139 ); 140 addMultiplexMapping(Rectangle2D.class, "type", rectangle2DEntries); 141 addManualMapping(Rectangle2D.Float.class, Rectangle2DReadHandler.class); 142 addManualMapping(Rectangle2D.Double.class, Rectangle2DReadHandler.class); 143 144 // Handle list types 145 final MultiplexMappingEntry[] listEntries = new MultiplexMappingEntry[4]; 146 listEntries[0] = new MultiplexMappingEntry("array-list", ArrayList.class.getName()); 147 listEntries[1] = new MultiplexMappingEntry("linked-list", LinkedList.class.getName()); 148 listEntries[2] = new MultiplexMappingEntry("vector", Vector.class.getName()); 149 listEntries[3] = new MultiplexMappingEntry("stack", Stack.class.getName()); 150 addMultiplexMapping(List.class, "type", listEntries); 151 addManualMapping(LinkedList.class, ListReadHandler.class); 152 addManualMapping(Vector.class, ListReadHandler.class); 153 addManualMapping(ArrayList.class, ListReadHandler.class); 154 addManualMapping(Stack.class, ListReadHandler.class); 155 156 final MultiplexMappingEntry[] strokeEntries = new MultiplexMappingEntry[1]; 157 strokeEntries[0] = new MultiplexMappingEntry("basic", BasicStroke.class.getName()); 158 addMultiplexMapping(Stroke.class, "type", strokeEntries); 159 addManualMapping(BasicStroke.class, BasicStrokeReadHandler.class); 160 161 addManualMapping(Font.class, FontReadHandler.class); 162 addManualMapping(Insets.class, InsetsReadHandler.class); 163 addManualMapping(RenderingHints.class, RenderingHintsReadHandler.class); 164 addManualMapping(String.class, StringReadHandler.class); 165 } 166 167 /** 168 * Returns the object factory. 169 * 170 * @return The object factory. 171 */ 172 public abstract ObjectFactory getFactoryLoader(); 173 174 /** 175 * Adds a mapping between a class and the handler for the class. 176 * 177 * @param classToRead the class. 178 * @param handler the handler class. 179 */ 180 protected void addManualMapping(final Class classToRead, final Class handler) { 181 if (handler == null) { 182 throw new NullPointerException("handler must not be null."); 183 } 184 if (classToRead == null) { 185 throw new NullPointerException("classToRead must not be null."); 186 } 187 if (!XmlReadHandler.class.isAssignableFrom(handler)) { 188 throw new IllegalArgumentException("The given handler is no XmlReadHandler."); 189 } 190 this.classToHandlerMapping.addManualMapping 191 (new ManualMappingDefinition(classToRead, handler.getName(), null)); 192 } 193 194 /** 195 * Adds a multiplex mapping. 196 * 197 * @param baseClass the base class. 198 * @param typeAttr the type attribute. 199 * @param mdef the mapping entry. 200 */ 201 protected void addMultiplexMapping(final Class baseClass, 202 final String typeAttr, 203 final MultiplexMappingEntry[] mdef) { 204 205 this.classToHandlerMapping.addMultiplexMapping( 206 new MultiplexMappingDefinition(baseClass, typeAttr, mdef) 207 ); 208 } 209 210 /** 211 * Adds an object to the registry. 212 * 213 * @param key the key. 214 * @param value the object. 215 */ 216 public void setHelperObject(final String key, final Object value) { 217 if (value == null) { 218 this.objectRegistry.remove(key); 219 } 220 else { 221 this.objectRegistry.put(key, value); 222 } 223 } 224 225 /** 226 * Returns an object from the registry. 227 * 228 * @param key the key. 229 * 230 * @return The object. 231 */ 232 public Object getHelperObject(final String key) { 233 return this.objectRegistry.get(key); 234 } 235 236 /** 237 * Creates a SAX handler for the specified class. 238 * 239 * @param classToRead the class. 240 * @param tagName the tag name. 241 * @param atts the attributes. 242 * 243 * @return a SAX handler. 244 * 245 * @throws XmlReaderException if there is a problem with the reader. 246 */ 247 public XmlReadHandler createHandler(final Class classToRead, final String tagName, final Attributes atts) 248 throws XmlReaderException { 249 250 final XmlReadHandler retval = findHandlerForClass(classToRead, atts, new ArrayList()); 251 if (retval == null) { 252 throw new NullPointerException("Unable to find handler for class: " + classToRead); 253 } 254 retval.init(this, tagName); 255 return retval; 256 } 257 258 /** 259 * Finds a handler for the specified class. 260 * 261 * @param classToRead the class to be read. 262 * @param atts the attributes. 263 * @param history the history. 264 * 265 * @return A handler for the specified class. 266 * 267 * @throws XmlReaderException if there is a problem with the reader. 268 */ 269 private XmlReadHandler findHandlerForClass(final Class classToRead, final Attributes atts, 270 final ArrayList history) 271 throws XmlReaderException { 272 final ObjectFactory genericFactory = getFactoryLoader(); 273 274 if (history.contains(classToRead)) { 275 throw new IllegalStateException("Circular reference detected: " + history); 276 } 277 history.add(classToRead); 278 // check the manual mappings ... 279 ManualMappingDefinition manualDefinition = 280 this.classToHandlerMapping.getManualMappingDefinition(classToRead); 281 if (manualDefinition == null) { 282 manualDefinition = genericFactory.getManualMappingDefinition(classToRead); 283 } 284 if (manualDefinition != null) { 285 // Log.debug ("Locating handler for " + manualDefinition.getBaseClass()); 286 return loadHandlerClass(manualDefinition.getReadHandler()); 287 } 288 289 // check whether a multiplexer is defined ... 290 // find multiplexer for this class... 291 MultiplexMappingDefinition mplex = 292 getFactoryLoader().getMultiplexDefinition(classToRead); 293 if (mplex == null) { 294 mplex = this.classToHandlerMapping.getMultiplexDefinition(classToRead); 295 } 296 if (mplex != null) { 297 final String attributeValue = atts.getValue(mplex.getAttributeName()); 298 if (attributeValue == null) { 299 throw new XmlReaderException( 300 "Multiplexer type attribute is not defined: " + mplex.getAttributeName() 301 + " for " + classToRead 302 ); 303 } 304 final MultiplexMappingEntry entry = 305 mplex.getEntryForType(attributeValue); 306 if (entry == null) { 307 throw new XmlReaderException( 308 "Invalid type attribute value: " + mplex.getAttributeName() + " = " 309 + attributeValue 310 ); 311 } 312 final Class c = loadClass(entry.getTargetClass()); 313 if (!c.equals(mplex.getBaseClass())) { 314 return findHandlerForClass(c, atts, history); 315 } 316 } 317 318 // check for generic classes ... 319 // and finally try the generic handler matches ... 320 if (this.classToHandlerMapping.isGenericHandler(classToRead)) { 321 return new GenericReadHandler 322 (this.classToHandlerMapping.getFactoryForClass(classToRead)); 323 } 324 if (getFactoryLoader().isGenericHandler(classToRead)) { 325 return new GenericReadHandler 326 (getFactoryLoader().getFactoryForClass(classToRead)); 327 } 328 return null; 329 } 330 331 /** 332 * Sets the root SAX handler. 333 * 334 * @param handler the SAX handler. 335 */ 336 protected void setRootHandler(final XmlReadHandler handler) { 337 this.rootHandler = handler; 338 this.rootHandlerInitialized = false; 339 } 340 341 /** 342 * Returns the root SAX handler. 343 * 344 * @return the root SAX handler. 345 */ 346 protected XmlReadHandler getRootHandler() { 347 return this.rootHandler; 348 } 349 350 /** 351 * Start a new handler stack and delegate to another handler. 352 * 353 * @param handler the handler. 354 * @param tagName the tag name. 355 * @param attrs the attributes. 356 * 357 * @throws XmlReaderException if there is a problem with the reader. 358 * @throws SAXException if there is a problem with the parser. 359 */ 360 public void recurse(final XmlReadHandler handler, final String tagName, final Attributes attrs) 361 throws XmlReaderException, SAXException { 362 363 this.outerScopes.push(this.currentHandlers); 364 this.currentHandlers = new Stack(); 365 this.currentHandlers.push(handler); 366 handler.startElement(tagName, attrs); 367 368 } 369 370 /** 371 * Delegate to another handler. 372 * 373 * @param handler the new handler. 374 * @param tagName the tag name. 375 * @param attrs the attributes. 376 * 377 * @throws XmlReaderException if there is a problem with the reader. 378 * @throws SAXException if there is a problem with the parser. 379 */ 380 public void delegate(final XmlReadHandler handler, final String tagName, final Attributes attrs) 381 throws XmlReaderException, SAXException { 382 this.currentHandlers.push(handler); 383 handler.init(this, tagName); 384 handler.startElement(tagName, attrs); 385 } 386 387 /** 388 * Hand control back to the previous handler. 389 * 390 * @param tagName the tagname. 391 * 392 * @throws SAXException if there is a problem with the parser. 393 * @throws XmlReaderException if there is a problem with the reader. 394 */ 395 public void unwind(final String tagName) throws SAXException, XmlReaderException { 396 // remove current handler from stack .. 397 this.currentHandlers.pop(); 398 if (this.currentHandlers.isEmpty() && !this.outerScopes.isEmpty()) { 399 // if empty, but "recurse" had been called, then restore the old handler stack .. 400 // but do not end the recursed element .. 401 this.currentHandlers = (Stack) this.outerScopes.pop(); 402 } 403 else if (!this.currentHandlers.isEmpty()) { 404 // if there are some handlers open, close them too (these handlers must be delegates).. 405 getCurrentHandler().endElement(tagName); 406 } 407 } 408 409 /** 410 * Returns the current handler. 411 * 412 * @return The current handler. 413 */ 414 protected XmlReadHandler getCurrentHandler() { 415 return (XmlReadHandler) this.currentHandlers.peek(); 416 } 417 418 /** 419 * Starts processing a document. 420 * 421 * @throws SAXException not in this implementation. 422 */ 423 public void startDocument() throws SAXException { 424 this.outerScopes = new Stack(); 425 this.currentHandlers = new Stack(); 426 this.currentHandlers.push(this.rootHandler); 427 } 428 429 /** 430 * Starts processing an element. 431 * 432 * @param uri the URI. 433 * @param localName the local name. 434 * @param qName the qName. 435 * @param attributes the attributes. 436 * 437 * @throws SAXException if there is a parsing problem. 438 */ 439 public void startElement(final String uri, final String localName, 440 final String qName, final Attributes attributes) 441 throws SAXException { 442 if (this.rootHandlerInitialized == false) { 443 this.rootHandler.init(this, qName); 444 this.rootHandlerInitialized = true; 445 } 446 447 try { 448 getCurrentHandler().startElement(qName, attributes); 449 } 450 catch (XmlReaderException xre) { 451 throw new ParseException(xre, getLocator()); 452 } 453 } 454 455 /** 456 * Process character data. 457 * 458 * @param ch the character buffer. 459 * @param start the start index. 460 * @param length the length of the character data. 461 * 462 * @throws SAXException if there is a parsing error. 463 */ 464 public void characters(final char[] ch, final int start, final int length) throws SAXException { 465 try { 466 getCurrentHandler().characters(ch, start, length); 467 } 468 catch (SAXException se) { 469 throw se; 470 } 471 catch (Exception e) { 472 throw new ParseException(e, getLocator()); 473 } 474 } 475 476 /** 477 * Finish processing an element. 478 * 479 * @param uri the URI. 480 * @param localName the local name. 481 * @param qName the qName. 482 * 483 * @throws SAXException if there is a parsing error. 484 */ 485 public void endElement(final String uri, final String localName, final String qName) 486 throws SAXException { 487 try { 488 getCurrentHandler().endElement(qName); 489 } 490 catch (XmlReaderException xre) { 491 throw new ParseException(xre, getLocator()); 492 } 493 } 494 495 /** 496 * Loads the given class, and ignores all exceptions which may occur 497 * during the loading. If the class was invalid, null is returned instead. 498 * 499 * @param className the name of the class to be loaded. 500 * @return the class or null. 501 * @throws XmlReaderException if there is a reader error. 502 */ 503 protected XmlReadHandler loadHandlerClass(final String className) 504 throws XmlReaderException { 505 try { 506 final Class c = loadClass(className); 507 return (XmlReadHandler) c.newInstance(); 508 } 509 catch (Exception e) { 510 // ignore buggy classes for now .. 511 throw new XmlReaderException("LoadHanderClass: Unable to instantiate " + className, e); 512 } 513 } 514 515 /** 516 * Loads the given class, and ignores all exceptions which may occur 517 * during the loading. If the class was invalid, null is returned instead. 518 * 519 * @param className the name of the class to be loaded. 520 * @return the class or null. 521 * @throws XmlReaderException if there is a reader error. 522 */ 523 protected Class loadClass(final String className) 524 throws XmlReaderException { 525 if (className == null) { 526 throw new XmlReaderException("LoadHanderClass: Class name not defined"); 527 } 528 try { 529 final Class c = ObjectUtilities.getClassLoader(getClass()).loadClass(className); 530 return c; 531 } 532 catch (Exception e) { 533 // ignore buggy classes for now .. 534 throw new XmlReaderException("LoadHanderClass: Unable to load " + className, e); 535 } 536 } 537 538 /** 539 * Returns ???. 540 * 541 * @return ???. 542 * 543 * @throws SAXException ???. 544 */ 545 public Object getResult () throws SAXException 546 { 547 if (this.rootHandler != null) { 548 try 549 { 550 return this.rootHandler.getObject(); 551 } 552 catch (XmlReaderException e) 553 { 554 throw new ElementDefinitionException(e); 555 } 556 } 557 return null; 558 } 559}