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}