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    }