/* * Copyright (C) 2003-2011 eXo Platform SAS. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.etk.model.plugins.entity; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.etk.model.plugins.entity.type.SimpleTypeBinding; import org.etk.model.plugins.entity.type.SimpleTypeResolver; import org.etk.orm.plugins.bean.ValueKind; import org.etk.reflect.api.ArrayTypeInfo; import org.etk.reflect.api.ClassTypeInfo; import org.etk.reflect.api.MethodInfo; import org.etk.reflect.api.ParameterizedTypeInfo; import org.etk.reflect.api.SimpleTypeInfo; import org.etk.reflect.api.TypeInfo; import org.etk.reflect.api.TypeVariableInfo; import org.etk.reflect.api.VoidTypeInfo; import org.etk.reflect.api.definition.ClassKind; import org.etk.reflect.api.introspection.MethodIntrospector; import org.etk.reflect.api.visit.HierarchyVisitor; import org.etk.reflect.api.visit.HierarchyVisitorStrategy; /** * Created by The eXo Platform SAS * Author : eXoPlatform * exo@exoplatform.com * Jul 14, 2011 */ public class EntityInfoBuilder { private final SimpleTypeResolver simpleTypeResolver; public EntityInfoBuilder(SimpleTypeResolver simpleTypeResolver) { this.simpleTypeResolver = simpleTypeResolver; } public Map<ClassTypeInfo, EntityInfo> build(Set<ClassTypeInfo> classTypes) { Context ctx = new Context(classTypes); ctx.build(); return ctx.entityInfos; } private class Context { private class EntityHierarchyVisitorStrategy<V extends HierarchyVisitor<V>> extends HierarchyVisitorStrategy<V> { /** . */ private final ClassTypeInfo current; private EntityHierarchyVisitorStrategy(ClassTypeInfo current) { this.current = current; } @Override protected boolean accept(ClassTypeInfo type) { return type == current || !classTypes.contains(type); } } /** The types to build. */ private final Set<ClassTypeInfo> classTypes; /** The beans being built in a method call to the builder. */ private final Map<ClassTypeInfo, EntityInfo> entityInfos; private Context(Set<ClassTypeInfo> classTypes) { this.classTypes = classTypes; this.entityInfos = new HashMap<ClassTypeInfo, EntityInfo>(); } void build() { while (true) { Iterator<ClassTypeInfo> iterator = classTypes.iterator(); if (iterator.hasNext()) { ClassTypeInfo cti = iterator.next(); EntityInfo bean = resolve(cti); } else { break; } } } /** * Resolve the bean object from the specified class type. The returned bean * is in correct state. However it can trigger recursive resolving while the current * bean is in an incorrect state, i.e not finished to be fully constructed. * * To ensure the fact that we have a unique bean created per class type we must relax * the fact that objects are created in one step, i.e have: * * <ol> * <li>Instantiate bean</li> * <li>Make bean available for lookup</li> * <li>Terminate bean initialization</li> * </ol> * * Note: it could be possible to use future object to build the full state and leverage * multi processors. * * @param classType the bean class type * @return the corresponding bean instance */ EntityInfo resolve(ClassTypeInfo classType) { EntityInfo entity = entityInfos.get(classType); if (entity == null) { boolean accept; Boolean declared; if (classType.getKind() == ClassKind.CLASS || classType.getKind() == ClassKind.INTERFACE) { //isPrimitive Object LONG, DOUBLE, STRING, DATE ... if (classType instanceof SimpleTypeInfo) { accept = false; declared = null; // BeanMappingBuilder.println("rejected simple type " + classType.getName()); } else if (classType instanceof VoidTypeInfo) { accept = false; declared = null; // BeanMappingBuilder.println("rejected void " + classType.getName()); } else { if (classTypes.remove(classType)) { // log.debug("resolved declared " + classType.getName()); accept = true; declared = true; } else { accept = false; declared = null; // BeanMappingBuilder.println("rejected " + classType.getName()); } } } else { accept = false; declared = null; // BeanMappingBuilder.println("rejected non class or non interface " + classType.getName()); } if (accept) { entity = new EntityInfo(classType, declared); entityInfos.put(classType, entity); build(entity); } } return entity; } void build(EntityInfo entityInfo) { // Build parents for (ClassTypeInfo ancestorClassType = entityInfo.classType.getSuperClass(); ancestorClassType != null;ancestorClassType = ancestorClassType.getSuperClass()) { // Resolve the ancestor class type EntityInfo ancestorBean = resolve(ancestorClassType); // If the ancestor resolves as a bean then it becomes the parent bean and we are done if (ancestorBean != null) { entityInfo.parent = ancestorBean; break; } } // buildProperties(entityInfo); // Now resolve types references by method return type // this is needed for @Create for instance for (MethodInfo mi : entityInfo.classType.getDeclaredMethods()) { TypeInfo rti = mi.getReturnType(); if (rti instanceof ClassTypeInfo) { resolve((ClassTypeInfo)rti); } } } private PropertyInfo resolveProperty(EntityInfo bean, String propertyName) { // We may have null in case we were dealing with java.lang.Object for instance if (bean == null) { return null; } // if (bean.properties == null) { // Defensive: it means we are looking for a bean in an incorrect state throw new AssertionError(); } // PropertyInfo property = bean.properties.get(propertyName); // Try in the parent if (property == null) { property = resolveProperty(bean.parent, propertyName); } // return property; } class ToBuild { final TypeInfo type; final MethodInfo getter; final MethodInfo setter; ToBuild(TypeInfo type, MethodInfo getter, MethodInfo setter) { this.type = type; this.getter = getter; this.setter = setter; } } /** * Build properties of a bean. * * @param entityInfo the bean to build properties. */ private void buildProperties(EntityInfo entityInfo) { EntityHierarchyVisitorStrategy strategy = new EntityHierarchyVisitorStrategy(entityInfo.classType); MethodIntrospector introspector = new MethodIntrospector(strategy, true); Map<String, MethodInfo> getterMap = introspector.getGetterMap(entityInfo.classType); Map<String, Set<MethodInfo>> setterMap = introspector.getSetterMap(entityInfo.classType); // Gather all properties on the bean Map<String, ToBuild> toBuilds = new HashMap<String,ToBuild>(); for (Map.Entry<String, MethodInfo> getterEntry : getterMap.entrySet()) { String name = getterEntry.getKey(); MethodInfo getter = getterEntry.getValue(); TypeInfo getterTypeInfo = getter.getReturnType(); // ToBuild toBuild = null; Set<MethodInfo> setters = setterMap.get(name); if (setters != null) { for (MethodInfo setter : setters) { TypeInfo setterTypeInfo = setter.getParameterTypes().get(0); if (getterTypeInfo.equals(setterTypeInfo)) { toBuild = new ToBuild(getterTypeInfo, getter, setter); break; } } } // if (toBuild == null) { toBuild = new ToBuild(getterTypeInfo, getter, null); } // if (toBuild != null) { toBuilds.put(name, toBuild); } } // setterMap.keySet().removeAll(toBuilds.keySet()); for (Map.Entry<String, Set<MethodInfo>> setterEntry : setterMap.entrySet()) { String name = setterEntry.getKey(); for (MethodInfo setter : setterEntry.getValue()) { TypeInfo setterTypeInfo = setter.getParameterTypes().get(0); toBuilds.put(name, new ToBuild(setterTypeInfo, null, setter)); } } // Now we have all the info to build each property correctly Map<String, PropertyInfo<?, ?>> properties = new HashMap<String, PropertyInfo<?, ?>>(); for (Map.Entry<String, ToBuild> toBuildEntry : toBuilds.entrySet()) { // Get parent property if any PropertyInfo parentProperty = resolveProperty(entityInfo.parent, toBuildEntry.getKey()); // TypeInfo type = toBuildEntry.getValue().type; // First resolve as much as we can TypeInfo resolvedType = entityInfo.classType.resolve(type); // PropertyInfo<?, ?> property = null; // We could not resolve it, get the upper bound if (resolvedType instanceof TypeVariableInfo) { resolvedType = ((TypeVariableInfo)resolvedType).getBounds().get(0); resolvedType = entityInfo.classType.resolve(resolvedType); // is it really enough ? for now it should be OK but we should check } // Now let's analyse if (resolvedType instanceof ParameterizedTypeInfo) { ParameterizedTypeInfo parameterizedType = (ParameterizedTypeInfo) resolvedType; TypeInfo rawType = parameterizedType.getRawType(); if (rawType instanceof ClassTypeInfo) { ClassTypeInfo rawClassType = (ClassTypeInfo)rawType; String rawClassName = rawClassType.getName(); final ValueKind.Multi collectionKind; final TypeInfo elementType; if (rawClassName.equals("java.util.Collection")) { collectionKind = ValueKind.COLLECTION; elementType = parameterizedType.getTypeArguments().get(0); } else if (rawClassName.equals("java.util.List")) { collectionKind = ValueKind.LIST; elementType = parameterizedType.getTypeArguments().get(0); } else if (rawClassName.equals("java.util.Map")) { TypeInfo keyType = parameterizedType.getTypeArguments().get(0); TypeInfo resolvedKeyType = entityInfo.classType.resolve(keyType); if (resolvedKeyType instanceof ClassTypeInfo && resolvedKeyType.getName().equals("java.lang.String")) { elementType = parameterizedType.getTypeArguments().get(1); collectionKind = ValueKind.MAP; } else { elementType = null; collectionKind = null; } } else { elementType = null; collectionKind = null; } if (collectionKind != null) { if (elementType instanceof ParameterizedTypeInfo) { ParameterizedTypeInfo parameterizedElementType = (ParameterizedTypeInfo)elementType; TypeInfo parameterizedElementRawType = parameterizedElementType.getRawType(); if (parameterizedElementRawType instanceof ClassTypeInfo) { ClassTypeInfo parameterizedElementRawClassType = (ClassTypeInfo)parameterizedElementRawType; String parameterizedElementRawClassName = parameterizedElementRawClassType.getName(); if (parameterizedElementRawClassName.equals("java.util.List")) { TypeInfo listElementType = parameterizedElementType.getTypeArguments().get(0); property = new PropertyInfo<SimpleValueInfo, ValueKind.Multi>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, collectionKind, createSimpleValueInfo(entityInfo, listElementType, ValueKind.LIST)); } } } else { ClassTypeInfo elementClassType = entityInfo.resolveToClass(elementType); if (elementClassType != null) { EntityInfo relatedBean = resolve(elementClassType); if (relatedBean != null) { property = new PropertyInfo<EntityValueInfo, ValueKind.Multi>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, collectionKind, new EntityValueInfo(type, entityInfo.resolveToClass(elementType), relatedBean)); } else { if (collectionKind == ValueKind.LIST) { property = new PropertyInfo<SimpleValueInfo, ValueKind.Single>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, ValueKind.SINGLE, createSimpleValueInfo(entityInfo, elementType, collectionKind)); } else if (collectionKind == ValueKind.MAP) { property = new PropertyInfo<SimpleValueInfo, ValueKind.Map>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, ValueKind.MAP, createSimpleValueInfo(entityInfo, elementType, ValueKind.SINGLE)); } } } } } } } else if (resolvedType instanceof ArrayTypeInfo) { final TypeInfo componentType = ((ArrayTypeInfo)resolvedType).getComponentType(); if (componentType instanceof SimpleTypeInfo) { SimpleTypeInfo componentSimpleType = (SimpleTypeInfo)componentType; switch (componentSimpleType.getLiteralType()) { case BOOLEAN: case DOUBLE: case FLOAT: case LONG: case INT: property = new PropertyInfo<SimpleValueInfo, ValueKind.Single>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, ValueKind.SINGLE, createSimpleValueInfo(entityInfo, componentType, ValueKind.ARRAY)); break; default: break; } } else { property = new PropertyInfo<SimpleValueInfo, ValueKind.Single>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, ValueKind.SINGLE, createSimpleValueInfo(entityInfo, componentType, ValueKind.ARRAY)); } } else if (resolvedType instanceof ClassTypeInfo) { EntityInfo related = resolve((ClassTypeInfo)resolvedType); if (related != null) { property = new PropertyInfo<EntityValueInfo, ValueKind.Single>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, ValueKind.SINGLE, new EntityValueInfo(type, entityInfo.resolveToClass(type), related)); } } // Otherwise consider everything as a single valued simple value if (property == null) { property = new PropertyInfo<SimpleValueInfo, ValueKind.Single>( entityInfo, parentProperty, toBuildEntry.getKey(), toBuildEntry.getValue().getter, toBuildEntry.getValue().setter, ValueKind.SINGLE, createSimpleValueInfo(entityInfo, type, ValueKind.SINGLE)); } // properties.put(property.getName(), property); } // Update properties entityInfo.properties.putAll(properties); } private <K extends ValueKind> SimpleValueInfo createSimpleValueInfo(EntityInfo bean, TypeInfo type, K valueKind) { TypeInfo resolvedType = bean.getClassType().resolve(type); SimpleTypeBinding binding = simpleTypeResolver.resolveType(resolvedType); return new SimpleValueInfo<K>(type, resolvedType, binding, valueKind); } } }