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: * ModelBuilder.java
029: * -----------------
030: * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
031: *
032: * Original Author: Thomas Morgner;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: ModelBuilder.java,v 1.3 2005/10/18 13:32:20 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 21-Jun-2003 : Initial version (TM);
040: * 26-Nov-2003 : Updated header and Javadocs (DG);
041: *
042: */
043:
044: package org.jfree.xml.generator;
045:
046: import java.beans.BeanInfo;
047: import java.beans.IndexedPropertyDescriptor;
048: import java.beans.IntrospectionException;
049: import java.beans.Introspector;
050: import java.beans.PropertyDescriptor;
051: import java.lang.reflect.Method;
052: import java.lang.reflect.Modifier;
053: import java.util.ArrayList;
054: import java.util.Arrays;
055: import java.util.Iterator;
056: import java.util.Properties;
057:
058: import org.jfree.util.HashNMap;
059: import org.jfree.xml.generator.model.ClassDescription;
060: import org.jfree.xml.generator.model.DescriptionModel;
061: import org.jfree.xml.generator.model.MultiplexMappingInfo;
062: import org.jfree.xml.generator.model.PropertyInfo;
063: import org.jfree.xml.generator.model.PropertyType;
064: import org.jfree.xml.generator.model.TypeInfo;
065: import org.jfree.xml.util.BasicTypeSupport;
066:
067: /**
068: * A model builder. This class performs the work of creating a class description model from
069: * a set of source files.
070: */
071: public final class ModelBuilder {
072:
073: /** The single instance. */
074: private static ModelBuilder instance;
075:
076: /**
077: * Returns the single instance of this class.
078: *
079: * @return the single instance of this class.
080: */
081: public static ModelBuilder getInstance() {
082: if (instance == null) {
083: instance = new ModelBuilder();
084: }
085: return instance;
086: }
087:
088: /** The handler mapping. */
089: private Properties handlerMapping;
090:
091: /**
092: * Creates a single instance.
093: */
094: private ModelBuilder() {
095: this .handlerMapping = new Properties();
096: }
097:
098: /**
099: * Adds attribute handlers.
100: *
101: * @param p the handlers.
102: */
103: public void addAttributeHandlers(final Properties p) {
104: this .handlerMapping.putAll(p);
105: }
106:
107: /**
108: * Builds a model from the classes provided by the {@link SourceCollector}.
109: * <P>
110: * The {@link DescriptionGenerator} class invokes this.
111: *
112: * @param c the source collector.
113: * @param model the model under construction (<code>null</code> permitted).
114: *
115: * @return The completed model.
116: */
117: public DescriptionModel buildModel(final SourceCollector c,
118: DescriptionModel model) {
119:
120: Class[] classes = c.getClasses();
121:
122: if (model == null) {
123: model = new DescriptionModel();
124: }
125:
126: while (classes.length != 0) {
127: classes = fillModel(classes, model);
128: }
129:
130: fillSuperClasses(model);
131: // search for multiplexer classes
132:
133: // first search all classes used in parameters and add them to
134: // our list of possible base classes
135: final Class[] baseClasses = findElementTypes(model);
136:
137: final HashNMap classMap = new HashNMap();
138: for (int i = 0; i < baseClasses.length; i++) {
139:
140: final Class base = baseClasses[i];
141:
142: for (int j = 0; j < baseClasses.length; j++) {
143:
144: final Class child = baseClasses[j];
145: if (Modifier.isAbstract(child.getModifiers())) {
146: continue;
147: }
148: if (base.isAssignableFrom(child)) {
149: classMap.add(base, child);
150: }
151: }
152: }
153:
154: // at this point, the keys of 'classMap' represent all required
155: // multiplexers, while the values assigned to these keys define the
156: // possible childs
157: final Iterator keys = classMap.keys();
158: while (keys.hasNext()) {
159: final Class base = (Class) keys.next();
160: final Class[] childs = (Class[]) classMap.toArray(base,
161: new Class[0]);
162: if (childs.length < 2) {
163: continue;
164: }
165:
166: boolean isNew = false;
167: MultiplexMappingInfo mmi = model.getMappingModel()
168: .lookupMultiplexMapping(base);
169: final ArrayList typeInfoList;
170: if (mmi == null) {
171: mmi = new MultiplexMappingInfo(base);
172: typeInfoList = new ArrayList();
173: isNew = true;
174: } else {
175: typeInfoList = new ArrayList(Arrays.asList(mmi
176: .getChildClasses()));
177: }
178:
179: for (int i = 0; i < childs.length; i++) {
180: // the generic information is only added, if no other information
181: // is already present ...
182: final TypeInfo typeInfo = new TypeInfo(childs[i]
183: .getName(), childs[i]);
184: if (!typeInfoList.contains(typeInfo)) {
185: typeInfoList.add(typeInfo);
186: }
187: }
188:
189: mmi.setChildClasses((TypeInfo[]) typeInfoList
190: .toArray(new TypeInfo[0]));
191: if (isNew) {
192: model.getMappingModel().addMultiplexMapping(mmi);
193: }
194: }
195:
196: // when resolving a class to an handler, the resolver first has to
197: // search for an multiplexer before searching for handlers. Otherwise
198: // non-abstract baseclasses will be found before the multiplexer can
199: // resolve the situation.
200: return model;
201: }
202:
203: private Class[] findElementTypes(final DescriptionModel model) {
204: final ArrayList baseClasses = new ArrayList();
205:
206: for (int i = 0; i < model.size(); i++) {
207: final ClassDescription cd = model.get(i);
208: if (!baseClasses.contains(cd.getObjectClass())) {
209: baseClasses.add(cd.getObjectClass());
210: }
211:
212: final PropertyInfo[] properties = cd.getProperties();
213: for (int p = 0; p < properties.length; p++) {
214: // filter primitive types ... they cannot form a generalization
215: // relation
216: if (!properties[p].getPropertyType().equals(
217: PropertyType.ELEMENT)) {
218: continue;
219: }
220: final Class type = properties[p].getType();
221: if (baseClasses.contains(type)) {
222: continue;
223: }
224: // filter final classes, they too cannot have derived classes
225: if (Modifier.isFinal(type.getModifiers())) {
226: continue;
227: }
228: baseClasses.add(type);
229: }
230: }
231: return (Class[]) baseClasses.toArray(new Class[baseClasses
232: .size()]);
233: }
234:
235: /**
236: * Fills the super class for all object descriptions of the model. The
237: * super class is only filled, if the object's super class is contained
238: * in the model.
239: *
240: * @param model the model which should get its superclasses updated.
241: */
242: private void fillSuperClasses(final DescriptionModel model) {
243: // Fill superclasses
244: for (int i = 0; i < model.size(); i++) {
245: final ClassDescription cd = model.get(i);
246: final Class parent = cd.getObjectClass().getSuperclass();
247: if (parent == null) {
248: continue;
249: }
250: final ClassDescription super CD = model.get(parent);
251: if (super CD != null) {
252: cd.setSuperClass(super CD.getObjectClass());
253: }
254: }
255: }
256:
257: /**
258: * Updates the model to contain the given classes.
259: *
260: * @param classes a list of classes which should be part of the model.
261: * @param model the model which is updated
262: *
263: * @return A list of super classes which should also be contained in the model.
264: */
265: private Class[] fillModel(final Class[] classes,
266: final DescriptionModel model) {
267: // first check all direct matches from the source collector.
268: // but make sure that we also detect external superclasses -
269: // we have to get all properties ...
270: final ArrayList super Classes = new ArrayList();
271: for (int i = 0; i < classes.length; i++) {
272:
273: Class super Class = classes[i].getSuperclass();
274: if (super Class != null) {
275: if (!Object.class.equals(super Class)
276: && !contains(classes, super Class)
277: && !super Classes.contains(super Class)) {
278: super Classes.add(super Class);
279: }
280: } else {
281: super Class = Object.class;
282: }
283:
284: try {
285: final BeanInfo bi = Introspector.getBeanInfo(
286: classes[i], super Class);
287: final ClassDescription parent = model.get(classes[i]);
288: final ClassDescription cd = createClassDescription(bi,
289: parent);
290: if (cd != null) {
291: model.addClassDescription(cd);
292: }
293: } catch (IntrospectionException ie) {
294: // swallowed....
295: }
296: }
297: return (Class[]) super Classes.toArray(new Class[0]);
298: }
299:
300: /**
301: * Creates a {@link ClassDescription} object for the specified bean info.
302: *
303: * @param beanInfo the bean info.
304: * @param parent the parent class description.
305: *
306: * @return The class description.
307: */
308: private ClassDescription createClassDescription(
309: final BeanInfo beanInfo, final ClassDescription parent) {
310: final PropertyDescriptor[] props = beanInfo
311: .getPropertyDescriptors();
312: final ArrayList properties = new ArrayList();
313: for (int i = 0; i < props.length; i++) {
314: final PropertyDescriptor propertyDescriptor = props[i];
315: PropertyInfo pi;
316: if (parent != null) {
317: pi = parent.getProperty(propertyDescriptor.getName());
318: if (pi != null) {
319: // Property already found, don't touch it
320: // Log.info (new Log.SimpleMessage
321: // ("Ignore predefined property: ", propertyDescriptor.getName()));
322: properties.add(pi);
323: continue;
324: }
325: }
326:
327: if (props[i] instanceof IndexedPropertyDescriptor) {
328: // this would handle lists and array access. We don't support
329: // this in the direct approach. We will need some cheating:
330: // <Chart>
331: // <Subtitle-list>
332: // <title1 ..>
333: // <title2 ..>
334: // pi = createIndexedPropertyInfo((IndexedPropertyDescriptor) props[i]);
335: } else {
336: pi = createSimplePropertyInfo(props[i]);
337: if (pi != null) {
338: properties.add(pi);
339: }
340: }
341: }
342:
343: final PropertyInfo[] propArray = (PropertyInfo[]) properties
344: .toArray(new PropertyInfo[properties.size()]);
345:
346: final ClassDescription cd;
347: if (parent != null) {
348: cd = parent;
349: } else {
350: cd = new ClassDescription(beanInfo.getBeanDescriptor()
351: .getBeanClass());
352: cd.setDescription(beanInfo.getBeanDescriptor()
353: .getShortDescription());
354: }
355:
356: cd.setProperties(propArray);
357: return cd;
358: }
359:
360: /**
361: * Checks, whether the given method can be called from the generic object factory.
362: *
363: * @param method the method descriptor
364: * @return true, if the method is not null and public, false otherwise.
365: */
366: public static boolean isValidMethod(final Method method) {
367: if (method == null) {
368: return false;
369: }
370: if (!Modifier.isPublic(method.getModifiers())) {
371: return false;
372: }
373: return true;
374: }
375:
376: /**
377: * Creates a {@link PropertyInfo} object from a {@link PropertyDescriptor}.
378: *
379: * @param pd the property descriptor.
380: *
381: * @return the property info (<code>null</code> possible).
382: */
383: public PropertyInfo createSimplePropertyInfo(
384: final PropertyDescriptor pd) {
385:
386: final boolean readMethod = isValidMethod(pd.getReadMethod());
387: final boolean writeMethod = isValidMethod(pd.getWriteMethod());
388: if (!writeMethod || !readMethod) {
389: // a property is useless for our purposes without having a read or write method.
390: return null;
391: }
392:
393: final PropertyInfo pi = new PropertyInfo(pd.getName(), pd
394: .getPropertyType());
395: pi.setConstrained(pd.isConstrained());
396: pi.setDescription(pd.getShortDescription());
397: pi.setNullable(true);
398: pi.setPreserve(false);
399: pi.setReadMethodAvailable(readMethod);
400: pi.setWriteMethodAvailable(writeMethod);
401: pi.setXmlName(pd.getName());
402: if (isAttributeProperty(pd.getPropertyType())) {
403: pi.setPropertyType(PropertyType.ATTRIBUTE);
404: pi.setXmlHandler(getHandlerClass(pd.getPropertyType()));
405: } else {
406: pi.setPropertyType(PropertyType.ELEMENT);
407: }
408: return pi;
409: }
410:
411: /**
412: * Checks, whether the given class can be handled as attribute.
413: * All primitive types can be attributes as well as all types which have
414: * a custom attribute handler defined.
415: *
416: * @param c the class which should be checked
417: * @return true, if the class can be handled as attribute, false otherwise.
418: */
419: private boolean isAttributeProperty(final Class c) {
420: if (BasicTypeSupport.isBasicDataType(c)) {
421: return true;
422: }
423: return this .handlerMapping.containsKey(c.getName());
424: }
425:
426: /**
427: * Returns the class name for the attribute handler for a property of the specified class.
428: *
429: * @param c the class for which to search an attribute handler
430: * @return the handler class or null, if this class cannot be handled
431: * as attribute.
432: */
433: private String getHandlerClass(final Class c) {
434: if (BasicTypeSupport.isBasicDataType(c)) {
435: final String handler = BasicTypeSupport.getHandlerClass(c);
436: if (handler != null) {
437: return handler;
438: }
439: }
440: return this .handlerMapping.getProperty(c.getName());
441: }
442:
443: /**
444: * Checks, whether the class <code>c</code> is contained in the given
445: * class array.
446: *
447: * @param cAll the list of all classes
448: * @param c the class to be searched
449: * @return true, if the class is contained in the array, false otherwise.
450: */
451: private boolean contains(final Class[] cAll, final Class c) {
452: for (int i = 0; i < cAll.length; i++) {
453: if (cAll[i].equals(c)) {
454: return true;
455: }
456: }
457: return false;
458: }
459:
460: // private PropertyInfo createIndexedPropertyInfo(IndexedPropertyDescriptor prop)
461: // {
462: //
463: // MethodInfo readMethod = createMethodInfo(prop.getIndexedReadMethod());
464: // MethodInfo writeMethod = createMethodInfo(prop.getIndexedWriteMethod());
465: // if (writeMethod == null)
466: // {
467: // return null;
468: // }
469: // IndexedPropertyInfo pi = new IndexedPropertyInfo(prop.getName());
470: // pi.setConstrained(prop.isConstrained());
471: // pi.setDescription(prop.getShortDescription());
472: // pi.setNullable(true);
473: // pi.setPreserve(false);
474: // pi.setType(prop.getIndexedPropertyType());
475: // pi.setReadMethod(readMethod);
476: // pi.setWriteMethod(writeMethod);
477: //
478: // TypeInfo keyInfo = new TypeInfo("index");
479: // keyInfo.setType(Integer.TYPE);
480: // keyInfo.setNullable(false);
481: // keyInfo.setConstrained(true); // throws indexoutofboundsexception
482: // keyInfo.setDescription("Generic index value");
483: // KeyDescription kd = new KeyDescription(new TypeInfo[]{keyInfo});
484: // pi.setKey(kd);
485: // return pi;
486: // }
487: }
|