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     * RootXmlWriteHandler.java
029     * ------------------------
030     * (C) Copyright 2002-2005, by Object Refinery Limited.
031     *
032     * Original Author:  Peter Becker;
033     * Contributor(s):   -;
034     *
035     * $Id: RootXmlWriteHandler.java,v 1.5 2005/10/18 13:35:06 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 23-Dec-2003 : Added missing Javadocs (DG);
040     *
041     */
042    package org.jfree.xml.writer;
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.io.IOException;
055    import java.util.ArrayList;
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.util.ManualMappingDefinition;
063    import org.jfree.xml.util.MultiplexMappingDefinition;
064    import org.jfree.xml.util.MultiplexMappingEntry;
065    import org.jfree.xml.util.ObjectFactory;
066    import org.jfree.xml.util.SimpleObjectFactory;
067    import org.jfree.xml.writer.coretypes.BasicStrokeWriteHandler;
068    import org.jfree.xml.writer.coretypes.ColorWriteHandler;
069    import org.jfree.xml.writer.coretypes.FontWriteHandler;
070    import org.jfree.xml.writer.coretypes.GenericWriteHandler;
071    import org.jfree.xml.writer.coretypes.GradientPaintWriteHandler;
072    import org.jfree.xml.writer.coretypes.InsetsWriteHandler;
073    import org.jfree.xml.writer.coretypes.ListWriteHandler;
074    import org.jfree.xml.writer.coretypes.Point2DWriteHandler;
075    import org.jfree.xml.writer.coretypes.Rectangle2DWriteHandler;
076    import org.jfree.xml.writer.coretypes.RenderingHintsWriteHandler;
077    
078    /**
079     * A root handler for writing objects to XML format.
080     */
081    public abstract class RootXmlWriteHandler {
082    
083        /** A map containg the manual mappings. */
084        private SimpleObjectFactory classToHandlerMapping;
085    
086        /**
087         * Creates a new RootXmlWrite handler with the default mappings enabled.
088         */
089        public RootXmlWriteHandler() {
090            this.classToHandlerMapping = new SimpleObjectFactory();
091    
092            // set up handling for Paint objects
093            final MultiplexMappingEntry[] paintEntries = new MultiplexMappingEntry[2];
094            paintEntries[0] = new MultiplexMappingEntry("color", Color.class.getName());
095            paintEntries[1] = new MultiplexMappingEntry("gradientPaint", GradientPaint.class.getName());
096            addMultiplexMapping(Paint.class, "type", paintEntries);
097            addManualMapping(GradientPaint.class, GradientPaintWriteHandler.class);
098            addManualMapping(Color.class, ColorWriteHandler.class);
099    
100            // set up handling for Point2D objects
101            final MultiplexMappingEntry[] point2DEntries = new MultiplexMappingEntry[2];
102            point2DEntries[0] = new MultiplexMappingEntry("float", Point2D.Float.class.getName());
103            point2DEntries[1] = new MultiplexMappingEntry("double", Point2D.Double.class.getName());
104            addMultiplexMapping(Point2D.class, "type", point2DEntries);
105            addManualMapping(Point2D.Float.class, Point2DWriteHandler.class);
106            addManualMapping(Point2D.Double.class, Point2DWriteHandler.class);
107    
108            // set up handling for Stroke objects
109            final MultiplexMappingEntry[] strokeEntries = new MultiplexMappingEntry[1];
110            strokeEntries[0] = new MultiplexMappingEntry("basic", BasicStroke.class.getName());
111            addMultiplexMapping(Stroke.class, "type", strokeEntries);
112            addManualMapping(BasicStroke.class, BasicStrokeWriteHandler.class);
113    
114            // set up handling for Rectangle2D objects
115            final MultiplexMappingEntry[] rectangle2DEntries = new MultiplexMappingEntry[2];
116            rectangle2DEntries[0] = new MultiplexMappingEntry(
117                "float", Rectangle2D.Float.class.getName()
118            );
119            rectangle2DEntries[1] = new MultiplexMappingEntry(
120                "double", Rectangle2D.Double.class.getName()
121            );
122            addMultiplexMapping(Rectangle2D.class, "type", rectangle2DEntries);
123            addManualMapping(Rectangle2D.Float.class, Rectangle2DWriteHandler.class);
124            addManualMapping(Rectangle2D.Double.class, Rectangle2DWriteHandler.class);
125    
126            // set up handling for List objects
127            final MultiplexMappingEntry[] listEntries = new MultiplexMappingEntry[4];
128            listEntries[0] = new MultiplexMappingEntry("array-list", ArrayList.class.getName());
129            listEntries[1] = new MultiplexMappingEntry("linked-list", LinkedList.class.getName());
130            listEntries[2] = new MultiplexMappingEntry("vector", Vector.class.getName());
131            listEntries[3] = new MultiplexMappingEntry("stack", Stack.class.getName());
132            addMultiplexMapping(List.class, "type", listEntries);
133            addManualMapping(LinkedList.class, ListWriteHandler.class);
134            addManualMapping(Vector.class, ListWriteHandler.class);
135            addManualMapping(ArrayList.class, ListWriteHandler.class);
136            addManualMapping(Stack.class, ListWriteHandler.class);
137    
138            // handle all other direct mapping types
139            addManualMapping(RenderingHints.class, RenderingHintsWriteHandler.class);
140            addManualMapping(Insets.class, InsetsWriteHandler.class);
141            addManualMapping(Font.class, FontWriteHandler.class);
142        }
143    
144        /**
145         * Returns the object factory.
146         * 
147         * @return the object factory.
148         */
149        protected abstract ObjectFactory getFactoryLoader();
150    
151        /**
152         * Adds a new manual mapping to this handler.
153         *
154         * This method provides support for the manual mapping. The manual mapping
155         * will become active before the multiplexers were queried. This facility
156         * could be used to override the model definition.
157         *
158         * @param classToWrite the class, which should be handled
159         * @param handler the write handler implementation for that class.
160         */
161        protected void addManualMapping(final Class classToWrite, final Class handler) {
162            if (handler == null) {
163                throw new NullPointerException("handler must not be null.");
164            }
165            if (classToWrite == null) {
166                throw new NullPointerException("classToWrite must not be null.");
167            }
168            if (!XmlWriteHandler.class.isAssignableFrom(handler)) {
169                throw new IllegalArgumentException("The given handler is no XmlWriteHandler.");
170            }
171    
172            this.classToHandlerMapping.addManualMapping
173                (new ManualMappingDefinition(classToWrite, null, handler.getName()));
174        }
175    
176        /**
177         * Adds a multiplex mapping.
178         * 
179         * @param baseClass  the base class.
180         * @param typeAttr  the type attribute.
181         * @param mdef  the mapping entries.
182         */
183        protected void addMultiplexMapping(final Class baseClass,
184                                           final String typeAttr,
185                                           final MultiplexMappingEntry[] mdef) {
186            
187            this.classToHandlerMapping.addMultiplexMapping(
188                new MultiplexMappingDefinition(baseClass, typeAttr, mdef)
189            );
190            
191        }
192    
193        /**
194         * Tries to find the mapping for the given class. This will first check
195         * the manual mapping and then try to use the object factory to resolve
196         * the class parameter into a write handler.
197         *
198         * @param classToWrite the class for which to find a handler.
199         * @return the write handler, never null.
200         * @throws XMLWriterException if no handler could be found for the given class.
201         */
202        protected XmlWriteHandler getMapping(final Class classToWrite) throws XMLWriterException {
203    
204            if (classToWrite == null) {
205                throw new NullPointerException("ClassToWrite is null.");
206            }
207    
208            // search direct matches, first the direct definitions ...
209            ManualMappingDefinition manualMapping =
210                this.classToHandlerMapping.getManualMappingDefinition(classToWrite);
211            if (manualMapping == null) {
212                // search the manual mappings from the xml file.
213                manualMapping = getFactoryLoader().getManualMappingDefinition(classToWrite);
214            }
215            if (manualMapping != null) {
216                return loadHandlerClass(manualMapping.getWriteHandler());
217            }
218    
219    
220            // multiplexer definitions can be safely ignored here, as they are used to
221            // map parent classes to more specific child classes. In this case, we already
222            // know the child class and can look up the handler directly.
223    
224            // of course we have to check for multiplexers later, so that we can apply
225            // the mutiplex-attributes.
226    
227            // and finally try the generic handler matches ...
228            if (this.classToHandlerMapping.isGenericHandler(classToWrite)) {
229                return new GenericWriteHandler(
230                    this.classToHandlerMapping.getFactoryForClass(classToWrite)
231                );
232            }
233            if (getFactoryLoader().isGenericHandler(classToWrite)) {
234                return new GenericWriteHandler(getFactoryLoader().getFactoryForClass(classToWrite));
235            }
236    
237            throw new XMLWriterException("Unable to handle " + classToWrite);
238        }
239    
240        /**
241         * Writes the given object with the specified tagname. This method will
242         * do nothing, if the given object is null.
243         *
244         * @param tagName  the tagname for the xml-element containing the object
245         * definition. The tagname must not be null.
246         * @param object  the object which should be written.
247         * @param baseClass  the base class.
248         * @param writer  the xml writer used to write the content, never null.
249         * 
250         * @throws IOException if an IOException occures.
251         * @throws XMLWriterException if an object model related error occures during
252         * the writing.
253         */
254        public void write(final String tagName, final Object object, final Class baseClass, final XMLWriter writer)
255            throws IOException, XMLWriterException {
256            if (object == null) {
257                return;
258            }
259            if (tagName == null) {
260                throw new NullPointerException("RootXmlWriteHandler.write(..) : tagName is null");
261            }
262            if (writer == null) {
263                throw new NullPointerException("RootXmlWriteHandler.write(..) : writer is null");
264            }
265            if (!baseClass.isInstance(object)) {
266                throw new ClassCastException("Object is no instance of " + baseClass);
267            }
268            final Class classToWrite = object.getClass();
269            final XmlWriteHandler handler = getMapping(classToWrite);
270            handler.setRootHandler(this);
271    
272            String attributeName = null;
273            String attributeValue = null;
274    
275            // find multiplexer for this class...
276            MultiplexMappingDefinition mplex =
277                getFactoryLoader().getMultiplexDefinition(baseClass);
278            if (mplex == null) {
279                mplex = this.classToHandlerMapping.getMultiplexDefinition(baseClass);
280            }
281            if (mplex != null) {
282                final MultiplexMappingEntry entry =
283                    mplex.getEntryForClass(classToWrite.getName());
284                if (entry != null) {
285                    attributeName = mplex.getAttributeName();
286                    attributeValue = entry.getAttributeValue();
287                }
288                else {
289                    throw new XMLWriterException(
290                        "Unable to find child mapping for multiplexer " 
291                        + baseClass + " to child " + classToWrite
292                    );
293                }
294            }
295    
296            handler.write(tagName, object, writer, attributeName, attributeValue);
297            writer.allowLineBreak();
298        }
299    
300        /**
301         * Loads the given class, and ignores all exceptions which may occur
302         * during the loading. If the class was invalid, null is returned instead.
303         *
304         * @param className the name of the class to be loaded.
305         * @return the class or null.
306         * 
307         * @throws XMLWriterException if there is a writer exception.
308         */
309        protected XmlWriteHandler loadHandlerClass(final String className)
310            throws XMLWriterException {
311            if (className == null) {
312                throw new XMLWriterException("LoadHanderClass: Class name not defined");
313            }
314            try {
315                final Class c = ObjectUtilities.getClassLoader(getClass()).loadClass(className);
316                return (XmlWriteHandler) c.newInstance();
317            }
318            catch (Exception e) {
319                // ignore buggy classes for now ..
320                throw new XMLWriterException("LoadHanderClass: Unable to instantiate " + className, e);
321            }
322        }
323        
324    }