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    }