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}