    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    }