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}