    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     * AbstractModelReader.java
    029     * ------------------------
    030     * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
    031     *
    032     * Original Author:  Thomas Morgner;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * $Id: AbstractModelReader.java,v 1.8 2005/10/18 13:33:53 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 12-Nov-2003 : Initial version
    040     * 25-Nov-2003 : Updated header (DG);
    041     *
    042     */
    043
    044    package org.jfree.xml.util;
    045
    046    import java.io.BufferedInputStream;
    047    import java.io.InputStream;
    048    import java.net.URL;
    049    import java.util.Stack;
    050
    051    import javax.xml.parsers.SAXParser;
    052    import javax.xml.parsers.SAXParserFactory;
    053
    054    import org.jfree.util.Log;
    055    import org.jfree.util.ObjectUtilities;
    056    import org.jfree.xml.CommentHandler;
    057    import org.jfree.xml.ElementDefinitionException;
    058    import org.xml.sax.Attributes;
    059    import org.xml.sax.InputSource;
    060    import org.xml.sax.SAXException;
    061    import org.xml.sax.XMLReader;
    062    import org.xml.sax.helpers.DefaultHandler;
    063
    064    /**
    065     * Loads the class model from an previously written xml file set.
    066     * This class provides abstract methods which get called during the parsing
    067     * (similiar to the SAX parsing, but slightly easier to code).
    068     *
    069     * This will need a rewrite in the future, when the structure is finished.
    070     */
    071    public abstract class AbstractModelReader {
    072
    073        /** The 'START' state. */
    074        private static final int STATE_START = 0;
    075
    076        /** The 'IN_OBJECT' state. */
    077        private static final int IN_OBJECT = 1;
    078
    079        /** The 'IGNORE_OBJECT' state. */
    080        private static final int IGNORE_OBJECT = 2;
    081
    082        /** The 'MAPPING' state. */
    083        private static final int MAPPING_STATE = 3;
    084
    085        /** The 'CONSTRUCTOR' state. */
    086        private static final int CONSTRUCTOR_STATE = 4;
    087
    088        /**
    089         * The SAX2 callback implementation used for parsing the model xml files.
    090         */
    091        private class SAXModelHandler extends DefaultHandler {
    092
    093            /** The resource URL. */
    094            private URL resource;
    095
    096            /** The current state. */
    097            private int state;
    098
    099            /** Open comments. */
    100            private Stack openComments;
    101
    102            /** Flag to track includes. */
    103            private boolean isInclude;
    104
    105            /**
    106             * Creates a new SAX handler for parsing the model.
    107             *
    108             * @param resource  the resource URL.
    109             * @param isInclude  an include?
    110             */
    111            public SAXModelHandler(final URL resource, final boolean isInclude) {
    112                if (resource == null) {
    113                    throw new NullPointerException();
    114                }
    115                this.resource = resource;
    116                this.openComments = new Stack();
    117                this.isInclude = isInclude;
    118            }
    119
    120            /**
    121             * Receive notification of the start of an element.
    122             *
    123             * @param uri The Namespace URI, or the empty string if the
    124             *        element has no Namespace URI or if Namespace
    125             *        processing is not being performed.
    126             * @param localName The local name (without prefix), or the
    127             *        empty string if Namespace processing is not being
    128             *        performed.
    129             * @param qName The qualified name (with prefix), or the
    130             *        empty string if qualified names are not available.
    131             * @param attributes The attributes attached to the element.  If
    132             *        there are no attributes, it shall be an empty
    133             *        Attributes object.
    134             * @exception SAXException Any SAX exception, possibly
    135             *            wrapping another exception.
    136             *
    137             * @see org.xml.sax.ContentHandler#startElement
    138             */
    139            public void startElement(final String uri, final String localName,
    140                                     final String qName, final Attributes attributes)
    141                throws SAXException {
    142
    143                setOpenComment(getCommentHandler().getComments());
    144                this.openComments.push(getOpenComment());
    145                setCloseComment(null);
    146
    147                try {
    148
    149                    if (!this.isInclude && qName.equals(ClassModelTags.OBJECTS_TAG)) {
    150                        //Log.debug ("Open Comments: " + openComment);
    151                        startRootDocument();
    152                        return;
    153                    }
    154
    155                    if (getState() == STATE_START) {
    156                        startRootElement(qName, attributes);
    157                    }
    158                    else if (getState() == IGNORE_OBJECT) {
    159                        return;
    160                    }
    161                    else if (getState() == IN_OBJECT) {
    162                        startObjectElement(qName, attributes);
    163                    }
    164                    else if (getState() == MAPPING_STATE) {
    165                        if (!qName.equals(ClassModelTags.TYPE_TAG)) {
    166                            throw new SAXException("Expected 'type' tag");
    167                        }
    168                        final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
    169                        final String target = attributes.getValue(ClassModelTags.CLASS_ATTR);
    170                        handleMultiplexMapping(name, target);
    171                    }
    172                    else if (getState() == CONSTRUCTOR_STATE) {
    173                        if (!qName.equals(ClassModelTags.PARAMETER_TAG)) {
    174                            throw new SAXException("Expected 'parameter' tag");
    175                        }
    176                        final String parameterClass = attributes.getValue(ClassModelTags.CLASS_ATTR);
    177                        final String tagName = attributes.getValue(ClassModelTags.PROPERTY_ATTR); // optional
    178                        handleConstructorDefinition(tagName, parameterClass);
    179                    }
    180                }
    181                catch (ObjectDescriptionException e) {
    182                    throw new SAXException(e);
    183                }
    184                finally {
    185                    getCommentHandler().clearComments();
    186                }
    187            }
    188
    189            /**
    190             * Receive notification of the end of an element.
    191             *
    192             * @param uri The Namespace URI, or the empty string if the
    193             *        element has no Namespace URI or if Namespace
    194             *        processing is not being performed.
    195             * @param localName The local name (without prefix), or the
    196             *        empty string if Namespace processing is not being
    197             *        performed.
    198             * @param qName The qualified name (with prefix), or the
    199             *        empty string if qualified names are not available.
    200             * @exception SAXException Any SAX exception, possibly
    201             *            wrapping another exception.
    202             * @see org.xml.sax.ContentHandler#endElement
    203             */
    204            public void endElement(final String uri, final String localName, final String qName)
    205                throws SAXException {
    206
    207                setOpenComment((String[]) this.openComments.pop());
    208                setCloseComment(getCommentHandler().getComments());
    209
    210                try {
    211                    if (!this.isInclude && qName.equals(ClassModelTags.OBJECTS_TAG)) {
    212                        endRootDocument();
    213                        return;
    214                    }
    215
    216                    if (qName.equals(ClassModelTags.OBJECT_TAG)) {
    217                        if (getState() != IGNORE_OBJECT) {
    218                            endObjectDefinition();
    219                        }
    220                        setState(STATE_START);
    221                    }
    222                    else if (qName.equals(ClassModelTags.MAPPING_TAG)) {
    223                        setState(STATE_START);
    224                        endMultiplexMapping();
    225                    }
    226                    else if (qName.equals(ClassModelTags.CONSTRUCTOR_TAG)) {
    227                        if (getState() != IGNORE_OBJECT) {
    228                            setState(IN_OBJECT);
    229                        }
    230                    }
    231                }
    232                catch (ObjectDescriptionException e) {
    233                    throw new SAXException(e);
    234                }
    235                finally {
    236                    getCommentHandler().clearComments();
    237                }
    238            }
    239
    240            /**
    241             * Handles the start of an element within an object definition.
    242             *
    243             * @param qName The qualified name (with prefix), or the
    244             *        empty string if qualified names are not available.
    245             * @param attributes The attributes attached to the element.  If
    246             *        there are no attributes, it shall be an empty
    247             *        Attributes object.
    248             * @throws ObjectDescriptionException if an error occured while
    249             *        handling this tag
    250             */
    251            private void startObjectElement(final String qName, final Attributes attributes)
    252                throws ObjectDescriptionException {
    253                if (qName.equals(ClassModelTags.CONSTRUCTOR_TAG)) {
    254                    setState(CONSTRUCTOR_STATE);
    255                }
    256                else if (qName.equals(ClassModelTags.LOOKUP_PROPERTY_TAG)) {
    257                    final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
    258                    final String lookupKey = attributes.getValue(ClassModelTags.LOOKUP_ATTR);
    259                    handleLookupDefinition(name, lookupKey);
    260                }
    261                else if (qName.equals(ClassModelTags.IGNORED_PROPERTY_TAG)) {
    262                    final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
    263                    handleIgnoredProperty(name);
    264                }
    265                else if (qName.equals(ClassModelTags.ELEMENT_PROPERTY_TAG)) {
    266                    final String elementAtt = attributes.getValue(ClassModelTags.ELEMENT_ATTR);
    267                    final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
    268                    handleElementDefinition(name, elementAtt);
    269                }
    270                else if (qName.equals(ClassModelTags.ATTRIBUTE_PROPERTY_TAG)) {
    271                    final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
    272                    final String attribName = attributes.getValue(ClassModelTags.ATTRIBUTE_ATTR);
    273                    final String handler = attributes.getValue(ClassModelTags.ATTRIBUTE_HANDLER_ATTR);
    274                    handleAttributeDefinition(name, attribName, handler);
    275                }
    276            }
    277
    278            /**
    279             * Handles the include or object tag.
    280             *
    281             * @param qName The qualified name (with prefix), or the
    282             *        empty string if qualified names are not available.
    283             * @param attributes The attributes attached to the element.  If
    284             *        there are no attributes, it shall be an empty
    285             *        Attributes object.
    286             * @throws SAXException if an parser error occured
    287             * @throws ObjectDescriptionException if an object model related
    288             *        error occured.
    289             */
    290            private void startRootElement(final String qName, final Attributes attributes)
    291                throws SAXException, ObjectDescriptionException {
    292
    293                if (qName.equals(ClassModelTags.INCLUDE_TAG)) {
    294                    if (this.isInclude) {
    295                        Log.warn("Ignored nested include tag.");
    296                        return;
    297                    }
    298                    final String src = attributes.getValue(ClassModelTags.SOURCE_ATTR);
    299                    try {
    300                        final URL url = new URL(this.resource, src);
    301                        startIncludeHandling(url);
    302                        parseXmlDocument(url, true);
    303                        endIncludeHandling();
    304                    }
    305                    catch (Exception ioe) {
    306                        throw new ElementDefinitionException
    307                            (ioe, "Unable to include file from " + src);
    308                    }
    309                }
    310                else if (qName.equals(ClassModelTags.OBJECT_TAG)) {
    311                    setState(IN_OBJECT);
    312                    final String className = attributes.getValue(ClassModelTags.CLASS_ATTR);
    313                    String register = attributes.getValue(ClassModelTags.REGISTER_NAMES_ATTR);
    314                    if (register != null && register.length() == 0) {
    315                        register = null;
    316                    }
    317                    final boolean ignored = "true".equals(attributes.getValue(ClassModelTags.IGNORE_ATTR));
    318                    if (!startObjectDefinition(className, register, ignored)) {
    319                        setState(IGNORE_OBJECT);
    320                    }
    321                }
    322                else if (qName.equals(ClassModelTags.MANUAL_TAG)) {
    323                    final String className = attributes.getValue(ClassModelTags.CLASS_ATTR);
    324                    final String readHandler = attributes.getValue(ClassModelTags.READ_HANDLER_ATTR);
    325                    final String writeHandler = attributes.getValue(ClassModelTags.WRITE_HANDLER_ATTR);
    326                    handleManualMapping(className, readHandler, writeHandler);
    327                }
    328                else if (qName.equals(ClassModelTags.MAPPING_TAG)) {
    329                    setState(MAPPING_STATE);
    330                    final String typeAttr = attributes.getValue(ClassModelTags.TYPE_ATTR);
    331                    final String baseClass = attributes.getValue(ClassModelTags.BASE_CLASS_ATTR);
    332                    startMultiplexMapping(baseClass, typeAttr);
    333                }
    334            }
    335
    336            /**
    337             * Returns the current state.
    338             *
    339             * @return the state.
    340             */
    341            private int getState() {
    342                return this.state;
    343            }
    344
    345            /**
    346             * Sets the current state.
    347             *
    348             * @param state  the state.
    349             */
    350            private void setState(final int state) {
    351                this.state = state;
    352            }
    353        }
    354
    355        /** The comment handler. */
    356        private CommentHandler commentHandler;
    357
    358        /** The close comments. */
    359        private String[] closeComment;
    360
    361        /** The open comments. */
    362        private String[] openComment;
    363
    364        /**
    365         * Default Constructor.
    366         */
    367        public AbstractModelReader() {
    368            this.commentHandler = new CommentHandler();
    369        }
    370
    371        /**
    372         * Returns the comment handler.
    373         *
    374         * @return The comment handler.
    375         */
    376        protected CommentHandler getCommentHandler() {
    377            return this.commentHandler;
    378        }
    379
    380        /**
    381         * Returns the close comment.
    382         *
    383         * @return The close comment.
    384         */
    385        protected String[] getCloseComment() {
    386            return this.closeComment;
    387        }
    388
    389        /**
    390         * Returns the open comment.
    391         *
    392         * @return The open comment.
    393         */
    394        protected String[] getOpenComment() {
    395            return this.openComment;
    396        }
    397
    398        /**
    399         * Sets the close comment.
    400         *
    401         * @param closeComment  the close comment.
    402         */
    403        protected void setCloseComment(final String[] closeComment) {
    404            this.closeComment = closeComment;
    405        }
    406
    407        /**
    408         * Sets the open comment.
    409         *
    410         * @param openComment  the open comment.
    411         */
    412        protected void setOpenComment(final String[] openComment) {
    413            this.openComment = openComment;
    414        }
    415
    416        /**
    417         * Parses an XML document at the given URL.
    418         *
    419         * @param resource  the document URL.
    420         *
    421         * @throws ObjectDescriptionException ??
    422         */
    423        protected void parseXml(final URL resource) throws ObjectDescriptionException {
    424            parseXmlDocument(resource, false);
    425        }
    426
    427        /**
    428         * Parses the given specification and loads all includes specified in the files.
    429         * This implementation does not check for loops in the include files.
    430         *
    431         * @param resource  the url of the xml specification.
    432         * @param isInclude  an include?
    433         *
    434         * @throws org.jfree.xml.util.ObjectDescriptionException if an error occured which prevented the
    435         * loading of the specifications.
    436         */
    437        protected void parseXmlDocument(final URL resource, final boolean isInclude)
    438            throws ObjectDescriptionException {
    439
    440            try {
    441                final InputStream in = new BufferedInputStream(resource.openStream());
    442                final SAXParserFactory factory = SAXParserFactory.newInstance();
    443                final SAXParser saxParser = factory.newSAXParser();
    444                final XMLReader reader = saxParser.getXMLReader();
    445
    446                final SAXModelHandler handler = new SAXModelHandler(resource, isInclude);
    447                try {
    448                    reader.setProperty
    449                        ("http://xml.org/sax/properties/lexical-handler",
    450                            getCommentHandler());
    451                }
    452                catch (SAXException se) {
    453                    Log.debug("Comments are not supported by this SAX implementation.");
    454                }
    455                reader.setContentHandler(handler);
    456                reader.setDTDHandler(handler);
    457                reader.setErrorHandler(handler);
    458                reader.parse(new InputSource(in));
    459                in.close();
    460            }
    461            catch (Exception e) {
    462                // unable to init
    463                Log.warn("Unable to load factory specifications", e);
    464                throw new ObjectDescriptionException("Unable to load object factory specs.", e);
    465            }
    466        }
    467
    468        /**
    469         * Start the root document.
    470         */
    471        protected void startRootDocument() {
    472            // nothing required
    473        }
    474
    475        /**
    476         * End the root document.
    477         */
    478        protected void endRootDocument() {
    479            // nothing required
    480        }
    481
    482        /**
    483         * Start handling an include.
    484         *
    485         * @param resource  the URL.
    486         */
    487        protected void startIncludeHandling(final URL resource) {
    488            // nothing required
    489        }
    490
    491        /**
    492         * End handling an include.
    493         */
    494        protected void endIncludeHandling() {
    495            // nothing required
    496        }
    497
    498        /**
    499         * Callback method for ignored properties. Such properties get marked so that
    500         * the information regarding these properties won't get lost.
    501         *
    502         * @param name the name of the ignored property.
    503         */
    504        protected void handleIgnoredProperty(final String name) {
    505            // nothing required
    506        }
    507
    508        /**
    509         * Handles a manual mapping definition. The manual mapping maps specific
    510         * read and write handlers to a given base class. Manual mappings always
    511         * override any other definition.
    512         *
    513         * @param className the base class name
    514         * @param readHandler the class name of the read handler
    515         * @param writeHandler the class name of the write handler
    516         * @return true, if the mapping was accepted, false otherwise.
    517         * @throws ObjectDescriptionException if an unexpected error occured.
    518         */
    519        protected abstract boolean handleManualMapping(String className, String readHandler,
    520            String writeHandler) throws ObjectDescriptionException;
    521
    522        /**
    523         * Starts a object definition. The object definition collects all properties of
    524         * an bean-class and defines, which constructor should be used when creating the
    525         * class.
    526         *
    527         * @param className the class name of the defined object
    528         * @param register the (optional) register name, to lookup and reference the object
    529         * later.
    530         * @param ignored  ??.
    531         *
    532         * @return true, if the definition was accepted, false otherwise.
    533         * @throws ObjectDescriptionException if an unexpected error occured.
    534         */
    535        protected abstract boolean startObjectDefinition(String className, String register,
    536            boolean ignored) throws ObjectDescriptionException;
    537
    538        /**
    539         * Handles an attribute definition. This method gets called after the object definition
    540         * was started. The method will be called for every defined attribute property.
    541         *
    542         * @param name the name of the property
    543         * @param attribName the xml-attribute name to use later.
    544         * @param handlerClass the attribute handler class.
    545         * @throws ObjectDescriptionException if an error occured.
    546         */
    547        protected abstract void handleAttributeDefinition(String name, String attribName,
    548                                                          String handlerClass)
    549            throws ObjectDescriptionException;
    550
    551        /**
    552         * Handles an element definition. This method gets called after the object definition
    553         * was started. The method will be called for every defined element property. Element
    554         * properties are used to describe complex objects.
    555         *
    556         * @param name the name of the property
    557         * @param element the xml-tag name for the child element.
    558         * @throws ObjectDescriptionException if an error occurs.
    559         */
    560        protected abstract void handleElementDefinition(String name, String element)
    561            throws ObjectDescriptionException;
    562
    563        /**
    564         * Handles an lookup definition. This method gets called after the object definition
    565         * was started. The method will be called for every defined lookup property. Lookup properties
    566         * reference previously created object using the object's registry name.
    567         *
    568         * @param name the property name of the base object
    569         * @param lookupKey the register key of the referenced object
    570         * @throws ObjectDescriptionException if an error occured.
    571         */
    572        protected abstract void handleLookupDefinition(String name, String lookupKey)
    573            throws ObjectDescriptionException;
    574
    575        /**
    576         * Finializes the object definition.
    577         *
    578         * @throws ObjectDescriptionException if an error occures.
    579         */
    580        protected abstract void endObjectDefinition() throws ObjectDescriptionException;
    581
    582        /**
    583         * Starts a multiplex mapping. Multiplex mappings are used to define polymorphic
    584         * argument handlers. The mapper will collect all derived classes of the given
    585         * base class and will select the corresponding mapping based on the given type
    586         * attribute.
    587         *
    588         * @param className the base class name
    589         * @param typeAttr the xml-attribute name containing the mapping key
    590         */
    591        protected abstract void startMultiplexMapping(String className, String typeAttr);
    592
    593        /**
    594         * Defines an entry for the multiplex mapping. The new entry will be activated
    595         * when the base mappers type attribute contains this <code>typename</code> and
    596         * will resolve to the handler for the given classname.
    597         *
    598         * @param typeName the type value for this mapping.
    599         * @param className the class name to which this mapping resolves.
    600         * @throws ObjectDescriptionException if an error occurs.
    601         */
    602        protected abstract void handleMultiplexMapping(String typeName, String className)
    603            throws ObjectDescriptionException;
    604
    605        /**
    606         * Finializes the multiplexer mapping.
    607         *
    608         * @throws ObjectDescriptionException if an error occurs.
    609         */
    610        protected abstract void endMultiplexMapping() throws ObjectDescriptionException;
    611
    612        /**
    613         * Handles a constructor definition. Only one constructor can be defined for
    614         * a certain object type. The constructor will be filled using the given properties.
    615         *
    616         * @param propertyName the property name of the referenced local property
    617         * @param parameterClass the parameter class for the parameter.
    618         * @throws ObjectDescriptionException if an error occured.
    619         */
    620        protected abstract void handleConstructorDefinition(String propertyName, String parameterClass)
    621            throws ObjectDescriptionException;
    622
    623        /**
    624         * Loads the given class, and ignores all exceptions which may occur
    625         * during the loading. If the class was invalid, null is returned instead.
    626         *
    627         * @param className the name of the class to be loaded.
    628         * @return the class or null.
    629         */
    630        protected Class loadClass(final String className) {
    631            if (className == null) {
    632                return null;
    633            }
    634            if (className.startsWith("::")) {
    635                return BasicTypeSupport.getClassRepresentation(className);
    636            }
    637            try {
    638                return ObjectUtilities.getClassLoader(getClass()).loadClass(className);
    639            }
    640            catch (Exception e) {
    641                // ignore buggy classes for now ..
    642                Log.warn("Unable to load class", e);
    643                return null;
    644            }
    645        }
    646
    647    }