    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 * GenericObjectFactory.java
    029 * -------------------------
    030 * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
    031 *
    032 * Original Author:  Thomas Morgner;
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *
    035 * $Id: GenericObjectFactory.java,v 1.5 2011/10/17 20:08:22 mungady Exp $
    036 *
    037 * Changes
    038 * -------
    039 * 23-Sep-2003 : Initial version (TM);
    040 *
    041 */
    042
    043package org.jfree.xml.util;
    044
    045import java.beans.BeanInfo;
    046import java.beans.IntrospectionException;
    047import java.beans.Introspector;
    048import java.beans.PropertyDescriptor;
    049import java.lang.reflect.Constructor;
    050import java.lang.reflect.Method;
    051import java.util.HashMap;
    052
    053/**
    054 * The generic object factory contains all methods necessary to collect
    055 * the property values needed to produce a fully instantiated object.
    056 */
    057public final class GenericObjectFactory {
    058
    059    /** Storage for the constructor definitions. */
    060    private final ConstructorDefinition[] constructorDefinitions;
    061
    062    /** Storage for the property definitions. */
    063    private final PropertyDefinition[] propertyDefinitions;
    064
    065    /** Storage for the lookup definitions. */
    066    private final LookupDefinition[] lookupDefinitions;
    067
    068    /** Storage for the attribute definitions. */
    069    private final AttributeDefinition[] attributeDefinitions;
    070
    071    /** The ordered property names. */
    072    private final String[] orderedPropertyNames;
    073
    074    /** Storage for property info. */
    075    private final HashMap propertyInfos;
    076
    077    /** Storage for property values. */
    078    private final HashMap propertyValues;
    079
    080    /** The base class. */
    081    private final Class baseClass;
    082
    083    /** The register name. */
    084    private final String registerName;
    085
    086    /**
    087     * Creates a new generic object factory.
    088     *
    089     * @param c  the class.
    090     * @param registerName the (optional) name under which to register the class for
    091     *                     any later lookup.
    092     * @param constructors  the constructor definitions.
    093     * @param propertyDefinitions  the property definitions.
    094     * @param lookupDefinitions  the lookup definitions.
    095     * @param attributeDefinitions  the attribute definitions.
    096     * @param orderedPropertyNames  the ordered property names.
    097     *
    098     * @throws ObjectDescriptionException if there is a problem.
    099     */
    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}