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 */
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 /**
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 }