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 }