/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.commons.serialization.model;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import org.exoplatform.commons.serialization.api.TypeConverter;
import org.exoplatform.commons.serialization.api.annotations.Converted;
import org.exoplatform.commons.serialization.api.annotations.Serialized;
import org.exoplatform.commons.serialization.model.metadata.ClassTypeMetaData;
import org.exoplatform.commons.serialization.model.metadata.ConvertedTypeMetaData;
import org.exoplatform.commons.serialization.model.metadata.DomainMetaData;
import org.exoplatform.commons.serialization.model.metadata.TypeMetaData;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
class TypeModelBuilder {
/** . */
private static final Logger log = LoggerFactory.getLogger(TypeModelBuilder.class);
/** . */
private static final Map<Class<?>, Class<?>> primitiveToWrapperMap = new HashMap<Class<?>, Class<?>>();
static {
primitiveToWrapperMap.put(byte.class, Byte.class);
primitiveToWrapperMap.put(short.class, Short.class);
primitiveToWrapperMap.put(int.class, Integer.class);
primitiveToWrapperMap.put(long.class, Long.class);
primitiveToWrapperMap.put(float.class, Float.class);
primitiveToWrapperMap.put(double.class, Double.class);
primitiveToWrapperMap.put(boolean.class, Boolean.class);
primitiveToWrapperMap.put(char.class, Character.class);
}
// Build the missing types required to have knowledge about the
// provided java type
private final Map<String, TypeModel<?>> addedTypeModels = new HashMap<String, TypeModel<?>>();
/** . */
private final DomainMetaData metaData;
/** . */
private final Map<String, TypeModel<?>> existingTypeModels;
public TypeModelBuilder(DomainMetaData metaData, Map<String, TypeModel<?>> existingTypeModels) {
this.metaData = metaData;
this.existingTypeModels = existingTypeModels;
}
Map<String, TypeModel<?>> getAddedTypeModels() {
return addedTypeModels;
}
<O> TypeModel<O> build(Class<O> javaType) {
if (javaType.isPrimitive()) {
throw new IllegalArgumentException("No primitive type accepted");
}
//
TypeModel<O> typeModel = get(javaType);
//
if (typeModel != null) {
log.debug("Found type model " + typeModel + " for java type " + javaType);
return typeModel;
}
//
log.debug("About to build type model for java type " + javaType);
TypeMetaData typeMetaData = metaData.getTypeMetaData(javaType);
//
if (typeMetaData == null) {
log.debug("No meta data found for java type " + javaType + " about to build it");
boolean serialized = javaType.getAnnotation(Serialized.class) != null;
Converted converted = javaType.getAnnotation(Converted.class);
if (serialized) {
if (converted != null) {
throw new TypeException();
}
typeMetaData = new ClassTypeMetaData(javaType.getName(), true);
} else if (converted != null) {
typeMetaData = new ConvertedTypeMetaData(javaType.getName(), converted.value());
} else {
typeMetaData = new ClassTypeMetaData(javaType.getName(), false);
}
}
log.debug("Built type meta data " + typeMetaData + " for java type " + javaType);
//
return build(javaType, typeMetaData);
}
private <O> TypeModel<O> build(Class<O> javaType, TypeMetaData typeMetaData) {
if (typeMetaData instanceof ClassTypeMetaData) {
return buildClassType(javaType, (ClassTypeMetaData) typeMetaData);
} else {
return buildConvertedType(javaType, (ConvertedTypeMetaData) typeMetaData);
}
}
private <O> ConvertedTypeModel<O, ?> buildConvertedType(Class<O> javaType, ConvertedTypeMetaData typeMetaData) {
log.debug("About to build type model from type type metadata " + typeMetaData);
//
Class<? extends TypeConverter<?, ?>> converterClass = typeMetaData.getConverterClass();
ParameterizedType converterParameterizedType = (ParameterizedType) converterClass.getGenericSuperclass();
if (!converterParameterizedType.getActualTypeArguments()[0].equals(javaType)) {
throw new TypeException("The declared type parameter in the converter " + converterClass.getName()
+ " does not match the type it is related to " + javaType.getName());
}
//
Class<? extends TypeConverter<O, ?>> converterJavaType = (Class<TypeConverter<O, ?>>) converterClass;
//
return buildConvertedType(javaType, converterJavaType);
}
private <O, T> ConvertedTypeModel<O, T> buildConvertedType(Class<O> javaType,
Class<? extends TypeConverter<O, ? /* This is a bit funky and nasty, need to investigate */>> converterJavaType) {
Class<T> outputClass = (Class<T>) ((ParameterizedType) converterJavaType.getGenericSuperclass())
.getActualTypeArguments()[1];
//
ClassTypeModel<T> targetType = (ClassTypeModel<T>) build(outputClass);
//
TypeModel<? super O> superType = null;
Class<? super O> superJavaType = javaType.getSuperclass();
if (superJavaType != null) {
superType = build(superJavaType);
}
//
ConvertedTypeModel<O, T> typeModel = new ConvertedTypeModel<O, T>(javaType, superType, targetType,
(Class<TypeConverter<O, T>>) converterJavaType);
//
addedTypeModels.put(typeModel.getName(), typeModel);
//
return typeModel;
}
private <O> ClassTypeModel<O> buildClassType(Class<O> javaType, ClassTypeMetaData typeMetaData) {
ClassTypeModel<? super O> superTypeModel = null;
if (javaType.getSuperclass() != null) {
TypeModel<? super O> builtType = build(javaType.getSuperclass());
if (builtType instanceof ClassTypeModel) {
superTypeModel = (ClassTypeModel<? super O>) builtType;
} else {
throw new TypeException();
}
}
//
TreeMap<String, FieldModel<O, ?>> fieldModels = new TreeMap<String, FieldModel<O, ?>>();
//
SerializationMode serializationMode;
if (typeMetaData.isSerialized()) {
serializationMode = SerializationMode.SERIALIZED;
} else if (Serializable.class.isAssignableFrom(javaType)) {
serializationMode = SerializationMode.SERIALIZABLE;
} else {
serializationMode = SerializationMode.NONE;
}
//
ClassTypeModel<O> typeModel = new ClassTypeModel<O>(javaType, superTypeModel, fieldModels, serializationMode);
//
addedTypeModels.put(javaType.getName(), typeModel);
// Now build fields
for (Field field : javaType.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
Class<?> fieldJavaType = field.getType();
// Replace if a primitive
if (fieldJavaType.isPrimitive()) {
fieldJavaType = primitiveToWrapperMap.get(fieldJavaType);
}
TypeModel<?> fieldTypeModel = build(fieldJavaType);
if (fieldTypeModel != null) {
fieldModels.put(field.getName(), createField(typeModel, field, fieldTypeModel));
}
}
}
//
return typeModel;
}
private <O, V> FieldModel<O, V> createField(TypeModel<O> owner, Field field, TypeModel<V> fieldTypeModel) {
return new FieldModel<O, V>(owner, field, fieldTypeModel);
}
private <O> TypeModel<O> get(Class<O> javaType) {
TypeModel<?> typeModel = existingTypeModels.get(javaType.getName());
if (typeModel == null) {
typeModel = addedTypeModels.get(javaType.getName());
}
// Cast OK
return (TypeModel<O>) typeModel;
}
}