    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    }