Source for org.jfree.xml.generator.ModelBuilder

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  * 
  27:  * -----------------
  28:  * ModelBuilder.java
  29:  * -----------------
  30:  * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
  31:  *
  32:  * Original Author:  Thomas Morgner;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: ModelBuilder.java,v 1.3 2005/10/18 13:32:20 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 21-Jun-2003 : Initial version (TM);
  40:  * 26-Nov-2003 : Updated header and Javadocs (DG);
  41:  * 
  42:  */
  43: 
  44: package org.jfree.xml.generator;
  45: 
  46: import java.beans.BeanInfo;
  47: import java.beans.IndexedPropertyDescriptor;
  48: import java.beans.IntrospectionException;
  49: import java.beans.Introspector;
  50: import java.beans.PropertyDescriptor;
  51: import java.lang.reflect.Method;
  52: import java.lang.reflect.Modifier;
  53: import java.util.ArrayList;
  54: import java.util.Arrays;
  55: import java.util.Iterator;
  56: import java.util.Properties;
  57: 
  58: import org.jfree.util.HashNMap;
  59: import org.jfree.xml.generator.model.ClassDescription;
  60: import org.jfree.xml.generator.model.DescriptionModel;
  61: import org.jfree.xml.generator.model.MultiplexMappingInfo;
  62: import org.jfree.xml.generator.model.PropertyInfo;
  63: import org.jfree.xml.generator.model.PropertyType;
  64: import org.jfree.xml.generator.model.TypeInfo;
  65: import org.jfree.xml.util.BasicTypeSupport;
  66: 
  67: /**
  68:  * A model builder.  This class performs the work of creating a class description model from
  69:  * a set of source files.
  70:  */
  71: public final class ModelBuilder {
  72: 
  73:     /** The single instance. */
  74:     private static ModelBuilder instance;
  75: 
  76:     /**
  77:      * Returns the single instance of this class.
  78:      * 
  79:      * @return the single instance of this class.
  80:      */
  81:     public static ModelBuilder getInstance() {
  82:         if (instance == null) {
  83:             instance = new ModelBuilder();
  84:         }
  85:         return instance;
  86:     }
  87: 
  88:     /** The handler mapping. */
  89:     private Properties handlerMapping;
  90: 
  91:     /**
  92:      * Creates a single instance.
  93:      */
  94:     private ModelBuilder() {
  95:         this.handlerMapping = new Properties();
  96:     }
  97: 
  98:     /**
  99:      * 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: }