    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.4 2005/10/18 13:33:53 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 23-Sep-2003 : Initial version (TM);
    040     *
    041     */
    042
    043    package org.jfree.xml.util;
    044
    045    import java.beans.BeanInfo;
    046    import java.beans.IntrospectionException;
    047    import java.beans.Introspector;
    048    import java.beans.PropertyDescriptor;
    049    import java.lang.reflect.Constructor;
    050    import java.lang.reflect.Method;
    051    import 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     */
    057    public 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    }