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    }