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 }