    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}