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 }