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    
043    package org.jfree.xml.util;
044    
045    import java.net.URL;
046    import java.util.ArrayList;
047    import java.util.Arrays;
048    import java.util.HashMap;
049    import java.util.Iterator;
050    
051    import org.jfree.util.Log;
052    import 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     */
063    public 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    }