    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    }