Commits

Michael Ludwig committed 2e4dfaf

Improve PropertyFactory and ComponentDataFactory model to add support for customizable annotation attributes.

Comments (0)

Files changed (40)

src/main/java/com/lhkbob/entreri/Attributes.java

+package com.lhkbob.entreri;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.lhkbob.entreri.annot.Attribute;
+
+public class Attributes {
+    private final Map<Class<? extends Annotation>, Annotation> attrs;
+    
+    public Attributes(Field f) {
+        if (f == null)
+            throw new NullPointerException("Field cannot be null");
+        
+        attrs = new HashMap<Class<? extends Annotation>, Annotation>();
+        
+        for (Annotation a: f.getAnnotations()) {
+            if (a.annotationType().getAnnotation(Attribute.class) != null) {
+                // the attribute is an annotation
+                attrs.put(a.annotationType(), a);
+            }
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    public <T extends Annotation> T getAttribute(Class<T> cls) {
+        if (cls == null)
+            throw new NullPointerException("Annotation class cannot be null");
+        return (T) attrs.get(cls);
+    }
+    
+    public boolean hasAttribute(Class<? extends Annotation> cls) {
+        if (cls == null)
+            throw new NullPointerException("Annotation class cannot be null");
+        return attrs.containsKey(cls);
+    }
+    
+    public Collection<Annotation> getAttributes() {
+        return attrs.values();
+    }
+}

src/main/java/com/lhkbob/entreri/ComponentData.java

 package com.lhkbob.entreri;
 
 import com.lhkbob.entreri.annot.DefaultFactory;
-import com.lhkbob.entreri.property.ReflectionComponentDataFactory;
 
 /**
  * <p>

src/main/java/com/lhkbob/entreri/ComponentDataFactory.java

 
 import java.util.Map;
 
-import com.lhkbob.entreri.property.Property;
-import com.lhkbob.entreri.property.PropertyFactory;
-import com.lhkbob.entreri.property.ReflectionComponentDataFactory;
 
 /**
  * <p>

src/main/java/com/lhkbob/entreri/ComponentRepository.java

 import java.util.Map.Entry;
 
 import com.lhkbob.entreri.property.BooleanProperty;
-import com.lhkbob.entreri.property.CompactAwareProperty;
-import com.lhkbob.entreri.property.IndexedDataStore;
 import com.lhkbob.entreri.property.IntProperty;
-import com.lhkbob.entreri.property.Property;
-import com.lhkbob.entreri.property.PropertyFactory;
 
 /**
  * ComponentRepository manages storing all the componentDatas of a specific type for an
         return dst;
     }
     
-    private void notifyCompactAwareProperties(List<PropertyStore<?>> props) {
-        PropertyStore<?> p;
-        for (int i = 0; i < props.size(); i++) {
-            p = props.get(i);
-            if (p.property instanceof CompactAwareProperty)
-                ((CompactAwareProperty) p.property).onCompactComplete();
-        }
-    }
-
     /**
      * <p>
      * Compact the data of this ComponentRepository to account for removals and
         
         for (int i = 1; i < componentInsert; i++)
             entityIndexToComponentRepository[componentIndexToEntityIndex[i]] = i;
-        
-        notifyCompactAwareProperties(declaredProperties);
-        notifyCompactAwareProperties(decoratedProperties);
     }
 
     /**

src/main/java/com/lhkbob/entreri/Entity.java

 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
-import com.lhkbob.entreri.property.Property;
-import com.lhkbob.entreri.property.PropertyFactory;
 
 /**
  * <p>

src/main/java/com/lhkbob/entreri/EntitySystem.java

 import java.util.NoSuchElementException;
 
 import com.lhkbob.entreri.annot.DefaultFactory;
-import com.lhkbob.entreri.property.Property;
-import com.lhkbob.entreri.property.PropertyFactory;
-import com.lhkbob.entreri.property.ReflectionComponentDataFactory;
 
 /**
  * <p>

src/main/java/com/lhkbob/entreri/IndexedDataStore.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.lhkbob.entreri;
+
+/**
+ * IndexedDataStore is a generic data storage interface representing packed,
+ * random-access data storage for a property of a ComponentData. All Components of
+ * the same type in the same EntitySystem will have their properties share the
+ * IndexedDataStores so that iteration will have much better cache locality, and
+ * will avoid the reorganization caused by Java's garbage collector.
+ * 
+ * @author Michael Ludwig
+ */
+public interface IndexedDataStore {
+    /**
+     * Create a new data store with the same behavior as this IndexedDataStore
+     * except, that it holds enough space for <tt>size</tt> Properties.
+     * 
+     * @param size The size, in number of properties
+     */
+    public IndexedDataStore create(int size);
+    
+    /**
+     * @return The number of properties that can fit into this IndexedDataStore
+     */
+    public int size();
+    
+    /**
+     * @return Memory usage estimate of this data store
+     */
+    public long memory();
+
+    /**
+     * <p>
+     * Copy <tt>len</tt> property values starting at <tt>srcOffset</tt> from
+     * this IndexedDataStore into <tt>dest</tt>, placing the first property's
+     * values at <tt>destOffset</tt>. Both <tt>srcOffset</tt> and
+     * <tt>destOffset</tt> are in units of property, and not any underlying
+     * array.
+     * </p>
+     * <p>
+     * An exception should be thrown if the destination IndexedDataStore is not
+     * of the same type, or is not compatible with this IndexedDataStore.
+     * </p>
+     * 
+     * @param srcOffset The offset of the first property to copy
+     * @param len The number of properties to copy
+     * @param dest The destination IndexedDataStore
+     * @param destOffset The offset into dest to place the first property's
+     *            values
+     * @throws IndexOutOfBoundsException if the offsets or lengths cause
+     *             out-of-bounds exceptions
+     */
+    public void copy(int srcOffset, int len, IndexedDataStore dest, int destOffset);
+}

src/main/java/com/lhkbob/entreri/Property.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.lhkbob.entreri;
+
+import com.lhkbob.entreri.property.FloatProperty;
+
+/**
+ * <p>
+ * Property represents a generic field or property of a ComponentData
+ * definition. It's interface allows it's values and underlying data store to be
+ * packed together with the corresponding property instances of all the other
+ * Components of the same type in an EntitySystem.
+ * </p>
+ * <p>
+ * This is an approach to mapped-objects where Components can be mapped onto
+ * primitive arrays so that iteration sees optimal cache locality. As an
+ * example, there could be two instances of type A, with properties a and b. The
+ * two 'a' properties would share the same data store, and the two 'b'
+ * properties would share a separate store.
+ * </p>
+ * <p>
+ * Property instances are carefully managed by an EntitySystem. There is ever
+ * only one property instance per defined property in a component type for a
+ * system. Every component of that type uses their index into the property's
+ * IndexedDataStore to access their data. This helps keep memory usage low and
+ * simplifies system maintenance.
+ * </p>
+ * <p>
+ * Property instances are created by {@link PropertyFactory PropertyFactories},
+ * which are created by {@link ComponentDataFactory ComponentDataFactories}.
+ * ComponentDataFactory implementations define how property factories are
+ * declared.
+ * </p>
+ * 
+ * @see ReflectionComponentDataFactory
+ * @author Michael Ludwig
+ */
+public interface Property {
+    /**
+     * <p>
+     * Return the IndexedDataStore holding this property's values. The data
+     * store may also hold other property values if the owning ComponentData is in
+     * an EntitySystem with many other components of the same type.
+     * </p>
+     * <p>
+     * This should not be used by ComponentData implementations, and manipulating
+     * the IndexedDataStore outside of the EntitySystem code could cause
+     * unexpected behavior. Instead Property implementations should expose other
+     * ways to access their data; as an example see
+     * {@link FloatProperty#getIndexedData()}.
+     * </p>
+     * <p>
+     * The returned data store must always have at least 1 element in it.
+     * </p>
+     * 
+     * @return The current IndexedDataStore used by the property
+     */
+    public IndexedDataStore getDataStore();
+
+    /**
+     * <p>
+     * Assign a new IndexedDataStore to this Property. If the old values for
+     * this Property were not copied out of its previous IndexedDataStore into
+     * the new one, this assignment will change the apparent value of this
+     * property.
+     * </p>
+     * <p>
+     * This should only be called internally by the EntitySystem. Calling it
+     * within a ComponentData implementation or otherwise will result in undefined
+     * consequences.
+     * </p>
+     * <p>
+     * It can be assumed that the new store is not null.
+     * </p>
+     * 
+     * @param store The new IndexedDataStore
+     */
+    public void setDataStore(IndexedDataStore store);
+}

src/main/java/com/lhkbob/entreri/PropertyFactory.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.lhkbob.entreri;
+
+import com.lhkbob.entreri.annot.Factory;
+
+/**
+ * <p>
+ * A PropertyFactory is a simple factory that can be used to create Property
+ * instances. Additionally, it is used when decorating a ComponentData type in
+ * an EntitySystem to ensure that each decoration event gets a unique property
+ * instance.
+ * </p>
+ * <p>
+ * To be used with the {@link Factory} annotation when defining a ComponentData
+ * type, the PropertyFactory must have an accessible, no-argument constructor.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ * @param <T> The Property type created
+ */
+public interface PropertyFactory<T extends Property> {
+    /**
+     * Return a new Property instance. This must be a new instance that has not
+     * been returned previously or the entity framework will have undefined
+     * behavior.
+     * 
+     * @return A new Property of type T
+     */
+    public T create();
+
+    /**
+     * Set the default value that the component at the specified <tt>index</tt>
+     * will see before it's init() method is invoked. In some cases, this could
+     * be used in-place of initializing in init() method.
+     * 
+     * @param property The property whose value will be updated
+     * @param index The component index to be updated
+     */
+    public void setDefaultValue(T property, int index);
+
+    /**
+     * Copy the value from <tt>src</tt> at component index, <tt>srcIndex</tt> to
+     * <tt>dst</tt> at <tt>dstIndex</tt>. This is used when a component is
+     * created and cloned from a template with
+     * {@link Entity#add(com.lhkbob.entreri.Component)}. For many cases a
+     * plain copy-by-value or copy-by-reference is sufficient, but some
+     * component types might require more complicated cloning rules.
+     * 
+     * @param src The source property that is being cloned
+     * @param srcIndex The index into src of the component being cloned
+     * @param dst The destination property created from the template
+     * @param dstIndex The index into dst of the component being created
+     */
+    public void clone(T src, int srcIndex, T dst, int dstIndex);
+}

src/main/java/com/lhkbob/entreri/ReflectionComponentDataFactory.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.lhkbob.entreri;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.lhkbob.entreri.annot.DefaultFactory;
+import com.lhkbob.entreri.annot.Factory;
+import com.lhkbob.entreri.annot.Unmanaged;
+
+/**
+ * <p>
+ * ReflectionComponentDataFactory is a factory for creating new instances of
+ * ComponentDatas of a specific type. ReflectionComponentDataFactory has a
+ * number of requirements for the way that a ComponentData subclass is defined
+ * in order to be processed correctly. If these are too restrictive for your
+ * needs, you can implement your own {@link ComponentDataFactory} and annotate
+ * the ComponentData type with {@link DefaultFactory}.
+ * </p>
+ * <p>
+ * ReflectionComponentDataFactory has the following requirements:
+ * <ol>
+ * <li>If the class is not a direct subclass of ComponentData, its parent must
+ * be a assignable to ComponentData and be declared abstract. The parent's
+ * declared fields must also follow the rules below.</li>
+ * <li>A concrete subclass of ComponentData must have only one constructor; it
+ * must be private or protected and take zero arguments.</li>
+ * <li>All non-static fields that are not annotated with {@link Unmanaged}
+ * defined in the ComponentData type, or its abstract parents, must be
+ * Properties and be private or protected.</li>
+ * <li>The Property field must be annotated with {@link Factory} or the Property
+ * class must be annotated with {@link Factory}.</li>
+ * <li>The specified factory must have a default constructor, or a constructor
+ * that takes a single {@link Attributes} instance.</li>
+ * </ol>
+ * </p>
+ * 
+ * @author Michael Ludwig
+ * @param <T> The built component type
+ */
+public final class ReflectionComponentDataFactory<T extends ComponentData<T>> implements ComponentDataFactory<T> {
+    private final Constructor<T> constructor;
+    private final Map<Field, PropertyFactory<?>> propertyFactories;
+
+    /**
+     * Create a new ReflectionComponentDataFactory for the given type of
+     * ComponentData. This is a slower constructor with lots of reflection so
+     * builders should be cached. This will throw an exception if the
+     * ComponentData type does meet the conventions required for this type of
+     * factory.
+     * 
+     * @param type The component type created by this builder
+     * @throws IllegalArgumentException if the class is not really a component
+     *             or if it is abstract
+     * @throws IllegalComponentDefinitionException if the class hierarchy of the
+     *             component type is invalid by breaking any of the constructor
+     *             or field rules for defining a component
+     */
+    @SuppressWarnings("unchecked")
+    public ReflectionComponentDataFactory(Class<T> type) {
+        // Now we actually have to build up a new TypeId - which is sort of slow
+        if (!ComponentData.class.isAssignableFrom(type))
+            throw new IllegalArgumentException("Type must be a subclass of ComponentData: " + type);
+        
+        // Make sure we don't create TypedIds for abstract ComponentData types 
+        // (we don't want to try to allocate these)
+        if (Modifier.isAbstract(type.getModifiers()))
+            throw new IllegalArgumentException("ComponentData class type cannot be abstract: " + type);
+        
+        // Accumulate all property fields and validate type hierarchy
+        List<Field> fields = new ArrayList<Field>(getFields(type));
+        
+        Class<? super T> parent = type.getSuperclass();
+        while(!ComponentData.class.equals(parent)) {
+            if (!Modifier.isAbstract(parent.getModifiers()))
+                throw new IllegalComponentDefinitionException(type, "Parent class " + parent + " is not abstract");
+            
+            // this cast is safe since we're in the while loop
+            fields.addAll(getFields((Class<? extends ComponentData<?>>) parent));
+            parent = parent.getSuperclass();
+        }
+        
+        constructor = getConstructor(type);
+        propertyFactories = Collections.unmodifiableMap(getPropertyFactories(fields));
+    }
+
+    @Override
+    public Map<Field, PropertyFactory<?>> getPropertyFactories() {
+        return propertyFactories;
+    }
+    
+    @Override
+    public T createInstance() {
+        try {
+            return constructor.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to create ComponentData instance", e);
+        }
+    }
+    
+    @Override
+    public void setProperty(T instance, Object key, Property property) {
+        if (instance == null || key == null || property == null)
+            throw new NullPointerException("Arguments cannot be null");
+        Field f = (Field) key;
+        
+        if (!f.getType().isAssignableFrom(property.getClass()))
+            throw new IllegalArgumentException("Property was not created by correct PropertyFactory for key: " + key);
+        
+        try {
+            f.set(instance, property);
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to inject Property", e);
+        }
+    }
+
+    private static <T extends ComponentData<?>> Map<Field, PropertyFactory<?>> getPropertyFactories(List<Field> fields) {
+        Map<Field, PropertyFactory<?>> factories = new HashMap<Field, PropertyFactory<?>>();
+        for (Field f: fields) {
+            factories.put(f, createFactory(f));
+        }
+        return factories;
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static PropertyFactory<?> createFactory(Field field) {
+        Class<? extends Property> type = (Class<? extends Property>) field.getType();
+        Class<? extends ComponentData<?>> forCType = (Class<? extends ComponentData<?>>) field.getDeclaringClass();
+        
+        // Check for the @Factory on the field and the type
+        Class<? extends PropertyFactory<?>> factoryType;
+        if (field.getAnnotation(Factory.class) != null) {
+            // prefer field declaration
+            factoryType = field.getAnnotation(Factory.class).value();
+        } else if (type.getAnnotation(Factory.class) != null) {
+            // fall back to type declaration
+            factoryType = type.getAnnotation(Factory.class).value();
+        } else {
+            throw new IllegalComponentDefinitionException(forCType, "Cannot create PropertyFactory for " + type + ", no @Factory annotation on field or type");
+        }
+
+        // verify that the PropertyFactory actually creates the right type
+        try {
+            Method create = factoryType.getMethod("create");
+            if (!type.isAssignableFrom(create.getReturnType()))
+                throw new IllegalComponentDefinitionException(forCType, "@Factory(" + factoryType + ") creates incorrect Property type: " + create.getReturnType() + ", required type: " + type);
+        } catch (SecurityException e) {
+            // should not happen
+            throw new RuntimeException("Unable to inspect factory's create method", e);
+        } catch (NoSuchMethodException e) {
+            // should not happen
+            throw new RuntimeException("Unable to inspect factory's create method", e);
+        }
+        
+        PropertyFactory<?> factory = invokeConstructor(factoryType, new Attributes(field));
+        if (factory == null)
+            factory = invokeConstructor(factoryType);
+
+        if (factory == null) {
+            // unable to create a PropertyFactory
+            throw new IllegalComponentDefinitionException(forCType, "Unable to create PropertyFactory for " + field);
+        } else {
+            return factory;
+        }
+    }
+    
+    private static PropertyFactory<?> invokeConstructor(Class<? extends PropertyFactory<?>> type, Object... args) {
+        Class<?>[] paramTypes = new Class<?>[args.length];
+        for (int i = 0; i < args.length; i++)
+            paramTypes[i] = args[i].getClass();
+        
+        try {
+            Constructor<?> ctor = type.getConstructor(paramTypes);
+            ctor.setAccessible(true);
+            return (PropertyFactory<?>) ctor.newInstance(args);
+        } catch (SecurityException e) {
+            throw new RuntimeException("Unable to inspect factory's constructor", e);
+        } catch (NoSuchMethodException e) {
+            // ignore, fall back to default constructor
+            return null;
+        } catch (Exception e) {
+            // other exceptions should not occur
+            throw new RuntimeException("Unexpected exception during factory creation", e);
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static <T extends ComponentData<?>> Constructor<T> getConstructor(Class<T> type) {
+        // This assumes that type is the concrete type, so it will fail if there
+        // are multiple constructors or it's not private with the correct arguments
+        Constructor<?>[] ctors = type.getDeclaredConstructors();
+        if (ctors.length != 1)
+            throw new IllegalComponentDefinitionException(type, "ComponentData type must only define a single constructor");
+        
+        Constructor<T> ctor = (Constructor<T>) ctors[0];
+        if (!Modifier.isPrivate(ctor.getModifiers()) && !Modifier.isProtected(ctor.getModifiers()))
+            throw new IllegalComponentDefinitionException(type, "ComponentData constructor must be private or protected");
+        
+        Class<?>[] args = ctor.getParameterTypes();
+        if (args.length != 0)
+            throw new IllegalComponentDefinitionException(type, "ComponentData constructor does not have a default constructor");
+        
+        // Found it, now make it accessible (which might throw a SecurityException)
+        ctor.setAccessible(true);
+        return ctor;
+    }
+    
+    private static List<Field> getFields(Class<? extends ComponentData<?>> type) {
+        Field[] declared = type.getDeclaredFields();
+        List<Field> nonTransientFields = new ArrayList<Field>(declared.length);
+
+        for (int i = 0; i < declared.length; i++) {
+            int modifiers = declared[i].getModifiers();
+            if (Modifier.isStatic(modifiers))
+                continue; // ignore static fields
+            
+            if (declared[i].isAnnotationPresent(Unmanaged.class))
+                continue; // ignore the field
+            
+            if (!Property.class.isAssignableFrom(declared[i].getType())) {
+                throw new IllegalComponentDefinitionException(type, "ComponentData has non-Property field that is not unmanaged: " + declared[i]);
+            }
+            
+            if (!Modifier.isPrivate(modifiers) && !Modifier.isProtected(modifiers))
+                throw new IllegalComponentDefinitionException(type, "Field must be private or protected: " + declared[i]);
+            
+            nonTransientFields.add(declared[i]);
+        }
+        
+        // Make sure all fields are accessible so we can assign them
+        Field[] access = new Field[nonTransientFields.size()];
+        nonTransientFields.toArray(access);
+        Field.setAccessible(access, true);
+        return nonTransientFields;
+    }
+}

src/main/java/com/lhkbob/entreri/annot/Attribute.java

+package com.lhkbob.entreri.annot;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import java.lang.annotation.Documented;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface Attribute { }

src/main/java/com/lhkbob/entreri/annot/DefaultFactory.java

 
 import com.lhkbob.entreri.ComponentDataFactory;
 import com.lhkbob.entreri.EntitySystem;
-import com.lhkbob.entreri.property.ReflectionComponentDataFactory;
+import com.lhkbob.entreri.ReflectionComponentDataFactory;
 
 /**
  * <p>

src/main/java/com/lhkbob/entreri/annot/DefaultValue.java

 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import com.lhkbob.entreri.property.Property;
-import com.lhkbob.entreri.property.ReflectionComponentDataFactory;
-
 /**
  * <p>
- * DefaultValue is an annotation that can be added to {@link Property} fields in
- * a component definition to automatically configure their creating
- * PropertyFactories.
- * </p>
- * <p>
- * The default {@link ReflectionComponentDataFactory} will look for a static
- * method named 'factory()' that takes two parameters. The first must be an int
- * and represents the element size. If the {@link ElementSize} is not present,
- * the ComponentDataFactory should use a value of 1. The second parameter is the
- * default value to use. The parameter type determines which annotation method
- * is checked (e.g. {@link #defaultByte()} or {@link #defaultDouble()}).
+ * DefaultValue is an attribute annotation for primitive properties to specify
+ * the default value that components are initialized to.
  * </p>
  * <p>
  * All Properties defined in com.lhkbob.entreri.property support this
  * 
  * @author Michael Ludwig
  */
+@Attribute
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.FIELD)
 public @interface DefaultValue {

src/main/java/com/lhkbob/entreri/annot/ElementSize.java

 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import com.lhkbob.entreri.property.Property;
-import com.lhkbob.entreri.property.ReflectionComponentDataFactory;
-
 /**
  * <p>
- * ElementSize is an annotation that can be added to {@link Property} fields in
- * a component definition to automatically configure their creating
- * PropertyFactories.
- * </p>
+ * ElementSize is an attribute annotation that can be applied to a field
+ * declaration of a Property to declare how many elements belong to each
+ * component. This is useful if multiple primitives must be packed together for
+ * the same component, such as with a 3D vector.
  * <p>
- * The default {@link ReflectionComponentDataFactory} will look for a static
- * method named 'factory()' that takes a single int as a parameter. ElementSize
- * can be combined with the {@link DefaultValue} if there is a factory() that
- * takes an int and a second argument for the default value. It is not necessary
- * to use the {@link ElementSize} if the element size should be 1.
- * </p>
- * <p>
- * All Property implementations in com.lhkbob.entreri.property support the
- * use of this annotation.
+ * All Property implementations in com.lhkbob.entreri.property support the use
+ * of this annotation.
  * </p>
  * 
  * @author Michael Ludwig
  */
+@Attribute
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.FIELD)
 public @interface ElementSize {

src/main/java/com/lhkbob/entreri/annot/Factory.java

 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import com.lhkbob.entreri.property.PropertyFactory;
-import com.lhkbob.entreri.property.ReflectionComponentDataFactory;
+import com.lhkbob.entreri.Attributes;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.ReflectionComponentDataFactory;
 
 /**
+ * <p>
  * The Factory annotation can be declared on a Property field in a ComponentData
  * definition to specify the type of PropertyFactory to use when creating
  * instances of the Property for the component. The factory type must have a
  * no-argument constructor in order to be instantiated correctly. This
- * annotation should be used if {@link ElementSize} and {@link DefaultValue} are
- * not sufficient.
+ * annotation should be used if Property does not provide a default factory with
+ * sufficient flexibility with annotation attributes (e.g. {@link DefaultValue}
+ * and {@link Factory}).
+ * </p>
+ * <p>
+ * Factory can also be placed at the type level on a Property implementation to
+ * declare the default PropertyFactory to use. When using the
+ * {@link ReflectionComponentDataFactory}, it checks for a constructor taking a
+ * single {@link Attributes} object, or the default constructor.
+ * </p>
  * 
  * @see ReflectionComponentDataFactory
  * @author Michael Ludwig
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.FIELD)
+@Target({ ElementType.FIELD, ElementType.TYPE })
 public @interface Factory {
     /**
      * @return Class of the PropertyFactory to instantiate, must have an

src/main/java/com/lhkbob/entreri/property/AbstractIndexedDataStore.java

 import java.nio.Buffer;
 import java.util.List;
 
+import com.lhkbob.entreri.IndexedDataStore;
+
 
 /**
  * AbstractIndexedDataStore is an implementation of IndexedDataStore that uses

src/main/java/com/lhkbob/entreri/property/AbstractPropertyFactory.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+
 /**
  * AbstractPropertyFactory is an abstract PropertyFactory implementation that
  * implements {@link #clone(Property, int, Property, int)} in terms of
  * @param <P>
  */
 public abstract class AbstractPropertyFactory<P extends Property> implements PropertyFactory<P> {
+    protected final Attributes attributes;
+    
+    public AbstractPropertyFactory(Attributes attrs) {
+        attributes = attrs;
+    }
+    
     @Override
     public void clone(P src, int srcIndex, P dst, int dstIndex) {
         src.getDataStore().copy(srcIndex, 1, dst.getDataStore(), dstIndex);

src/main/java/com/lhkbob/entreri/property/BooleanProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.DefaultValue;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * BooleanProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(BooleanProperty.BooleanPropertyFactory.class)
 public final class BooleanProperty implements Property {
     private BooleanDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for BooleanProperty
      */
-    public static PropertyFactory<BooleanProperty> factory(final int elementSize) {
+    public static PropertyFactory<BooleanProperty> factory(int elementSize) {
         return factory(elementSize, false);
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for IntProperty
      */
-    public static PropertyFactory<BooleanProperty> factory(final int elementSize, final boolean dflt) {
-        return new AbstractPropertyFactory<BooleanProperty>() {
-            @Override
-            public BooleanProperty create() {
-                return new BooleanProperty(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(BooleanProperty p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    public static PropertyFactory<BooleanProperty> factory(int elementSize, boolean dflt) {
+        return new BooleanPropertyFactory(elementSize, dflt);
     }
 
     /**
         
         this.store = newStore;
     }
+    
+    private static class BooleanPropertyFactory extends AbstractPropertyFactory<BooleanProperty> {
+        private final int elementSize;
+        private final boolean defaultValue;
+        
+        public BooleanPropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            if (attrs.hasAttribute(DefaultValue.class))
+                defaultValue = attrs.getAttribute(DefaultValue.class).defaultBoolean();
+            else
+                defaultValue = false;
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public BooleanPropertyFactory(int elementSize, boolean defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public BooleanProperty create() {
+            return new BooleanProperty(elementSize);
+        }
+
+        @Override
+        public void setDefaultValue(BooleanProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }
+    }
 
     private static class BooleanDataStore extends AbstractIndexedDataStore<boolean[]> {
         private final boolean[] array;

src/main/java/com/lhkbob/entreri/property/ByteProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.DefaultValue;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * ByteProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(ByteProperty.BytePropertyFactory.class)
 public final class ByteProperty implements Property {
     private ByteDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for ByteProperty
      */
-    public static PropertyFactory<ByteProperty> factory(final int elementSize) {
+    public static PropertyFactory<ByteProperty> factory(int elementSize) {
         return factory(elementSize, (byte) 0);
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for ByteProperty
      */
-    public static PropertyFactory<ByteProperty> factory(final int elementSize, final byte dflt) {
-        return new AbstractPropertyFactory<ByteProperty>() {
-            @Override
-            public ByteProperty create() {
-                return new ByteProperty(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(ByteProperty p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    public static PropertyFactory<ByteProperty> factory(int elementSize, byte dflt) {
+        return new BytePropertyFactory(elementSize, dflt);
     }
 
     /**
     public IndexedDataStore getDataStore() {
         return store;
     }
-
+    
     @Override
     public void setDataStore(IndexedDataStore store) {
         if (store == null)
         
         this.store = newStore;
     }
+    
+    private static class BytePropertyFactory extends AbstractPropertyFactory<ByteProperty> {
+        private final int elementSize;
+        private final byte defaultValue;
+        
+        public BytePropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            if (attrs.hasAttribute(DefaultValue.class))
+                defaultValue = attrs.getAttribute(DefaultValue.class).defaultByte();
+            else
+                defaultValue = 0;
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public BytePropertyFactory(int elementSize, byte defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public ByteProperty create() {
+            return new ByteProperty(elementSize);
+        }
+
+        @Override
+        public void setDefaultValue(ByteProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }
+    }
 
     private static class ByteDataStore extends AbstractIndexedDataStore<byte[]> {
         private final byte[] array;

src/main/java/com/lhkbob/entreri/property/CharProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.DefaultValue;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * CharProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(CharProperty.CharPropertyFactory.class)
 public final class CharProperty implements Property {
     private CharDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for CharProperty
      */
-    public static PropertyFactory<CharProperty> factory(final int elementSize) {
+    public static PropertyFactory<CharProperty> factory(int elementSize) {
         return factory(elementSize, '\0');
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for CharProperty
      */
-    public static PropertyFactory<CharProperty> factory(final int elementSize, final char dflt) {
-        return new AbstractPropertyFactory<CharProperty>() {
-            @Override
-            public CharProperty create() {
-                return new CharProperty(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(CharProperty p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    public static PropertyFactory<CharProperty> factory(int elementSize, char dflt) {
+        return new CharPropertyFactory(elementSize, dflt);
     }
 
     /**
         
         this.store = newStore;
     }
+    
+    private static class CharPropertyFactory extends AbstractPropertyFactory<CharProperty> {
+        private final int elementSize;
+        private final char defaultValue;
+        
+        public CharPropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            if (attrs.hasAttribute(DefaultValue.class))
+                defaultValue = attrs.getAttribute(DefaultValue.class).defaultChar();
+            else
+                defaultValue = '\0';
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public CharPropertyFactory(int elementSize, char defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public CharProperty create() {
+            return new CharProperty(elementSize);
+        }
+
+        @Override
+        public void setDefaultValue(CharProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }
+    }
 
     private static class CharDataStore extends AbstractIndexedDataStore<char[]> {
         private final char[] array;

src/main/java/com/lhkbob/entreri/property/CompactAwareProperty.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the
- *         documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.lhkbob.entreri.property;
-
-import com.lhkbob.entreri.EntitySystem;
-
-/**
- * CompactAwareProperty is an extension of Property that receives notification
- * from the EntitySystem when it has completed a compaction (i.e. a call to
- * {@link EntitySystem#compact()}). This is most useful for properties which
- * cache values based on the last used index, which is invalidated when the
- * system is compacted.
- * 
- * @author Michael Ludwig
- */
-public interface CompactAwareProperty extends Property {
-    public void onCompactComplete();
-}

src/main/java/com/lhkbob/entreri/property/DoubleProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.DefaultValue;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * DoubleProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(DoubleProperty.DoublePropertyFactory.class)
 public final class DoubleProperty implements Property {
     private DoubleDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for DoubleProperty
      */
-    public static PropertyFactory<DoubleProperty> factory(final int elementSize) {
+    public static PropertyFactory<DoubleProperty> factory(int elementSize) {
         return factory(elementSize, 0.0);
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for DoubleProperty
      */
-    public static PropertyFactory<DoubleProperty> factory(final int elementSize, final double dflt) {
-        return new AbstractPropertyFactory<DoubleProperty>() {
-            @Override
-            public DoubleProperty create() {
-                return new DoubleProperty(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(DoubleProperty p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    public static PropertyFactory<DoubleProperty> factory(int elementSize, double dflt) {
+        return new DoublePropertyFactory(elementSize, dflt);
     }
 
     /**
         
         this.store = newStore;
     }
+    
+    private static class DoublePropertyFactory extends AbstractPropertyFactory<DoubleProperty> {
+        private final int elementSize;
+        private final double defaultValue;
+        
+        public DoublePropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            if (attrs.hasAttribute(DefaultValue.class))
+                defaultValue = attrs.getAttribute(DefaultValue.class).defaultDouble();
+            else
+                defaultValue = 0;
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public DoublePropertyFactory(int elementSize, double defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public DoubleProperty create() {
+            return new DoubleProperty(elementSize);
+        }
+
+        @Override
+        public void setDefaultValue(DoubleProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }
+    }
 
     private static class DoubleDataStore extends AbstractIndexedDataStore<double[]> {
         private final double[] array;

src/main/java/com/lhkbob/entreri/property/FloatProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.DefaultValue;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * FloatProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(FloatProperty.FloatPropertyFactory.class)
 public final class FloatProperty implements Property {
     private FloatDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for FloatProperty
      */
-    public static PropertyFactory<FloatProperty> factory(final int elementSize) {
+    public static PropertyFactory<FloatProperty> factory(int elementSize) {
         return factory(elementSize, 0f);
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for FloatProperty
      */
-    public static PropertyFactory<FloatProperty> factory(final int elementSize, final float dflt) {
-        return new AbstractPropertyFactory<FloatProperty>() {
-            @Override
-            public FloatProperty create() {
-                return new FloatProperty(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(FloatProperty p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    public static PropertyFactory<FloatProperty> factory(int elementSize, float dflt) {
+        return new FloatPropertyFactory(elementSize, dflt);
     }
 
     /**
         
         this.store = newStore;
     }
+    
+    private static class FloatPropertyFactory extends AbstractPropertyFactory<FloatProperty> {
+        private final int elementSize;
+        private final float defaultValue;
+        
+        public FloatPropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            if (attrs.hasAttribute(DefaultValue.class))
+                defaultValue = attrs.getAttribute(DefaultValue.class).defaultFloat();
+            else
+                defaultValue = 0f;
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public FloatPropertyFactory(int elementSize, float defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public FloatProperty create() {
+            return new FloatProperty(elementSize);
+        }
+
+        @Override
+        public void setDefaultValue(FloatProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }
+    }
 
     private static class FloatDataStore extends AbstractIndexedDataStore<float[]> {
         private final float[] array;

src/main/java/com/lhkbob/entreri/property/IndexedDataStore.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the
- *         documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.lhkbob.entreri.property;
-
-/**
- * IndexedDataStore is a generic data storage interface representing packed,
- * random-access data storage for a property of a ComponentData. All Components of
- * the same type in the same EntitySystem will have their properties share the
- * IndexedDataStores so that iteration will have much better cache locality, and
- * will avoid the reorganization caused by Java's garbage collector.
- * 
- * @author Michael Ludwig
- */
-public interface IndexedDataStore {
-    /**
-     * Create a new data store with the same behavior as this IndexedDataStore
-     * except, that it holds enough space for <tt>size</tt> Properties.
-     * 
-     * @param size The size, in number of properties
-     */
-    public IndexedDataStore create(int size);
-    
-    /**
-     * @return The number of properties that can fit into this IndexedDataStore
-     */
-    public int size();
-    
-    /**
-     * @return Memory usage estimate of this data store
-     */
-    public long memory();
-
-    /**
-     * <p>
-     * Copy <tt>len</tt> property values starting at <tt>srcOffset</tt> from
-     * this IndexedDataStore into <tt>dest</tt>, placing the first property's
-     * values at <tt>destOffset</tt>. Both <tt>srcOffset</tt> and
-     * <tt>destOffset</tt> are in units of property, and not any underlying
-     * array.
-     * </p>
-     * <p>
-     * An exception should be thrown if the destination IndexedDataStore is not
-     * of the same type, or is not compatible with this IndexedDataStore.
-     * </p>
-     * 
-     * @param srcOffset The offset of the first property to copy
-     * @param len The number of properties to copy
-     * @param dest The destination IndexedDataStore
-     * @param destOffset The offset into dest to place the first property's
-     *            values
-     * @throws IndexOutOfBoundsException if the offsets or lengths cause
-     *             out-of-bounds exceptions
-     */
-    public void copy(int srcOffset, int len, IndexedDataStore dest, int destOffset);
-}

src/main/java/com/lhkbob/entreri/property/IntProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.DefaultValue;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * IntProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(IntProperty.IntPropertyFactory.class)
 public final class IntProperty implements Property {
     private IntDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for IntProperty
      */
-    public static PropertyFactory<IntProperty> factory(final int elementSize) {
+    public static PropertyFactory<IntProperty> factory(int elementSize) {
         return factory(elementSize, 0);
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for IntProperty
      */
-    public static PropertyFactory<IntProperty> factory(final int elementSize, final int dflt) {
-        return new AbstractPropertyFactory<IntProperty>() {
-            @Override
-            public IntProperty create() {
-                return new IntProperty(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(IntProperty p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    public static PropertyFactory<IntProperty> factory(int elementSize, final int dflt) {
+        return new IntPropertyFactory(elementSize, dflt);
     }
 
     /**
         
         this.store = newStore;
     }
+    
+    private static class IntPropertyFactory extends AbstractPropertyFactory<IntProperty> {
+        private final int elementSize;
+        private final int defaultValue;
+        
+        public IntPropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            if (attrs.hasAttribute(DefaultValue.class))
+                defaultValue = attrs.getAttribute(DefaultValue.class).defaultInt();
+            else
+                defaultValue = 0;
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public IntPropertyFactory(int elementSize, int defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public IntProperty create() {
+            return new IntProperty(elementSize);
+        }
+
+        @Override
+        public void setDefaultValue(IntProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }
+    }
 
     private static class IntDataStore extends AbstractIndexedDataStore<int[]> {
         private final int[] array;

src/main/java/com/lhkbob/entreri/property/LongProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.DefaultValue;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * LongProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(LongProperty.LongPropertyFactory.class)
 public final class LongProperty implements Property {
     private LongDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for LongProperty
      */
-    public static PropertyFactory<LongProperty> factory(final int elementSize) {
+    public static PropertyFactory<LongProperty> factory(int elementSize) {
         return factory(elementSize, 0);
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for LongProperty
      */
-    public static PropertyFactory<LongProperty> factory(final int elementSize, final long dflt) {
-        return new AbstractPropertyFactory<LongProperty>() {
-            @Override
-            public LongProperty create() {
-                return new LongProperty(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(LongProperty p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    public static PropertyFactory<LongProperty> factory(int elementSize, long dflt) {
+        return new LongPropertyFactory(elementSize, dflt);
     }
 
     /**
         
         this.store = newStore;
     }
+    
+    private static class LongPropertyFactory extends AbstractPropertyFactory<LongProperty> {
+        private final int elementSize;
+        private final long defaultValue;
+        
+        public LongPropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            if (attrs.hasAttribute(DefaultValue.class))
+                defaultValue = attrs.getAttribute(DefaultValue.class).defaultLong();
+            else
+                defaultValue = 0L;
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public LongPropertyFactory(int elementSize, long defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public LongProperty create() {
+            return new LongProperty(elementSize);
+        }
+
+        @Override
+        public void setDefaultValue(LongProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }
+    }
 
     private static class LongDataStore extends AbstractIndexedDataStore<long[]> {
         private final long[] array;

src/main/java/com/lhkbob/entreri/property/ObjectProperty.java

  */
 package com.lhkbob.entreri.property;
 
+import com.lhkbob.entreri.Attributes;
 import com.lhkbob.entreri.ComponentData;
+import com.lhkbob.entreri.IndexedDataStore;
+import com.lhkbob.entreri.Property;
+import com.lhkbob.entreri.PropertyFactory;
+import com.lhkbob.entreri.annot.ElementSize;
+import com.lhkbob.entreri.annot.Factory;
 
 /**
  * ObjectProperty is an implementation of Property that stores the property data
  * 
  * @author Michael Ludwig
  */
+@Factory(ObjectProperty.ObjectPropertyFactory.class)
 public final class ObjectProperty<T> implements Property {
     private ObjectDataStore store;
     
      * @param elementSize The element size of the created properties
      * @return A PropertyFactory for ObjectProperty
      */
-    public static <T> PropertyFactory<ObjectProperty<T>> factory(final int elementSize) {
+    public static <T> PropertyFactory<ObjectProperty<T>> factory(int elementSize) {
         return factory(elementSize, null);
     }
 
      * @param dflt The default value assigned to each component and element
      * @return A PropertyFactory for ObjectProperty
      */
-    public static <T> PropertyFactory<ObjectProperty<T>> factory(final int elementSize, final T dflt) {
-        return new AbstractPropertyFactory<ObjectProperty<T>>() {
-            @Override
-            public ObjectProperty<T> create() {
-                return new ObjectProperty<T>(elementSize);
-            }
-          
-            @Override
-            public void setDefaultValue(ObjectProperty<T> p, int index) {
-                for (int i = 0; i < elementSize; i++)
-                    p.set(dflt, index, i);
-            }
-        };
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static <T> PropertyFactory<ObjectProperty<T>> factory(int elementSize, T dflt) {
+        PropertyFactory superRaw = new ObjectPropertyFactory(elementSize, dflt);
+        return (PropertyFactory<ObjectProperty<T>>) superRaw;
     }
     
     /**
         
         this.store = newStore;
     }
+    
+    @SuppressWarnings("rawtypes")
+    private static class ObjectPropertyFactory extends AbstractPropertyFactory<ObjectProperty> {
+        private final int elementSize;
+        private final Object defaultValue;
+        
+        public ObjectPropertyFactory(Attributes attrs) {
+            super(attrs);
+            
+            defaultValue = null;
+            
+            if (attrs.hasAttribute(ElementSize.class))
+                elementSize = attrs.getAttribute(ElementSize.class).value();
+            else
+                elementSize = 1;
+        }
+        
+        public ObjectPropertyFactory(int elementSize, Object defaultValue) {
+            super(null);
+            this.elementSize = elementSize;
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public ObjectProperty create() {
+            return new ObjectProperty(elementSize);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public void setDefaultValue(ObjectProperty property, int index) {
+            for (int i = 0; i < elementSize; i++)
+                property.set(defaultValue, index, i);
+        }