Source for org.jfree.xml.util.GenericObjectFactory

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  * 
  27:  * -------------------------
  28:  * GenericObjectFactory.java
  29:  * -------------------------
  30:  * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
  31:  *
  32:  * Original Author:  Thomas Morgner;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: GenericObjectFactory.java,v 1.4 2005/10/18 13:33:53 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 23-Sep-2003 : Initial version (TM);
  40:  *
  41:  */
  42: 
  43: package org.jfree.xml.util;
  44: 
  45: import java.beans.BeanInfo;
  46: import java.beans.IntrospectionException;
  47: import java.beans.Introspector;
  48: import java.beans.PropertyDescriptor;
  49: import java.lang.reflect.Constructor;
  50: import java.lang.reflect.Method;
  51: import java.util.HashMap;
  52: 
  53: /**
  54:  * The generic object factory contains all methods necessary to collect
  55:  * the property values needed to produce a fully instantiated object.
  56:  */
  57: public final class GenericObjectFactory {
  58: 
  59:     /** Storage for the constructor definitions. */
  60:     private final ConstructorDefinition[] constructorDefinitions;
  61:     
  62:     /** Storage for the property definitions. */
  63:     private final PropertyDefinition[] propertyDefinitions;
  64:     
  65:     /** Storage for the lookup definitions. */
  66:     private final LookupDefinition[] lookupDefinitions;
  67:     
  68:     /** Storage for the attribute definitions. */
  69:     private final AttributeDefinition[] attributeDefinitions;
  70:     
  71:     /** The ordered property names. */
  72:     private final String[] orderedPropertyNames;
  73: 
  74:     /** Storage for property info. */
  75:     private final HashMap propertyInfos;
  76:     
  77:     /** Storage for property values. */
  78:     private final HashMap propertyValues;
  79: 
  80:     /** The base class. */
  81:     private final Class baseClass;
  82:     
  83:     /** The register name. */
  84:     private final String registerName;
  85: 
  86:     /**
  87:      * Creates a new generic object factory.
  88:      * 
  89:      * @param c  the class.
  90:      * @param registerName the (optional) name under which to register the class for
  91:      *                     any later lookup.
  92:      * @param constructors  the constructor definitions.
  93:      * @param propertyDefinitions  the property definitions.
  94:      * @param lookupDefinitions  the lookup definitions.
  95:      * @param attributeDefinitions  the attribute definitions.
  96:      * @param orderedPropertyNames  the ordered property names.
  97:      * 
  98:      * @throws ObjectDescriptionException if there is a problem.
  99:      */
 100:     public GenericObjectFactory(final Class c, 
 101:                                 final String registerName,
 102:                                 final ConstructorDefinition[] constructors,
 103:                                 final PropertyDefinition[] propertyDefinitions,
 104:                                 final LookupDefinition[] lookupDefinitions,
 105:                                 final AttributeDefinition[] attributeDefinitions,
 106:                                 final String[] orderedPropertyNames)
 107:         throws ObjectDescriptionException {
 108: 
 109:         if (c == null) {
 110:             throw new NullPointerException("BaseClass cannot be null.");
 111:         }
 112:         this.baseClass = c;
 113:         this.registerName = registerName;
 114: 
 115:         this.propertyInfos = new HashMap();
 116:         this.propertyValues = new HashMap();
 117: 
 118:         this.constructorDefinitions = constructors;
 119:         this.propertyDefinitions = propertyDefinitions;
 120:         this.lookupDefinitions = lookupDefinitions;
 121:         this.attributeDefinitions = attributeDefinitions;
 122:         this.orderedPropertyNames = orderedPropertyNames;
 123: 
 124:         try {
 125:             final BeanInfo chartBeaninfo = Introspector.getBeanInfo(c, Object.class);
 126:             final PropertyDescriptor[] pd = chartBeaninfo.getPropertyDescriptors();
 127:             for (int i = 0; i < pd.length; i++) {
 128:                 this.propertyInfos.put(pd[i].getName(), pd[i]);
 129:             }
 130:         }
 131:         catch (IntrospectionException ioe) {
 132:             throw new ObjectDescriptionException(
 133:                 "This is an ugly solution right now ... dirty hack attack"
 134:             );
 135:         }
 136:     }
 137: 
 138:     /**
 139:      * A copy constructor.
 140:      * 
 141:      * @param factory  the factory to copy.
 142:      */
 143:     private GenericObjectFactory (final GenericObjectFactory factory) {
 144:         this.baseClass = factory.baseClass;
 145:         this.propertyValues = new HashMap();
 146:         this.orderedPropertyNames = factory.orderedPropertyNames;
 147:         this.constructorDefinitions = factory.constructorDefinitions;
 148:         this.propertyDefinitions = factory.propertyDefinitions;
 149:         this.attributeDefinitions = factory.attributeDefinitions;
 150:         this.propertyInfos = factory.propertyInfos;
 151:         this.registerName = factory.registerName;
 152:         this.lookupDefinitions = factory.lookupDefinitions;
 153:     }
 154: 
 155:     /**
 156:      * Returns a copy of this instance.
 157:      * 
 158:      * @return a copy of this instance.
 159:      */
 160:     public GenericObjectFactory getInstance () {
 161:         return new GenericObjectFactory(this);
 162:     }
 163: 
 164:     /**
 165:      * Returns the register name.
 166:      * 
 167:      * @return the register name.
 168:      */
 169:     public String getRegisterName() {
 170:         return this.registerName;
 171:     }
 172: 
 173:     /**
 174:      * Returns a property descriptor.
 175:      * 
 176:      * @param propertyName  the property name.
 177:      * 
 178:      * @return a property descriptor.
 179:      */
 180:     private PropertyDescriptor getPropertyDescriptor(final String propertyName) {
 181:         return (PropertyDescriptor) this.propertyInfos.get(propertyName);
 182:     }
 183: 
 184:     /**
 185:      * Returns the class for a tag name.
 186:      * 
 187:      * @param tagName  the tag name.
 188:      * 
 189:      * @return the class.
 190:      * 
 191:      * @throws ObjectDescriptionException if there is a problem.
 192:      */
 193:     public Class getTypeForTagName(final String tagName) throws ObjectDescriptionException {
 194:         final PropertyDefinition pdef = getPropertyDefinitionByTagName(tagName);
 195:         final PropertyDescriptor pdescr = getPropertyDescriptor(pdef.getPropertyName());
 196:         if (pdescr == null) {
 197:             throw new ObjectDescriptionException("Invalid Definition: " + pdef.getPropertyName());
 198:         }
 199:         return pdescr.getPropertyType();
 200:     }
 201: 
 202:     /**
 203:      * Returns true if there is a property definition for the specified property name.
 204:      * 
 205:      * @param propertyName  the property name.
 206:      * 
 207:      * @return A boolean.
 208:      */
 209:     public boolean isPropertyDefinition (final String propertyName) {
 210:         for (int i = 0; i < this.propertyDefinitions.length; i++) {
 211:             final PropertyDefinition pdef = this.propertyDefinitions[i];
 212:             if (pdef.getPropertyName().equals(propertyName)) {
 213:                 return true;
 214:             }
 215:         }
 216:         return false;
 217:     }
 218: 
 219:     /**
 220:      * Returns the property definition for the specified property name.
 221:      * 
 222:      * @param propertyName  the property name.
 223:      * 
 224:      * @return the property definition.
 225:      * 
 226:      * @throws ObjectDescriptionException if there is no such property for this object.
 227:      */
 228:     public PropertyDefinition getPropertyDefinitionByPropertyName(final String propertyName)
 229:         throws ObjectDescriptionException {
 230:         for (int i = 0; i < this.propertyDefinitions.length; i++) {
 231:             final PropertyDefinition pdef = this.propertyDefinitions[i];
 232:             if (pdef.getPropertyName().equals(propertyName)) {
 233:                 return pdef;
 234:             }
 235:         }
 236:         throw new ObjectDescriptionException(
 237:             "This property is not defined for this kind of object. : " + propertyName
 238:         );
 239:     }
 240: 
 241:     /**
 242:      * Returns a property definition for the specified tag name.
 243:      * 
 244:      * @param tagName  the tag name.
 245:      * 
 246:      * @return the property definition.
 247:      * 
 248:      * @throws ObjectDescriptionException if there is no such tag defined for this object.
 249:      */
 250:     public PropertyDefinition getPropertyDefinitionByTagName(final String tagName)
 251:         throws ObjectDescriptionException {
 252:         for (int i = 0; i < this.propertyDefinitions.length; i++) {
 253:             final PropertyDefinition pdef = this.propertyDefinitions[i];
 254:             if (pdef.getElementName().equals(tagName)) {
 255:                 return pdef;
 256:             }
 257:         }
 258:         throw new ObjectDescriptionException(
 259:             "This tag is not defined for this kind of object. : " + tagName
 260:         );
 261:     }
 262: 
 263:     /**
 264:      * Returns the constructor definitions.
 265:      * 
 266:      * @return the constructor definitions.
 267:      */
 268:     public ConstructorDefinition[] getConstructorDefinitions() {
 269:         return this.constructorDefinitions;
 270:     }
 271: 
 272:     /**
 273:      * Returns the attribute definitions.
 274:      * 
 275:      * @return the attribute definitions.
 276:      */
 277:     public AttributeDefinition[] getAttributeDefinitions() {
 278:         return this.attributeDefinitions;
 279:     }
 280: 
 281:     /**
 282:      * Returns the property definitions.
 283:      * 
 284:      * @return the property definitions.
 285:      */
 286:     public PropertyDefinition[] getPropertyDefinitions() {
 287:         return this.propertyDefinitions;
 288:     }
 289: 
 290:     /**
 291:      * Returns the property names.
 292:      * 
 293:      * @return the property names.
 294:      */
 295:     public String[] getOrderedPropertyNames() {
 296:         return this.orderedPropertyNames;
 297:     }
 298: 
 299:     /**
 300:      * Returns the lookup definitions.
 301:      * 
 302:      * @return the lookup definitions.
 303:      */
 304:     public LookupDefinition[] getLookupDefinitions() {
 305:         return this.lookupDefinitions;
 306:     }
 307: 
 308:     /**
 309:      * Returns the value of the specified property.
 310:      * 
 311:      * @param name  the property name.
 312:      * 
 313:      * @return the property value.
 314:      */
 315:     public Object getProperty(final String name) {
 316:         return this.propertyValues.get(name);
 317:     }
 318: 
 319:     /**
 320:      * Creates an object according to the definition.
 321:      * 
 322:      * @return the object.
 323:      * 
 324:      * @throws ObjectDescriptionException if there is a problem with the object description.
 325:      */
 326:     public Object createObject() throws ObjectDescriptionException {
 327:         final Class[] cArgs = new Class[this.constructorDefinitions.length];
 328:         final Object[] oArgs = new Object[this.constructorDefinitions.length];
 329:         for (int i = 0; i < cArgs.length; i++) {
 330:             final ConstructorDefinition cDef = this.constructorDefinitions[i];
 331:             cArgs[i] = cDef.getType();
 332:             if (cDef.isNull()) {
 333:                 oArgs[i] = null;
 334:             }
 335:             else {
 336:                 oArgs[i] = getProperty(cDef.getPropertyName());
 337:             }
 338:         }
 339: 
 340:         try {
 341:             final Constructor constr = this.baseClass.getConstructor(cArgs);
 342:             final Object o = constr.newInstance(oArgs);
 343:             return o;
 344:         }
 345:         catch (Exception e) {
 346:             throw new ObjectDescriptionException("Ugh! Constructor made a buuuh!", e);
 347:         }
 348:     }
 349: 
 350:     /**
 351:      * Sets a property value.
 352:      * 
 353:      * @param propertyName  the property name.
 354:      * @param value  the property value.
 355:      * 
 356:      * @throws ObjectDescriptionException if there is a problem with the object description.
 357:      */
 358:     public void setProperty(final String propertyName, final Object value)
 359:         throws ObjectDescriptionException {
 360:         final PropertyDescriptor pdesc = getPropertyDescriptor(propertyName);
 361:         if (pdesc == null) {
 362:             throw new ObjectDescriptionException("Unknown property " + propertyName);
 363:         }
 364: 
 365:         if (!isAssignableOrPrimitive(pdesc.getPropertyType(), value.getClass())) {
 366:             throw new ObjectDescriptionException(
 367:                 "Invalid value: " + pdesc.getPropertyType() + " vs. " + value.getClass()
 368:             );
 369:         }
 370: 
 371:         this.propertyValues.put(propertyName, value);
 372:     }
 373: 
 374:     /**
 375:      * Returns <code>true</code> if the base type is a primitive or assignable from the value type.
 376:      * 
 377:      * @param baseType  the base class.
 378:      * @param valueType  the value class.
 379:      * 
 380:      * @return A boolean.
 381:      */
 382:     private boolean isAssignableOrPrimitive(final Class baseType, final Class valueType) {
 383:         if (BasicTypeSupport.isBasicDataType(baseType)) {
 384:             return true;
 385:         }
 386:         // verbose stuff below *should* no longer be needed
 387:         return baseType.isAssignableFrom(valueType);
 388:     }
 389: 
 390:     /**
 391:      * Returns <code>true<code> if the specified property is...
 392:      * 
 393:      * @param propertyName  the property name.
 394:      * 
 395:      * @return A boolean.
 396:      */
 397:     private boolean isConstructorProperty(final String propertyName) {
 398:         for (int i = 0; i < this.constructorDefinitions.length; i++) {
 399:             final ConstructorDefinition cDef = this.constructorDefinitions[i];
 400:             if (propertyName.equals(cDef.getPropertyName())) {
 401:                 return true;
 402:             }
 403:         }
 404:         return false;
 405:     }
 406: 
 407:     /**
 408:      * Writes the properties for the object.
 409:      * 
 410:      * @param object  the object.
 411:      * 
 412:      * @throws ObjectDescriptionException if there is a problem.
 413:      */
 414:     public void writeObjectProperties(final Object object) throws ObjectDescriptionException {
 415:         // this assumes that the order of setting the attributes does not matter.
 416:         for (int i = 0; i < this.orderedPropertyNames.length; i++) {
 417:             try {
 418:                 final String name = this.orderedPropertyNames[i];
 419:                 if (isConstructorProperty(name)) {
 420:                     continue;
 421:                 }
 422:                 final Object value = getProperty(name);
 423:                 if (value == null) {
 424:                     // do nothing if value is not defined ...
 425:                     continue;
 426:                 }
 427:                 final PropertyDescriptor pdescr = getPropertyDescriptor(name);
 428:                 final Method setter = pdescr.getWriteMethod();
 429:                 setter.invoke(object, new Object[]{value});
 430:             }
 431:             catch (Exception e) {
 432:                 throw new ObjectDescriptionException(
 433:                     "Failed to set properties." + getBaseClass(), e
 434:                 );
 435:             }
 436:         }
 437:     }
 438: 
 439:     /**
 440:      * Reads the properties.
 441:      * 
 442:      * @param object  the object.
 443:      * 
 444:      * @throws ObjectDescriptionException if there is a problem.
 445:      */
 446:     public void readProperties(final Object object) throws ObjectDescriptionException {
 447:         // this assumes that the order of setting the attributes does not matter.
 448:         for (int i = 0; i < this.orderedPropertyNames.length; i++) {
 449:             try {
 450:                 final String name = this.orderedPropertyNames[i];
 451:                 final PropertyDescriptor pdescr = getPropertyDescriptor(name);
 452:                 if (pdescr == null) {
 453:                     throw new IllegalStateException("No property defined: " + name);
 454:                 }
 455:                 final Method setter = pdescr.getReadMethod();
 456:                 final Object value = setter.invoke(object, new Object[0]);
 457:                 if (value == null) {
 458:                     // do nothing if value is not defined ... or null
 459:                     continue;
 460:                 }
 461:                 setProperty(name, value);
 462:             }
 463:             catch (Exception e) {
 464:                 throw new ObjectDescriptionException("Failed to set properties.", e);
 465:             }
 466:         }
 467:     }
 468: 
 469:     /**
 470:      * Returns the base class.
 471:      * 
 472:      * @return the base class.
 473:      */
 474:     public Class getBaseClass() {
 475:         return this.baseClass;
 476:     }
 477:     
 478: }