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 }