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