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 */
042package org.jfree.xml.parser;
043
044import java.awt.BasicStroke;
045import java.awt.Color;
046import java.awt.Font;
047import java.awt.GradientPaint;
048import java.awt.Insets;
049import java.awt.Paint;
050import java.awt.RenderingHints;
051import java.awt.Stroke;
052import java.awt.geom.Point2D;
053import java.awt.geom.Rectangle2D;
054import java.util.ArrayList;
055import java.util.HashMap;
056import java.util.LinkedList;
057import java.util.List;
058import java.util.Stack;
059import java.util.Vector;
060
061import org.jfree.util.ObjectUtilities;
062import org.jfree.xml.FrontendDefaultHandler;
063import org.jfree.xml.ParseException;
064import org.jfree.xml.ElementDefinitionException;
065import org.jfree.xml.parser.coretypes.BasicStrokeReadHandler;
066import org.jfree.xml.parser.coretypes.ColorReadHandler;
067import org.jfree.xml.parser.coretypes.FontReadHandler;
068import org.jfree.xml.parser.coretypes.GenericReadHandler;
069import org.jfree.xml.parser.coretypes.GradientPaintReadHandler;
070import org.jfree.xml.parser.coretypes.InsetsReadHandler;
071import org.jfree.xml.parser.coretypes.ListReadHandler;
072import org.jfree.xml.parser.coretypes.Point2DReadHandler;
073import org.jfree.xml.parser.coretypes.Rectangle2DReadHandler;
074import org.jfree.xml.parser.coretypes.RenderingHintsReadHandler;
075import org.jfree.xml.parser.coretypes.StringReadHandler;
076import org.jfree.xml.util.ManualMappingDefinition;
077import org.jfree.xml.util.MultiplexMappingDefinition;
078import org.jfree.xml.util.MultiplexMappingEntry;
079import org.jfree.xml.util.ObjectFactory;
080import org.jfree.xml.util.SimpleObjectFactory;
081import org.xml.sax.Attributes;
082import org.xml.sax.SAXException;
083
084/**
085 * A base root SAX handler.
086 */
087public 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}