org.jfree.xml.generator.ModelBuilder Maven / Gradle / Ivy
Show all versions of jtstand-common Show documentation
/*
* Copyright (c) 2009 Albert Kurucz.
*
* This file, ModelBuilder.java is part of JTStand.
*
* JTStand is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JTStand is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with GTStand. If not, see .
*/
package org.jfree.xml.generator;
import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Properties;
import org.jfree.util.HashNMap;
import org.jfree.xml.generator.model.ClassDescription;
import org.jfree.xml.generator.model.DescriptionModel;
import org.jfree.xml.generator.model.MultiplexMappingInfo;
import org.jfree.xml.generator.model.PropertyInfo;
import org.jfree.xml.generator.model.PropertyType;
import org.jfree.xml.generator.model.TypeInfo;
import org.jfree.xml.util.BasicTypeSupport;
/**
* A model builder. This class performs the work of creating a class description model from
* a set of source files.
*/
public final class ModelBuilder {
/** The single instance. */
private static ModelBuilder instance;
/**
* Returns the single instance of this class.
*
* @return the single instance of this class.
*/
public static ModelBuilder getInstance() {
if (instance == null) {
instance = new ModelBuilder();
}
return instance;
}
/** The handler mapping. */
private Properties handlerMapping;
/**
* Creates a single instance.
*/
private ModelBuilder() {
this.handlerMapping = new Properties();
}
/**
* Adds attribute handlers.
*
* @param p the handlers.
*/
public void addAttributeHandlers(final Properties p) {
this.handlerMapping.putAll(p);
}
/**
* Builds a model from the classes provided by the {@link SourceCollector}.
*
* The {@link DescriptionGenerator} class invokes this.
*
* @param c the source collector.
* @param model the model under construction (null
permitted).
*
* @return The completed model.
*/
public DescriptionModel buildModel(final SourceCollector c, DescriptionModel model) {
Class[] classes = c.getClasses();
if (model == null) {
model = new DescriptionModel();
}
while (classes.length != 0) {
classes = fillModel(classes, model);
}
fillSuperClasses(model);
// search for multiplexer classes
// first search all classes used in parameters and add them to
// our list of possible base classes
final Class[] baseClasses = findElementTypes(model);
final HashNMap classMap = new HashNMap();
for (int i = 0; i < baseClasses.length; i++) {
final Class base = baseClasses[i];
for (int j = 0; j < baseClasses.length; j++) {
final Class child = baseClasses[j];
if (Modifier.isAbstract(child.getModifiers())) {
continue;
}
if (base.isAssignableFrom(child)) {
classMap.add(base, child);
}
}
}
// at this point, the keys of 'classMap' represent all required
// multiplexers, while the values assigned to these keys define the
// possible childs
final Iterator keys = classMap.keys();
while (keys.hasNext()) {
final Class base = (Class) keys.next();
final Class[] childs = (Class[]) classMap.toArray(base, new Class[0]);
if (childs.length < 2) {
continue;
}
boolean isNew = false;
MultiplexMappingInfo mmi = model.getMappingModel().lookupMultiplexMapping(base);
final ArrayList typeInfoList;
if (mmi == null) {
mmi = new MultiplexMappingInfo(base);
typeInfoList = new ArrayList();
isNew = true;
}
else {
typeInfoList = new ArrayList(Arrays.asList(mmi.getChildClasses()));
}
for (int i = 0; i < childs.length; i++) {
// the generic information is only added, if no other information
// is already present ...
final TypeInfo typeInfo = new TypeInfo(childs[i].getName(), childs[i]);
if (!typeInfoList.contains(typeInfo)) {
typeInfoList.add(typeInfo);
}
}
mmi.setChildClasses((TypeInfo[]) typeInfoList.toArray(new TypeInfo[0]));
if (isNew) {
model.getMappingModel().addMultiplexMapping(mmi);
}
}
// when resolving a class to an handler, the resolver first has to
// search for an multiplexer before searching for handlers. Otherwise
// non-abstract baseclasses will be found before the multiplexer can
// resolve the situation.
return model;
}
private Class[] findElementTypes(final DescriptionModel model) {
final ArrayList baseClasses = new ArrayList();
for (int i = 0; i < model.size(); i++) {
final ClassDescription cd = model.get(i);
if (!baseClasses.contains(cd.getObjectClass())) {
baseClasses.add(cd.getObjectClass());
}
final PropertyInfo[] properties = cd.getProperties();
for (int p = 0; p < properties.length; p++) {
// filter primitive types ... they cannot form a generalization
// relation
if (!properties[p].getPropertyType().equals(PropertyType.ELEMENT)) {
continue;
}
final Class type = properties[p].getType();
if (baseClasses.contains(type)) {
continue;
}
// filter final classes, they too cannot have derived classes
if (Modifier.isFinal(type.getModifiers())) {
continue;
}
baseClasses.add(type);
}
}
return (Class[]) baseClasses.toArray(new Class[baseClasses.size()]);
}
/**
* Fills the super class for all object descriptions of the model. The
* super class is only filled, if the object's super class is contained
* in the model.
*
* @param model the model which should get its superclasses updated.
*/
private void fillSuperClasses(final DescriptionModel model) {
// Fill superclasses
for (int i = 0; i < model.size(); i++) {
final ClassDescription cd = model.get(i);
final Class parent = cd.getObjectClass().getSuperclass();
if (parent == null) {
continue;
}
final ClassDescription superCD = model.get(parent);
if (superCD != null) {
cd.setSuperClass(superCD.getObjectClass());
}
}
}
/**
* Updates the model to contain the given classes.
*
* @param classes a list of classes which should be part of the model.
* @param model the model which is updated
*
* @return A list of super classes which should also be contained in the model.
*/
private Class[] fillModel(final Class[] classes, final DescriptionModel model) {
// first check all direct matches from the source collector.
// but make sure that we also detect external superclasses -
// we have to get all properties ...
final ArrayList superClasses = new ArrayList();
for (int i = 0; i < classes.length; i++) {
Class superClass = classes[i].getSuperclass();
if (superClass != null) {
if (!Object.class.equals(superClass)
&& !contains(classes, superClass)
&& !superClasses.contains(superClass)) {
superClasses.add(superClass);
}
}
else {
superClass = Object.class;
}
try {
final BeanInfo bi = Introspector.getBeanInfo(classes[i], superClass);
final ClassDescription parent = model.get(classes[i]);
final ClassDescription cd = createClassDescription(bi, parent);
if (cd != null) {
model.addClassDescription(cd);
}
}
catch (IntrospectionException ie) {
// swallowed....
}
}
return (Class[]) superClasses.toArray(new Class[0]);
}
/**
* Creates a {@link ClassDescription} object for the specified bean info.
*
* @param beanInfo the bean info.
* @param parent the parent class description.
*
* @return The class description.
*/
private ClassDescription createClassDescription (final BeanInfo beanInfo, final ClassDescription parent) {
final PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
final ArrayList properties = new ArrayList();
for (int i = 0; i < props.length; i++) {
final PropertyDescriptor propertyDescriptor = props[i];
PropertyInfo pi;
if (parent != null) {
pi = parent.getProperty(propertyDescriptor.getName());
if (pi != null) {
// Property already found, don't touch it
// Log.info (new Log.SimpleMessage
// ("Ignore predefined property: ", propertyDescriptor.getName()));
properties.add(pi);
continue;
}
}
if (props[i] instanceof IndexedPropertyDescriptor) {
// this would handle lists and array access. We don't support
// this in the direct approach. We will need some cheating:
//
//
//
//
// pi = createIndexedPropertyInfo((IndexedPropertyDescriptor) props[i]);
}
else {
pi = createSimplePropertyInfo(props[i]);
if (pi != null) {
properties.add(pi);
}
}
}
final PropertyInfo[] propArray = (PropertyInfo[])
properties.toArray(new PropertyInfo[properties.size()]);
final ClassDescription cd;
if (parent != null) {
cd = parent;
}
else {
cd = new ClassDescription(beanInfo.getBeanDescriptor().getBeanClass());
cd.setDescription(beanInfo.getBeanDescriptor().getShortDescription());
}
cd.setProperties(propArray);
return cd;
}
/**
* Checks, whether the given method can be called from the generic object factory.
*
* @param method the method descriptor
* @return true, if the method is not null and public, false otherwise.
*/
public static boolean isValidMethod(final Method method) {
if (method == null) {
return false;
}
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
return true;
}
/**
* Creates a {@link PropertyInfo} object from a {@link PropertyDescriptor}.
*
* @param pd the property descriptor.
*
* @return the property info (null
possible).
*/
public PropertyInfo createSimplePropertyInfo(final PropertyDescriptor pd) {
final boolean readMethod = isValidMethod(pd.getReadMethod());
final boolean writeMethod = isValidMethod(pd.getWriteMethod());
if (!writeMethod || !readMethod) {
// a property is useless for our purposes without having a read or write method.
return null;
}
final PropertyInfo pi = new PropertyInfo(pd.getName(), pd.getPropertyType());
pi.setConstrained(pd.isConstrained());
pi.setDescription(pd.getShortDescription());
pi.setNullable(true);
pi.setPreserve(false);
pi.setReadMethodAvailable(readMethod);
pi.setWriteMethodAvailable(writeMethod);
pi.setXmlName(pd.getName());
if (isAttributeProperty(pd.getPropertyType())) {
pi.setPropertyType(PropertyType.ATTRIBUTE);
pi.setXmlHandler(getHandlerClass(pd.getPropertyType()));
}
else {
pi.setPropertyType(PropertyType.ELEMENT);
}
return pi;
}
/**
* Checks, whether the given class can be handled as attribute.
* All primitive types can be attributes as well as all types which have
* a custom attribute handler defined.
*
* @param c the class which should be checked
* @return true, if the class can be handled as attribute, false otherwise.
*/
private boolean isAttributeProperty(final Class c) {
if (BasicTypeSupport.isBasicDataType(c)) {
return true;
}
return this.handlerMapping.containsKey(c.getName());
}
/**
* Returns the class name for the attribute handler for a property of the specified class.
*
* @param c the class for which to search an attribute handler
* @return the handler class or null, if this class cannot be handled
* as attribute.
*/
private String getHandlerClass(final Class c) {
if (BasicTypeSupport.isBasicDataType(c)) {
final String handler = BasicTypeSupport.getHandlerClass(c);
if (handler != null) {
return handler;
}
}
return this.handlerMapping.getProperty(c.getName());
}
/**
* Checks, whether the class c
is contained in the given
* class array.
*
* @param cAll the list of all classes
* @param c the class to be searched
* @return true, if the class is contained in the array, false otherwise.
*/
private boolean contains(final Class[] cAll, final Class c) {
for (int i = 0; i < cAll.length; i++) {
if (cAll[i].equals(c)) {
return true;
}
}
return false;
}
// private PropertyInfo createIndexedPropertyInfo(IndexedPropertyDescriptor prop)
// {
//
// MethodInfo readMethod = createMethodInfo(prop.getIndexedReadMethod());
// MethodInfo writeMethod = createMethodInfo(prop.getIndexedWriteMethod());
// if (writeMethod == null)
// {
// return null;
// }
// IndexedPropertyInfo pi = new IndexedPropertyInfo(prop.getName());
// pi.setConstrained(prop.isConstrained());
// pi.setDescription(prop.getShortDescription());
// pi.setNullable(true);
// pi.setPreserve(false);
// pi.setType(prop.getIndexedPropertyType());
// pi.setReadMethod(readMethod);
// pi.setWriteMethod(writeMethod);
//
// TypeInfo keyInfo = new TypeInfo("index");
// keyInfo.setType(Integer.TYPE);
// keyInfo.setNullable(false);
// keyInfo.setConstrained(true); // throws indexoutofboundsexception
// keyInfo.setDescription("Generic index value");
// KeyDescription kd = new KeyDescription(new TypeInfo[]{keyInfo});
// pi.setKey(kd);
// return pi;
// }
}