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; } }