/*
* 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.binding;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.etk.model.api.annotations.Entity;
import org.etk.model.api.annotations.Property;
import org.etk.model.plugins.entity.EntityInfo;
import org.etk.model.plugins.entity.EntityInfoBuilder;
import org.etk.model.plugins.entity.PropertyInfo;
import org.etk.model.plugins.entity.SimpleValueInfo;
import org.etk.model.plugins.entity.type.SimpleTypeBinding;
import org.etk.model.plugins.entity.type.SimpleTypeResolver;
import org.etk.model.plugins.json.PropertyDefinitionMapping;
import org.etk.model.plugins.vt2.PropertyMetaType;
import org.etk.orm.api.annotations.DefaultValue;
import org.etk.orm.api.annotations.NamingPrefix;
import org.etk.orm.api.annotations.Properties;
import org.etk.orm.plugins.bean.BeanValueInfo;
import org.etk.orm.plugins.bean.ValueInfo;
import org.etk.orm.plugins.bean.ValueKind;
import org.etk.orm.plugins.bean.mapping.InvalidMappingException;
import org.etk.reflect.api.ClassTypeInfo;
import org.etk.reflect.api.TypeInfo;
import org.etk.reflect.api.TypeResolver;
import org.etk.reflect.api.introspection.MethodIntrospector;
import org.etk.reflect.api.visit.HierarchyScope;
import org.etk.reflect.core.TypeResolverImpl;
import org.etk.reflect.jlr.metadata.JLReflectionMetadata;
/**
* Created by The eXo Platform SAS
* Author : eXoPlatform
* exo@exoplatform.com
* Jul 14, 2011
*/
public class EntityBindingBuilder {
/** Used for receiving {@code java.lang.Object} */
private final TypeResolver<Type> domain = TypeResolverImpl.create(JLReflectionMetadata.newInstance());
private final SimpleTypeResolver simpleTypeResolver;
public EntityBindingBuilder() {
this(new SimpleTypeResolver());
}
public EntityBindingBuilder(SimpleTypeResolver simpleTypeResolver) {
this.simpleTypeResolver = simpleTypeResolver;
}
public Map<ClassTypeInfo, EntityBinding> build(Set<ClassTypeInfo> classTypes) {
// Clone for modification
classTypes = new HashSet<ClassTypeInfo>(classTypes);
Map<ClassTypeInfo, EntityInfo> entityMap = new EntityInfoBuilder(simpleTypeResolver).build(classTypes);
// Create context to resolve the EntityMapping
Context ctx = new Context(new SimpleTypeResolver(), new HashSet<EntityInfo>(entityMap.values()));
// Build mappings
Map<EntityInfo, EntityBinding> beanMappings = ctx.build();
//
Map<ClassTypeInfo, EntityBinding> classTypeBindings = new HashMap<ClassTypeInfo, EntityBinding>();
for (Map.Entry<EntityInfo, EntityBinding> beanBinding : beanMappings.entrySet()) {
classTypeBindings.put(beanBinding.getKey().getClassType(), beanBinding.getValue());
}
//
return classTypeBindings;
}
private class Context {
/** . */
final SimpleTypeResolver typeResolver;
/** . */
final Map<ClassTypeInfo, EntityInfo> beanClassTypeMap;
/** . */
final Set<EntityInfo> entityList;
/** . */
final Map<EntityInfo, EntityBinding> entityMappings;
private Context(SimpleTypeResolver typeResolver, Set<EntityInfo> entityList) {
//
Map<ClassTypeInfo, EntityInfo> beanClassTypeMap = new HashMap<ClassTypeInfo, EntityInfo>();
for (EntityInfo entity : entityList) {
beanClassTypeMap.put(entity.getClassType(), entity);
}
//
this.typeResolver = typeResolver;
this.beanClassTypeMap = beanClassTypeMap;
this.entityList = entityList;
this.entityMappings = new HashMap<EntityInfo, EntityBinding>();
}
public Map<EntityInfo, EntityBinding> build() {
while (true) {
Iterator<EntityInfo> iterator = entityList.iterator();
if (iterator.hasNext()) {
EntityInfo entityInfo = iterator.next();
resolve(entityInfo);
} else {
return entityMappings;
}
}
}
/**
* Executes to resolve each EntityInfo and return the EntityBinding.
*
* @param entityInfo
* @return
*/
private EntityBinding resolve(EntityInfo entityInfo) {
EntityBinding mapping = entityMappings.get(entityInfo);
if (mapping == null) {
//removes the entityInfo in entityList
if (entityList.remove(entityInfo)) {
//create the EntityMapping here
mapping = create(entityInfo);
entityMappings.put(entityInfo, mapping);
build(mapping);
} else {
// It does not resolve
}
}
return mapping;
}
/**
* Resolves the parent of current ClassTypeInfo
* @param entityInfo
* @return
*/
private EntityBinding create(EntityInfo entityInfo) {
Collection<? extends Annotation> annotations = entityInfo.getAnnotations(Entity.class);
if (annotations.size() != 1) {
throw new InvalidMappingException(entityInfo.getClassType(), "Class is not annotated with an Entity type");
}
//
NamingPrefix namingPrefix = entityInfo.getAnnotation(NamingPrefix.class);
String prefix = null;
if (namingPrefix != null) {
prefix = namingPrefix.value();
}
Annotation mappingAnnotation = annotations.iterator().next();
Entity typeAnnotation = (Entity)mappingAnnotation;
EntityTypeKind entityTypeKind = EntityTypeKind.ENTITY;
String entityTypeName = typeAnnotation.name();
boolean abstract_ = typeAnnotation.abstract_();
return new EntityBinding(entityInfo, entityTypeKind, entityTypeName, abstract_, prefix);
}
private void build(EntityBinding entityMapping) {
EntityInfo entityInfo = entityMapping.getEntity();
// First build the parent mapping if any
if (entityInfo.getParent() != null) {
entityMapping.parent = resolve(entityInfo.getParent());
}
//
Map<String, PropertyBinding<?, ?, ?>> properties = new HashMap<String, PropertyBinding<?, ?, ?>>();
for (PropertyInfo<?, ?> property : entityInfo.getProperties().values()) {
// Determine kind
Collection<? extends Annotation> annotations = property.getAnnotations(Property.class);
//
if (annotations.size() > 1) {
throw new InvalidMappingException(entityInfo.getClassType(), "The property " + property
+ " declares too many annotations " + annotations);
}
// Build the correct mapping or fail
PropertyBinding<?, ?, ?> mapping = null;
if (annotations.size() == 1) {
Annotation annotation = annotations.iterator().next();
ValueInfo value = property.getValue();
if (property.getValueKind() == ValueKind.SINGLE) {
if (value instanceof SimpleValueInfo<?>) {
SimpleValueInfo<?> simpleValue = (SimpleValueInfo<?>) value;
if (annotation instanceof Property) {
Property propertyAnnotation = (Property) annotation;
if (simpleValue.getValueKind() instanceof ValueKind.Single) {
PropertyInfo<SimpleValueInfo<ValueKind.Single>, ValueKind.Single> propertyInfo = (PropertyInfo<SimpleValueInfo<ValueKind.Single>, ValueKind.Single>) property;
mapping = createValueMapping(propertyAnnotation, propertyInfo);
} else {
PropertyInfo<SimpleValueInfo<ValueKind.Multi>, ValueKind.Single> a = (PropertyInfo<SimpleValueInfo<ValueKind.Multi>, ValueKind.Single>) property;
mapping = createValueMapping(propertyAnnotation, a);
}
} else {
throw new InvalidMappingException(entityInfo.getClassType(), "The property "
+ property + " is not annotated");
}
} else {
throw new AssertionError();
}
} else if (property.getValueKind() instanceof ValueKind.Multi) {
if (value instanceof SimpleValueInfo) {
SimpleValueInfo<?> simpleValue = (SimpleValueInfo<?>) value;
if (annotation instanceof Property) {
Property propertyAnnotation = (Property) annotation;
if (simpleValue.getValueKind() instanceof ValueKind.Single) {
PropertyInfo<SimpleValueInfo<ValueKind.Single>, ValueKind.Single> a = (PropertyInfo<SimpleValueInfo<ValueKind.Single>, ValueKind.Single>) property;
mapping = createValueMapping(propertyAnnotation, a);
} else {
PropertyInfo<SimpleValueInfo<ValueKind.Multi>, ValueKind.Single> a = (PropertyInfo<SimpleValueInfo<ValueKind.Multi>, ValueKind.Single>) property;
mapping = createValueMapping(propertyAnnotation, a);
}
} else if (annotation instanceof Properties) {
mapping = createProperties((PropertyInfo<?, ValueKind.Map>) property);
} else {
throw new InvalidMappingException(entityInfo.getClassType(), "Annotation "
+ annotation + " is forbidden " + " on property " + property);
}
} else if (value instanceof BeanValueInfo) {
if (annotation instanceof Properties) {
mapping = createProperties((PropertyInfo<?, ValueKind.Map>) property);
} else {
throw new InvalidMappingException(entityInfo.getClassType(), "Annotation "
+ annotation + " is forbidden " + " on property " + property);
}
} else {
throw new AssertionError();
}
} else {
throw new AssertionError();
}
}
//
if (mapping != null) {
// Resolve parent property without any check for now
PropertyInfo parentProperty = property.getParent();
if (parentProperty != null) {
EntityInfo ancestor = parentProperty.getOwner();
EntityBinding ancestorMapping = resolve(ancestor);
mapping.parent = ancestorMapping.properties.get(parentProperty.getName());
}
//
properties.put(mapping.property.getName(), mapping);
}
}
// Wire
entityMapping.properties.putAll(properties);
for (PropertyBinding<?, ?, ?> propertyMapping : entityMapping.properties.values()) {
propertyMapping.owner = entityMapping;
}
// Take care of methods
MethodIntrospector introspector = new MethodIntrospector(HierarchyScope.ALL);
Set<MethodBinding> methodMappings = new HashSet<MethodBinding>();
//
entityMapping.methods.addAll(methodMappings);
}
private <K extends ValueKind> ValueBinding<K> createValueMapping(Property propertyAnnotation,
PropertyInfo<SimpleValueInfo<K>, ValueKind.Single> property) {
//
PropertyMetaType<?> propertyMetaType = PropertyMetaType.get(propertyAnnotation.type());
//
SimpleTypeBinding resolved = typeResolver.resolveType(property.getValue().getDeclaredType(),
propertyMetaType);
if (resolved == null) {
throw new InvalidMappingException(property.getOwner().getClassType(),
"No simple type mapping "
+ property.getValue().getDeclaredType()
+ " for property " + property);
}
//
List<String> defaultValueList = null;
DefaultValue defaultValueAnnotation = property.getAnnotation(DefaultValue.class);
if (defaultValueAnnotation != null) {
String[] defaultValues = defaultValueAnnotation.value();
defaultValueList = new ArrayList<String>(defaultValues.length);
defaultValueList.addAll(Arrays.asList(defaultValues));
defaultValueList = Collections.unmodifiableList(defaultValueList);
}
//
PropertyDefinitionMapping<?> propertyDefinition = new PropertyDefinitionMapping(propertyAnnotation.name(),
resolved.getPropertyMetaType(),
defaultValueList,
false);
//
return new ValueBinding<K>(property, propertyDefinition);
}
private <V extends ValueInfo> PropertiesBinding<V> createProperties(PropertyInfo<V, ValueKind.Map> property) {
if (property.getValueKind() != ValueKind.MAP) {
throw new InvalidMappingException(property.getOwner().getClassType(), "The @Properties " + property +
" must be of type java.util.Map instead of " + property.getValue().getEffectiveType());
}
TypeInfo type = property.getValue().getEffectiveType();
//
PropertyMetaType<?> mt = null;
ValueKind valueKind;
ValueInfo vi = property.getValue();
if (vi instanceof SimpleValueInfo<?>) {
SimpleValueInfo<?> svi = (SimpleValueInfo<?>)vi;
if (svi.getTypeMapping() != null) {
mt = svi.getTypeMapping().getPropertyMetaType();
}
valueKind = svi.getValueKind();
} else {
if (type.getName().equals(Object.class.getName())) {
mt = null;
}
valueKind = ValueKind.SINGLE;
}
//
String prefix = null;
NamingPrefix namingPrefix = property.getAnnotation(NamingPrefix.class);
if (namingPrefix != null) {
prefix = namingPrefix.value();
}
//
return new PropertiesBinding<V>(property, prefix, mt, valueKind);
}
}
}