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 * ObjectFactoryLoader.java
029 * ------------------------
030 * (C) Copyright 2002-2005, by Thomas Morgner and Contributors.
031 *
032 * Original Author:  Thomas Morgner;
033 * Contributor(s):   -;
034 *
035 * $Id: ObjectFactoryLoader.java,v 1.4 2005/10/18 13:33:53 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 24-Sep-2003: Initial version
040 *
041 */
042
043package org.jfree.xml.util;
044
045import java.net.URL;
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.HashMap;
049import java.util.Iterator;
050
051import org.jfree.util.Log;
052import org.jfree.xml.attributehandlers.AttributeHandler;
053
054/**
055 * The object factory loader loads the xml specification for the generic
056 * handlers. The specification may be distributed over multiple files.
057 * <p>
058 * This class provides the model management for the reader and writer.
059 * The instantiation of the handlers is done elsewhere.
060 *
061 * @author TM
062 */
063public class ObjectFactoryLoader extends AbstractModelReader implements ObjectFactory {
064
065    /** Maps classes to GenericObjectFactory instances. */
066    private HashMap objectMappings;
067    
068    /** Manual mappings. */
069    private HashMap manualMappings;
070    
071    /** Multiplex mappings. */
072    private HashMap multiplexMappings;
073
074    /** The target class. */
075    private Class target;
076    
077    /** The register name. */
078    private String registerName;
079    
080    /** The property definition. */
081    private ArrayList propertyDefinition;
082    
083    /** The attribute definition. */
084    private ArrayList attributeDefinition;
085    
086    /** The constructor definition. */
087    private ArrayList constructorDefinition;
088    
089    /** The lookup definitions. */
090    private ArrayList lookupDefinitions;
091    
092    /** The ordered names. */
093    private ArrayList orderedNames;
094
095    /** The base class. */
096    private String baseClass;
097    
098    /** The attribute name. */
099    private String attributeName;
100    
101    /** The multiplex entries. */
102    private ArrayList multiplexEntries;
103
104    /**
105     * Creates a new object factory loader for the given base file.
106     *
107     * @param resourceName the URL of the initial specification file.
108     * 
109     * @throws ObjectDescriptionException if the file could not be parsed.
110     */
111    public ObjectFactoryLoader(final URL resourceName) throws ObjectDescriptionException {
112        this.objectMappings = new HashMap();
113        this.manualMappings = new HashMap();
114        this.multiplexMappings = new HashMap();
115        parseXml(resourceName);
116        rebuildSuperClasses();
117    }
118
119    private void rebuildSuperClasses() throws ObjectDescriptionException {
120        this.propertyDefinition = new ArrayList();
121        this.attributeDefinition = new ArrayList();
122        this.constructorDefinition = new ArrayList();
123        this.lookupDefinitions = new ArrayList();
124        this.orderedNames = new ArrayList();
125
126        final HashMap newObjectDescriptions = new HashMap();
127        final Iterator it = this.objectMappings.keySet().iterator();
128        while (it.hasNext()) {
129            final Object key = it.next();
130            final GenericObjectFactory gef = (GenericObjectFactory) this.objectMappings.get(key);
131            performSuperClassUpdate(gef);
132
133            final PropertyDefinition[] propertyDefs = (PropertyDefinition[])
134            this.propertyDefinition.toArray(new PropertyDefinition[0]);
135            final LookupDefinition[] lookupDefs = (LookupDefinition[])
136            this.lookupDefinitions.toArray(new LookupDefinition[0]);
137            final AttributeDefinition[] attribDefs = (AttributeDefinition[])
138            this.attributeDefinition.toArray(new AttributeDefinition[0]);
139            final ConstructorDefinition[] constructorDefs = (ConstructorDefinition[])
140            this.constructorDefinition.toArray(new ConstructorDefinition[0]);
141            final String[] orderedNamesDefs = (String[])
142            this.orderedNames.toArray(new String[0]);
143
144            final GenericObjectFactory objectFactory = new GenericObjectFactory
145                (gef.getBaseClass(), gef.getRegisterName(), constructorDefs,
146                    propertyDefs, lookupDefs, attribDefs, orderedNamesDefs);
147            newObjectDescriptions.put(key, objectFactory);
148
149            this.propertyDefinition.clear();
150            this.attributeDefinition.clear();
151            this.constructorDefinition.clear();
152            this.lookupDefinitions.clear();
153            this.orderedNames.clear();
154        }
155
156        this.objectMappings.clear();
157        this.objectMappings = newObjectDescriptions;
158
159        this.propertyDefinition = null;
160        this.attributeDefinition = null;
161        this.constructorDefinition = null;
162        this.lookupDefinitions = null;
163        this.orderedNames = null;
164    }
165
166    private void performSuperClassUpdate(final GenericObjectFactory gef) {
167        // first handle the super classes, ...
168        final Class superClass = gef.getBaseClass().getSuperclass();
169        if (superClass != null && !superClass.equals(Object.class)) {
170            final GenericObjectFactory superGef = (GenericObjectFactory) this.objectMappings.get(
171                superClass
172            );
173            if (superGef != null) {
174                performSuperClassUpdate(superGef);
175            }
176        }
177
178        // and finally append all local properties ...
179        this.propertyDefinition.addAll(Arrays.asList(gef.getPropertyDefinitions()));
180        this.attributeDefinition.addAll(Arrays.asList(gef.getAttributeDefinitions()));
181        this.constructorDefinition.addAll(Arrays.asList(gef.getConstructorDefinitions()));
182        this.lookupDefinitions.addAll(Arrays.asList(gef.getLookupDefinitions()));
183        this.orderedNames.addAll(Arrays.asList(gef.getOrderedPropertyNames()));
184    }
185
186    /**
187     * Starts a object definition. The object definition collects all properties of
188     * an bean-class and defines, which constructor should be used when creating the
189     * class.
190     *
191     * @param className the class name of the defined object
192     * @param register the (optional) register name, to lookup and reference the object later.
193     * @param ignore  ignore?
194     * 
195     * @return true, if the definition was accepted, false otherwise.
196     * @throws ObjectDescriptionException if an unexpected error occured.
197     */
198    protected boolean startObjectDefinition(final String className, final String register, final boolean ignore)
199        throws ObjectDescriptionException {
200
201        if (ignore) {
202            return false;
203        }
204        this.target = loadClass(className);
205        if (this.target == null) {
206            Log.warn(new Log.SimpleMessage("Failed to load class ", className));
207            return false;
208        }
209        this.registerName = register;
210        this.propertyDefinition = new ArrayList();
211        this.attributeDefinition = new ArrayList();
212        this.constructorDefinition = new ArrayList();
213        this.lookupDefinitions = new ArrayList();
214        this.orderedNames = new ArrayList();
215        return true;
216    }
217
218    /**
219     * Handles an attribute definition. This method gets called after the object definition
220     * was started. The method will be called for every defined attribute property.
221     *
222     * @param name the name of the property
223     * @param attribName the xml-attribute name to use later.
224     * @param handlerClass the attribute handler class.
225     * @throws ObjectDescriptionException if an error occured.
226     */
227    protected void handleAttributeDefinition(final String name, final String attribName, final String handlerClass)
228        throws ObjectDescriptionException {
229        final AttributeHandler handler = loadAttributeHandler(handlerClass);
230        this.orderedNames.add(name);
231        this.attributeDefinition.add(new AttributeDefinition(name, attribName, handler));
232    }
233
234    /**
235     * Handles an element definition. This method gets called after the object definition
236     * was started. The method will be called for every defined element property. Element
237     * properties are used to describe complex objects.
238     *
239     * @param name the name of the property
240     * @param element the xml-tag name for the child element.
241     * @throws ObjectDescriptionException if an error occurs.
242     */
243    protected void handleElementDefinition(final String name, final String element)
244        throws ObjectDescriptionException {
245        this.orderedNames.add(name);
246        this.propertyDefinition.add(new PropertyDefinition(name, element));
247    }
248
249    /**
250     * Handles an lookup definition. This method gets called after the object definition
251     * was started. The method will be called for every defined lookup property. Lookup properties
252     * reference previously created object using the object's registry name.
253     *
254     * @param name the property name of the base object
255     * @param lookupKey the register key of the referenced object
256     * @throws ObjectDescriptionException if an error occured.
257     */
258    protected void handleLookupDefinition(final String name, final String lookupKey)
259        throws ObjectDescriptionException {
260        final LookupDefinition ldef = new LookupDefinition(name, lookupKey);
261        this.orderedNames.add(name);
262        this.lookupDefinitions.add(ldef);
263    }
264
265    /**
266     * Finializes the object definition.
267     *
268     * @throws ObjectDescriptionException if an error occures.
269     */
270    protected void endObjectDefinition()
271        throws ObjectDescriptionException {
272
273        final PropertyDefinition[] propertyDefs = (PropertyDefinition[])
274        this.propertyDefinition.toArray(new PropertyDefinition[0]);
275        final LookupDefinition[] lookupDefs = (LookupDefinition[])
276        this.lookupDefinitions.toArray(new LookupDefinition[0]);
277        final AttributeDefinition[] attribDefs = (AttributeDefinition[])
278        this.attributeDefinition.toArray(new AttributeDefinition[0]);
279        final ConstructorDefinition[] constructorDefs = (ConstructorDefinition[])
280        this.constructorDefinition.toArray(new ConstructorDefinition[0]);
281        final String[] orderedNamesDefs = (String[])
282        this.orderedNames.toArray(new String[0]);
283
284        final GenericObjectFactory objectFactory = new GenericObjectFactory
285            (this.target, this.registerName, constructorDefs,
286                propertyDefs, lookupDefs, attribDefs, orderedNamesDefs);
287        this.objectMappings.put(this.target, objectFactory);
288    }
289
290    /**
291     * Handles a constructor definition. Only one constructor can be defined for
292     * a certain object type. The constructor will be filled using the given properties.
293     *
294     * @param propertyName the property name of the referenced local property
295     * @param parameterClass the parameter class for the parameter.
296     */
297    protected void handleConstructorDefinition(final String propertyName, final String parameterClass) {
298        final Class c = loadClass(parameterClass);
299        this.orderedNames.add(propertyName);
300        this.constructorDefinition.add(new ConstructorDefinition(propertyName, c));
301    }
302
303    /**
304     * Handles a manual mapping definition. The manual mapping maps specific
305     * read and write handlers to a given base class. Manual mappings always
306     * override any other definition.
307     *
308     * @param className the base class name
309     * @param readHandler the class name of the read handler
310     * @param writeHandler the class name of the write handler
311     * @return true, if the mapping was accepted, false otherwise.
312     * @throws ObjectDescriptionException if an unexpected error occured.
313     */
314    protected boolean handleManualMapping(final String className, final String readHandler, final String writeHandler)
315        throws ObjectDescriptionException {
316
317        if (!this.manualMappings.containsKey(className)) {
318            final Class loadedClass = loadClass(className);
319            this.manualMappings.put(loadedClass, new ManualMappingDefinition
320                (loadedClass, readHandler, writeHandler));
321            return true;
322        }
323        return false;
324    }
325
326    /**
327     * Starts a multiplex mapping. Multiplex mappings are used to define polymorphic
328     * argument handlers. The mapper will collect all derived classes of the given
329     * base class and will select the corresponding mapping based on the given type
330     * attribute.
331     *
332     * @param className the base class name
333     * @param typeAttr the xml-attribute name containing the mapping key
334     */
335    protected void startMultiplexMapping(final String className, final String typeAttr) {
336        this.baseClass = className;
337        this.attributeName = typeAttr;
338        this.multiplexEntries = new ArrayList();
339    }
340
341    /**
342     * Defines an entry for the multiplex mapping. The new entry will be activated
343     * when the base mappers type attribute contains this <code>typename</code> and
344     * will resolve to the handler for the given classname.
345     *
346     * @param typeName the type value for this mapping.
347     * @param className the class name to which this mapping resolves.
348     * @throws ObjectDescriptionException if an error occurs.
349     */
350    protected void handleMultiplexMapping(final String typeName, final String className)
351        throws ObjectDescriptionException {
352        this.multiplexEntries.add
353            (new MultiplexMappingEntry(typeName, className));
354    }
355
356    /**
357     * Finializes the multiplexer mapping.
358     *
359     * @throws ObjectDescriptionException if an error occurs.
360     */
361    protected void endMultiplexMapping() throws ObjectDescriptionException {
362        final MultiplexMappingEntry[] mappings = (MultiplexMappingEntry[])
363        this.multiplexEntries.toArray(new MultiplexMappingEntry[0]);
364        final Class c = loadClass(this.baseClass);
365        this.multiplexMappings.put(c,
366            new MultiplexMappingDefinition(c, this.attributeName, mappings));
367        this.multiplexEntries = null;
368    }
369
370    /**
371     * Loads an instantiates the attribute handler specified by the given
372     * class name.
373     *
374     * @param attribute the attribute handlers classname.
375     * @return the created attribute handler instance
376     * @throws ObjectDescriptionException if the handler could not be loaded.
377     */
378    private AttributeHandler loadAttributeHandler(final String attribute)
379        throws ObjectDescriptionException {
380
381        final Class c = loadClass(attribute);
382        try {
383            return (AttributeHandler) c.newInstance();
384        }
385        catch (Exception e) {
386            throw new ObjectDescriptionException
387                ("Invalid attribute handler specified: " + attribute);
388        }
389    }
390
391    /**
392     * Checks, whether the factory has a description for the given class.
393     *
394     * @param c the class to be handled by the factory.
395     * @return true, if an description exists for the given class, false otherwise.
396     */
397    public boolean isGenericHandler(final Class c) {
398        return this.objectMappings.containsKey(c);
399    }
400
401    /**
402     * Returns a factory instance for the given class. The factory is independent
403     * from all previously generated instances.
404     *
405     * @param c the class
406     * @return the object factory.
407     */
408    public GenericObjectFactory getFactoryForClass(final Class c) {
409        final GenericObjectFactory factory = (GenericObjectFactory) this.objectMappings.get(c);
410        if (factory == null) {
411            return null;
412        }
413        return factory.getInstance();
414    }
415
416    /**
417     * Returns the manual mapping definition for the given class, or null, if
418     * not manual definition exists.
419     *
420     * @param c the class for which to check the existence of the definition
421     * @return the manual mapping definition or null.
422     */
423    public ManualMappingDefinition getManualMappingDefinition(final Class c) {
424        return (ManualMappingDefinition) this.manualMappings.get(c);
425    }
426
427    /**
428     * Returns the multiplex definition for the given class, or null, if no
429     * such definition exists.
430     *
431     * @param c the class for which to check the existence of the multiplexer
432     * @return the multiplexer for the class, or null if no multiplexer exists.
433     */
434    public MultiplexMappingDefinition getMultiplexDefinition(final Class c) {
435        final MultiplexMappingDefinition definition = (MultiplexMappingDefinition)
436        this.multiplexMappings.get(c);
437        return definition;
438    }
439
440}