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, DescriptionModel model) {
118
119 Class[] classes = c.getClasses();
120
121 if (model == null) {
122 model = new DescriptionModel();
123 }
124
125 while (classes.length != 0) {
126 classes = fillModel(classes, model);
127 }
128
129 fillSuperClasses(model);
130 // search for multiplexer classes
131
132 // first search all classes used in parameters and add them to
133 // our list of possible base classes
134 final Class[] baseClasses = findElementTypes(model);
135
136 final HashNMap classMap = new HashNMap();
137 for (int i = 0; i < baseClasses.length; i++) {
138
139 final Class base = baseClasses[i];
140
141 for (int j = 0; j < baseClasses.length; j++) {
142
143 final Class child = baseClasses[j];
144 if (Modifier.isAbstract(child.getModifiers())) {
145 continue;
146 }
147 if (base.isAssignableFrom(child)) {
148 classMap.add(base, child);
149 }
150 }
151 }
152
153 // at this point, the keys of 'classMap' represent all required
154 // multiplexers, while the values assigned to these keys define the
155 // possible childs
156 final Iterator keys = classMap.keys();
157 while (keys.hasNext()) {
158 final Class base = (Class) keys.next();
159 final Class[] childs = (Class[]) classMap.toArray(base, new Class[0]);
160 if (childs.length < 2) {
161 continue;
162 }
163
164 boolean isNew = false;
165 MultiplexMappingInfo mmi = model.getMappingModel().lookupMultiplexMapping(base);
166 final ArrayList typeInfoList;
167 if (mmi == null) {
168 mmi = new MultiplexMappingInfo(base);
169 typeInfoList = new ArrayList();
170 isNew = true;
171 }
172 else {
173 typeInfoList = new ArrayList(Arrays.asList(mmi.getChildClasses()));
174 }
175
176 for (int i = 0; i < childs.length; i++) {
177 // the generic information is only added, if no other information
178 // is already present ...
179 final TypeInfo typeInfo = new TypeInfo(childs[i].getName(), childs[i]);
180 if (!typeInfoList.contains(typeInfo)) {
181 typeInfoList.add(typeInfo);
182 }
183 }
184
185 mmi.setChildClasses((TypeInfo[]) typeInfoList.toArray(new TypeInfo[0]));
186 if (isNew) {
187 model.getMappingModel().addMultiplexMapping(mmi);
188 }
189 }
190
191 // when resolving a class to an handler, the resolver first has to
192 // search for an multiplexer before searching for handlers. Otherwise
193 // non-abstract baseclasses will be found before the multiplexer can
194 // resolve the situation.
195 return model;
196 }
197
198 private Class[] findElementTypes(final DescriptionModel model) {
199 final ArrayList baseClasses = new ArrayList();
200
201 for (int i = 0; i < model.size(); i++) {
202 final ClassDescription cd = model.get(i);
203 if (!baseClasses.contains(cd.getObjectClass())) {
204 baseClasses.add(cd.getObjectClass());
205 }
206
207 final PropertyInfo[] properties = cd.getProperties();
208 for (int p = 0; p < properties.length; p++) {
209 // filter primitive types ... they cannot form a generalization
210 // relation
211 if (!properties[p].getPropertyType().equals(PropertyType.ELEMENT)) {
212 continue;
213 }
214 final Class type = properties[p].getType();
215 if (baseClasses.contains(type)) {
216 continue;
217 }
218 // filter final classes, they too cannot have derived classes
219 if (Modifier.isFinal(type.getModifiers())) {
220 continue;
221 }
222 baseClasses.add(type);
223 }
224 }
225 return (Class[]) baseClasses.toArray(new Class[baseClasses.size()]);
226 }
227
228 /**
229 * Fills the super class for all object descriptions of the model. The
230 * super class is only filled, if the object's super class is contained
231 * in the model.
232 *
233 * @param model the model which should get its superclasses updated.
234 */
235 private void fillSuperClasses(final DescriptionModel model) {
236 // Fill superclasses
237 for (int i = 0; i < model.size(); i++) {
238 final ClassDescription cd = model.get(i);
239 final Class parent = cd.getObjectClass().getSuperclass();
240 if (parent == null) {
241 continue;
242 }
243 final ClassDescription superCD = model.get(parent);
244 if (superCD != null) {
245 cd.setSuperClass(superCD.getObjectClass());
246 }
247 }
248 }
249
250 /**
251 * Updates the model to contain the given classes.
252 *
253 * @param classes a list of classes which should be part of the model.
254 * @param model the model which is updated
255 *
256 * @return A list of super classes which should also be contained in the model.
257 */
258 private Class[] fillModel(final Class[] classes, final DescriptionModel model) {
259 // first check all direct matches from the source collector.
260 // but make sure that we also detect external superclasses -
261 // we have to get all properties ...
262 final ArrayList superClasses = new ArrayList();
263 for (int i = 0; i < classes.length; i++) {
264
265 Class superClass = classes[i].getSuperclass();
266 if (superClass != null) {
267 if (!Object.class.equals(superClass)
268 && !contains(classes, superClass)
269 && !superClasses.contains(superClass)) {
270 superClasses.add(superClass);
271 }
272 }
273 else {
274 superClass = Object.class;
275 }
276
277 try {
278 final BeanInfo bi = Introspector.getBeanInfo(classes[i], superClass);
279 final ClassDescription parent = model.get(classes[i]);
280 final ClassDescription cd = createClassDescription(bi, parent);
281 if (cd != null) {
282 model.addClassDescription(cd);
283 }
284 }
285 catch (IntrospectionException ie) {
286 // swallowed....
287 }
288 }
289 return (Class[]) superClasses.toArray(new Class[0]);
290 }
291
292 /**
293 * Creates a {@link ClassDescription} object for the specified bean info.
294 *
295 * @param beanInfo the bean info.
296 * @param parent the parent class description.
297 *
298 * @return The class description.
299 */
300 private ClassDescription createClassDescription (final BeanInfo beanInfo, final ClassDescription parent) {
301 final PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
302 final ArrayList properties = new ArrayList();
303 for (int i = 0; i < props.length; i++) {
304 final PropertyDescriptor propertyDescriptor = props[i];
305 PropertyInfo pi;
306 if (parent != null) {
307 pi = parent.getProperty(propertyDescriptor.getName());
308 if (pi != null) {
309 // Property already found, don't touch it
310 // Log.info (new Log.SimpleMessage
311 // ("Ignore predefined property: ", propertyDescriptor.getName()));
312 properties.add(pi);
313 continue;
314 }
315 }
316
317 if (props[i] instanceof IndexedPropertyDescriptor) {
318 // this would handle lists and array access. We don't support
319 // this in the direct approach. We will need some cheating:
320 // <Chart>
321 // <Subtitle-list>
322 // <title1 ..>
323 // <title2 ..>
324 // pi = createIndexedPropertyInfo((IndexedPropertyDescriptor) props[i]);
325 }
326 else {
327 pi = createSimplePropertyInfo(props[i]);
328 if (pi != null) {
329 properties.add(pi);
330 }
331 }
332 }
333
334 final PropertyInfo[] propArray = (PropertyInfo[])
335 properties.toArray(new PropertyInfo[properties.size()]);
336
337 final ClassDescription cd;
338 if (parent != null) {
339 cd = parent;
340 }
341 else {
342 cd = new ClassDescription(beanInfo.getBeanDescriptor().getBeanClass());
343 cd.setDescription(beanInfo.getBeanDescriptor().getShortDescription());
344 }
345
346 cd.setProperties(propArray);
347 return cd;
348 }
349
350 /**
351 * Checks, whether the given method can be called from the generic object factory.
352 *
353 * @param method the method descriptor
354 * @return true, if the method is not null and public, false otherwise.
355 */
356 public static boolean isValidMethod(final Method method) {
357 if (method == null) {
358 return false;
359 }
360 if (!Modifier.isPublic(method.getModifiers())) {
361 return false;
362 }
363 return true;
364 }
365
366 /**
367 * Creates a {@link PropertyInfo} object from a {@link PropertyDescriptor}.
368 *
369 * @param pd the property descriptor.
370 *
371 * @return the property info (<code>null</code> possible).
372 */
373 public PropertyInfo createSimplePropertyInfo(final PropertyDescriptor pd) {
374
375 final boolean readMethod = isValidMethod(pd.getReadMethod());
376 final boolean writeMethod = isValidMethod(pd.getWriteMethod());
377 if (!writeMethod || !readMethod) {
378 // a property is useless for our purposes without having a read or write method.
379 return null;
380 }
381
382 final PropertyInfo pi = new PropertyInfo(pd.getName(), pd.getPropertyType());
383 pi.setConstrained(pd.isConstrained());
384 pi.setDescription(pd.getShortDescription());
385 pi.setNullable(true);
386 pi.setPreserve(false);
387 pi.setReadMethodAvailable(readMethod);
388 pi.setWriteMethodAvailable(writeMethod);
389 pi.setXmlName(pd.getName());
390 if (isAttributeProperty(pd.getPropertyType())) {
391 pi.setPropertyType(PropertyType.ATTRIBUTE);
392 pi.setXmlHandler(getHandlerClass(pd.getPropertyType()));
393 }
394 else {
395 pi.setPropertyType(PropertyType.ELEMENT);
396 }
397 return pi;
398 }
399
400 /**
401 * Checks, whether the given class can be handled as attribute.
402 * All primitive types can be attributes as well as all types which have
403 * a custom attribute handler defined.
404 *
405 * @param c the class which should be checked
406 * @return true, if the class can be handled as attribute, false otherwise.
407 */
408 private boolean isAttributeProperty(final Class c) {
409 if (BasicTypeSupport.isBasicDataType(c)) {
410 return true;
411 }
412 return this.handlerMapping.containsKey(c.getName());
413 }
414
415 /**
416 * Returns the class name for the attribute handler for a property of the specified class.
417 *
418 * @param c the class for which to search an attribute handler
419 * @return the handler class or null, if this class cannot be handled
420 * as attribute.
421 */
422 private String getHandlerClass(final Class c) {
423 if (BasicTypeSupport.isBasicDataType(c)) {
424 final String handler = BasicTypeSupport.getHandlerClass(c);
425 if (handler != null) {
426 return handler;
427 }
428 }
429 return this.handlerMapping.getProperty(c.getName());
430 }
431
432 /**
433 * Checks, whether the class <code>c</code> is contained in the given
434 * class array.
435 *
436 * @param cAll the list of all classes
437 * @param c the class to be searched
438 * @return true, if the class is contained in the array, false otherwise.
439 */
440 private boolean contains(final Class[] cAll, final Class c) {
441 for (int i = 0; i < cAll.length; i++) {
442 if (cAll[i].equals(c)) {
443 return true;
444 }
445 }
446 return false;
447 }
448
449
450 // private PropertyInfo createIndexedPropertyInfo(IndexedPropertyDescriptor prop)
451 // {
452 //
453 // MethodInfo readMethod = createMethodInfo(prop.getIndexedReadMethod());
454 // MethodInfo writeMethod = createMethodInfo(prop.getIndexedWriteMethod());
455 // if (writeMethod == null)
456 // {
457 // return null;
458 // }
459 // IndexedPropertyInfo pi = new IndexedPropertyInfo(prop.getName());
460 // pi.setConstrained(prop.isConstrained());
461 // pi.setDescription(prop.getShortDescription());
462 // pi.setNullable(true);
463 // pi.setPreserve(false);
464 // pi.setType(prop.getIndexedPropertyType());
465 // pi.setReadMethod(readMethod);
466 // pi.setWriteMethod(writeMethod);
467 //
468 // TypeInfo keyInfo = new TypeInfo("index");
469 // keyInfo.setType(Integer.TYPE);
470 // keyInfo.setNullable(false);
471 // keyInfo.setConstrained(true); // throws indexoutofboundsexception
472 // keyInfo.setDescription("Generic index value");
473 // KeyDescription kd = new KeyDescription(new TypeInfo[]{keyInfo});
474 // pi.setKey(kd);
475 // return pi;
476 // }
477 }