package org.terasology.entitySystem.metadata;
import com.google.common.collect.Maps;
import org.terasology.entitySystem.Component;
import org.terasology.entitySystem.metadata.core.*;
import java.lang.reflect.*;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Immortius <immortius@gmail.com>
*/
public final class ComponentLibraryImpl implements ComponentLibrary {
private static final int MAX_SERIALIZATION_DEPTH = 1;
private Logger logger = Logger.getLogger(getClass().getName());
private Map<Class<? extends Component>, ComponentMetadata> componentSerializationLookup = Maps.newHashMap();
private Map<String, Class<? extends Component>> componentTypeLookup = Maps.newHashMap();
private Map<Class<?>, TypeHandler<?>> typeHandlers = Maps.newHashMap();
public ComponentLibraryImpl() {
registerTypeHandler(Boolean.class, new BooleanTypeHandler());
registerTypeHandler(Boolean.TYPE, new BooleanTypeHandler());
registerTypeHandler(Byte.class, new ByteTypeHandler());
registerTypeHandler(Byte.TYPE, new ByteTypeHandler());
registerTypeHandler(Double.class, new DoubleTypeHandler());
registerTypeHandler(Double.TYPE, new DoubleTypeHandler());
registerTypeHandler(Float.class, new FloatTypeHandler());
registerTypeHandler(Float.TYPE, new FloatTypeHandler());
registerTypeHandler(Integer.class, new IntTypeHandler());
registerTypeHandler(Integer.TYPE, new IntTypeHandler());
registerTypeHandler(Long.class, new LongTypeHandler());
registerTypeHandler(Long.TYPE, new LongTypeHandler());
registerTypeHandler(String.class, new StringTypeHandler());
registerTypeHandler(Number.class, new NumberTypeHandler());
}
public <T> void registerTypeHandler(Class<? extends T> forClass, TypeHandler<T> handler) {
typeHandlers.put(forClass, handler);
}
public <T extends Component> void registerComponentClass(Class<T> componentClass) {
try {
// Check if constructor exists
componentClass.getConstructor();
} catch (NoSuchMethodException e) {
logger.log(Level.SEVERE, String.format("Unable to register component class %s: Default Constructor Required", componentClass.getSimpleName()));
return;
}
ComponentMetadata<T> info = new ComponentMetadata<T>(componentClass);
for (Field field : componentClass.getDeclaredFields()) {
if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers()))
continue;
field.setAccessible(true);
TypeHandler typeHandler = getHandlerFor(field.getGenericType(), 0);
if (typeHandler == null) {
logger.log(Level.SEVERE, "Unsupported field type in component type " + componentClass.getSimpleName() + ", " + field.getName() + " : " + field.getGenericType());
} else {
info.addField(new FieldMetadata(field, componentClass, typeHandler));
}
}
componentSerializationLookup.put(componentClass, info);
componentTypeLookup.put(ComponentUtil.getComponentClassName(componentClass).toLowerCase(Locale.ENGLISH), componentClass);
}
public <T extends Component> ComponentMetadata<T> getMetadata(Class<T> componentClass) {
return componentSerializationLookup.get(componentClass);
}
@Override
public <T extends Component> ComponentMetadata<? extends T> getMetadata(T component) {
if (component != null) {
return componentSerializationLookup.get(component.getClass());
}
return null;
}
@Override
public <T extends Component> T copy(T component) {
ComponentMetadata info = getMetadata(component);
if (info != null) {
return (T) info.clone(component);
}
return null;
}
public ComponentMetadata<?> getMetadata(String componentName) {
return getMetadata(componentTypeLookup.get(componentName.toLowerCase(Locale.ENGLISH)));
}
public Iterator<ComponentMetadata> iterator() {
return componentSerializationLookup.values().iterator();
}
// TODO: Refactor
private TypeHandler getHandlerFor(Type type, int depth) {
Class typeClass;
if (type instanceof Class) {
typeClass = (Class) type;
} else if (type instanceof ParameterizedType) {
typeClass = (Class) ((ParameterizedType) type).getRawType();
} else {
logger.log(Level.SEVERE, "Cannot obtain class for type " + type);
return null;
}
if (Enum.class.isAssignableFrom(typeClass)) {
return new EnumTypeHandler(typeClass);
}
// For lists, createEntityRef the handler for the contained type and wrap in a list type handler
else if (List.class.isAssignableFrom(typeClass)) {
// TODO - Improve parameter lookup
if (type instanceof ParameterizedType && ((ParameterizedType) type).getActualTypeArguments().length > 0) {
TypeHandler innerHandler = getHandlerFor(((ParameterizedType) type).getActualTypeArguments()[0], depth);
if (innerHandler != null) {
return new ListTypeHandler(innerHandler);
}
}
logger.log(Level.SEVERE, "List field is not parameterized, or holds unsupported type");
return null;
}
// For Maps, createEntityRef the handler for the value type (and maybe key too?)
else if (Map.class.isAssignableFrom(typeClass)) {
if (type instanceof ParameterizedType) {
// TODO - Improve parameter lookup
Type[] types = ((ParameterizedType) type).getActualTypeArguments();
if (types.length > 1 && String.class.equals(types[0])) {
TypeHandler valueHandler = getHandlerFor(types[1], depth);
if (valueHandler != null) {
return new StringMapTypeHandler(valueHandler);
}
}
}
logger.log(Level.SEVERE, "Map field is not parameterized, does not have a String key, or holds unsupported values");
}
// For know types, just use the handler
else if (typeHandlers.containsKey(typeClass)) {
return typeHandlers.get(typeClass);
}
// For unknown types of a limited depth, assume they are data holders and use them
else if (depth <= MAX_SERIALIZATION_DEPTH && !Modifier.isAbstract(typeClass.getModifiers()) && !typeClass.isLocalClass() && !(typeClass.isMemberClass() && !Modifier.isStatic(typeClass.getModifiers()))) {
try {
// Check if constructor exists
Constructor constructor = typeClass.getConstructor();
} catch (NoSuchMethodException e) {
logger.log(Level.SEVERE, String.format("Unable to register field of type %s: no publicly accessible default constructor", typeClass.getSimpleName()));
return null;
}
logger.log(Level.WARNING, "Handling serialization of type " + typeClass + " via MappedContainer");
MappedContainerTypeHandler mappedHandler = new MappedContainerTypeHandler(typeClass);
for (Field field : typeClass.getDeclaredFields()) {
if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers()))
continue;
field.setAccessible(true);
TypeHandler handler = getHandlerFor(field.getGenericType(), depth + 1);
if (handler == null) {
logger.log(Level.SEVERE, "Unsupported field type in component type " + typeClass.getSimpleName() + ", " + field.getName() + " : " + field.getGenericType());
} else {
mappedHandler.addField(new FieldMetadata(field, typeClass, handler));
}
}
return mappedHandler;
}
return null;
}
}