Commits

Michael Ludwig committed 5d401dd

Simplify maven module layout by moving janino tests directly into unit tests of core library

Comments (0)

Files changed (70)

entreri-core/pom.xml

                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.1</version>
-  
-                <executions>
-                    <execution>
-                        <id>default-compile</id>
-                        <configuration>
-                            <proc>none</proc>
-                        </configuration>
-                    </execution>
-                </executions>
+
+                <!-- Disable all annotation processing, even for tests. The unit tests
+                in this module will only test Janino, and entreri-apt-tests module will
+                perform all statically compiled tests. -->
+
+                <configuration>
+                    <proc>none</proc>
+                </configuration>
             </plugin>
         </plugins>
     </build>

entreri-core/src/main/java/com/lhkbob/entreri/impl/ComponentFactoryProvider.java

 import java.nio.charset.Charset;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.*;
 
 /**
         }
 
         if (use15) {
-            // prepend some annotations
-            sb.append("@Generated\n");
+            // prepend some annotations (and get UTC formatted date, as required by
+            // the @Generated annotation if we attach a date, which we do because it's useful)
+            TimeZone tz = TimeZone.getTimeZone("UTC");
+            DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
+            df.setTimeZone(tz);
+            String nowAsISO = df.format(new Date());
+
+            sb.append("import javax.annotation.Generated;\n\n");
+            sb.append("@Generated(value={\"")
+              .append(ComponentFactoryProvider.class.getCanonicalName())
+              .append("\"}, date=\"").append(nowAsISO).append("\")\n");
             sb.append("@SuppressWarnings(\"unchecked\")\n");
         }
 

entreri-core/src/test/java/com/lhkbob/entreri/ComponentIteratorTest.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.components.FloatComponent;
+import com.lhkbob.entreri.components.IntComponent;
+import com.lhkbob.entreri.components.ObjectComponent;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class ComponentIteratorTest {
+    private static final int ENTITY_COUNT = 5;
+
+    private EntitySystem system;
+    private List<Integer> entityIds;
+    private List<ObjectComponent.FooBlah> entityObjValues;
+    private List<Float> entityFloatValues;
+
+    private List<ObjectComponent.FooBlah> entityCombinedObjValues;
+    private List<Float> entityCombinedFloatValues;
+
+    private int countWithObj;
+    private int countWithFloat;
+    private int countWithBoth;
+
+    private ObjectComponent objData;
+    private FloatComponent floatData;
+
+    @Before
+    public void setup() {
+        entityIds = new ArrayList<Integer>();
+        entityObjValues = new ArrayList<ObjectComponent.FooBlah>();
+        entityFloatValues = new ArrayList<Float>();
+        entityCombinedObjValues = new ArrayList<ObjectComponent.FooBlah>();
+        entityCombinedFloatValues = new ArrayList<Float>();
+
+        system = EntitySystem.create();
+
+        for (int i = 0; i < ENTITY_COUNT; i++) {
+            Entity e = system.addEntity();
+
+            entityIds.add(e.getId());
+
+            double c = Math.random();
+            if (c > .8) {
+                // both components to add
+                objData = e.add(ObjectComponent.class);
+                ObjectComponent.FooBlah v = new ObjectComponent.FooBlah();
+                entityObjValues.add(v);
+                entityCombinedObjValues.add(v);
+                objData.setObject(v);
+
+                floatData = e.add(FloatComponent.class);
+                float fv = (float) (Math.random() * 1000);
+                entityFloatValues.add(fv);
+                entityCombinedFloatValues.add(fv);
+                floatData.setFloat(fv);
+
+                countWithBoth++;
+                countWithObj++;
+                countWithFloat++;
+            } else if (c > .4) {
+                // just float component
+                floatData = e.add(FloatComponent.class);
+                float fv = (float) (Math.random() * 1000);
+                entityFloatValues.add(fv);
+                floatData.setFloat(fv);
+
+                countWithFloat++;
+            } else {
+                // just object component
+                objData = e.add(ObjectComponent.class);
+                ObjectComponent.FooBlah v = new ObjectComponent.FooBlah();
+                entityObjValues.add(v);
+                objData.setObject(v);
+
+                countWithObj++;
+            }
+        }
+    }
+
+    private void doTestIterator(Iterator<Entity> it) {
+        int i = 0;
+        while (it.hasNext()) {
+            Assert.assertEquals(entityIds.get(i), Integer.valueOf(it.next().getId()));
+            i++;
+        }
+
+        Assert.assertEquals(entityIds.size(), i);
+    }
+
+    // assumes it has objData in it
+    private void doTestObjectComponentIterator(ComponentIterator it) {
+        int i = 0;
+        while (it.next()) {
+            Assert.assertEquals(entityObjValues.get(i), objData.getObject());
+            i++;
+        }
+
+        Assert.assertEquals(countWithObj, i);
+    }
+
+    // assumes it has floatData in it
+    private void doTestFloatComponentIterator(ComponentIterator it) {
+        int i = 0;
+        while (it.next()) {
+            Assert.assertEquals(entityFloatValues.get(i), floatData.getFloat(), .0001f);
+            i++;
+        }
+
+        Assert.assertEquals(countWithFloat, i);
+    }
+
+    // assumes it has floatData and objData as required
+    private void doTestBulkComponentIterator(ComponentIterator it) {
+        int i = 0;
+        while (it.next()) {
+            Assert.assertEquals(entityCombinedObjValues.get(i), objData.getObject());
+            Assert.assertEquals(entityCombinedFloatValues.get(i), floatData.getFloat(),
+                                .0001f);
+            i++;
+        }
+
+        Assert.assertEquals(countWithBoth, i);
+    }
+
+    private void doIteratorRemove(Iterator<Entity> it) {
+        int i = 0;
+        Iterator<Integer> ids = entityIds.iterator();
+        while (it.hasNext()) {
+            it.next();
+            ids.next();
+            if (i > ENTITY_COUNT / 2) {
+                it.remove();
+                ids.remove();
+            }
+
+            i++;
+        }
+
+        // this invalidates all of the value lists, but that is okay
+    }
+
+    @Test
+    public void testEntityIterator() {
+        doTestIterator(system.iterator());
+    }
+
+    @Test
+    public void testComponentIterator() {
+        ComponentIterator ft = system.fastIterator();
+        floatData = ft.addRequired(FloatComponent.class);
+        ComponentIterator it = system.fastIterator();
+        objData = it.addRequired(ObjectComponent.class);
+
+        doTestObjectComponentIterator(it);
+        it.reset();
+        doTestObjectComponentIterator(it);
+
+        doTestFloatComponentIterator(ft);
+        ft.reset();
+        doTestFloatComponentIterator(ft);
+    }
+
+    @Test
+    public void testBulkComponentIterator() {
+        ComponentIterator it = system.fastIterator();
+        floatData = it.addRequired(FloatComponent.class);
+        objData = it.addRequired(ObjectComponent.class);
+
+        doTestBulkComponentIterator(it);
+        it.reset();
+        doTestBulkComponentIterator(it);
+    }
+
+    @Test
+    public void testEntityIteratorRemove() {
+        doIteratorRemove(system.iterator());
+        doTestIterator(system.iterator());
+    }
+
+    @Test
+    public void testFlyweightComponent() {
+        ComponentIterator it = system.fastIterator();
+        IntComponent c = it.addRequired(IntComponent.class);
+
+        Assert.assertTrue(c.isFlyweight());
+        Assert.assertFalse(c.isAlive());
+
+        int count = 0;
+        system.addEntity().add(IntComponent.class);
+        while (it.next()) {
+            Assert.assertTrue(c.isFlyweight());
+            Assert.assertTrue(c.isAlive());
+            count++;
+        }
+
+        Assert.assertEquals(1, count);
+    }
+}

entreri-core/src/test/java/com/lhkbob/entreri/ComponentTest.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.components.ComplexComponent;
+import com.lhkbob.entreri.components.CustomProperty;
+import com.lhkbob.entreri.components.FloatPropertyFactory;
+import com.lhkbob.entreri.components.IntComponent;
+import junit.framework.Assert;
+import org.junit.Test;
+
+public class ComponentTest {
+    @Test
+    public void testIsAliveComponentRemove() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        IntComponent c = e.add(IntComponent.class);
+
+        Assert.assertTrue(c.isAlive()); // sanity check
+        e.remove(IntComponent.class);
+        Assert.assertFalse(c.isAlive());
+    }
+
+    @Test
+    public void testIsAliveEntityRemove() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        IntComponent c = e.add(IntComponent.class);
+
+        Assert.assertTrue(c.isAlive()); // sanity check
+        system.removeEntity(e);
+        Assert.assertFalse(c.isAlive());
+    }
+
+    @Test
+    public void testIsAlivePostCompact() {
+        EntitySystem system = EntitySystem.create();
+        Entity e1 = system.addEntity();
+        Entity e2 = system.addEntity();
+        Entity e3 = system.addEntity();
+
+        IntComponent cd1 = e1.add(IntComponent.class); // removed
+        IntComponent cd2 = e2.add(IntComponent.class); // will shift over
+        IntComponent cd3 = e3.add(IntComponent.class); // will shift over
+
+        cd2.setOwner(e2);
+        cd3.setOwner(e3);
+
+        int v2 = cd2.getVersion();
+        int v3 = cd3.getVersion();
+
+        e1.remove(IntComponent.class);
+        system.compact(); // since e1's component was moved, this shifts e2 and e3
+
+        // verify all state of the component
+        Assert.assertFalse(cd1.isAlive());
+
+        Assert.assertTrue(cd2.isAlive());
+        Assert.assertFalse(cd2.isFlyweight());
+        Assert.assertSame(e2, cd2.getEntity());
+        Assert.assertSame(e2, cd2.getOwner());
+        Assert.assertEquals(v2, cd2.getVersion());
+
+        Assert.assertTrue(cd3.isAlive());
+        Assert.assertFalse(cd3.isFlyweight());
+        Assert.assertSame(e3, cd3.getEntity());
+        Assert.assertSame(e3, cd3.getOwner());
+        Assert.assertEquals(v3, cd3.getVersion());
+    }
+
+    @Test
+    public void testNewlyAddedComponentState() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        IntComponent c = e.add(IntComponent.class);
+
+        Assert.assertTrue(c.isAlive());
+        Assert.assertFalse(c.isFlyweight());
+        Assert.assertNull(c.getOwner());
+        Assert.assertSame(system, c.getEntitySystem());
+        Assert.assertSame(e, c.getEntity());
+    }
+
+    @Test
+    public void testIsAlivePostNoopCompact() {
+        EntitySystem system = EntitySystem.create();
+        Entity e1 = system.addEntity();
+        Entity e2 = system.addEntity();
+
+        e1.add(IntComponent.class);
+        e2.add(IntComponent.class);
+        IntComponent cd = e2.get(IntComponent.class);
+
+        Assert.assertTrue(cd.isAlive()); // sanity check
+        Assert.assertFalse(cd.isFlyweight());
+        system.compact(); // no changes
+        Assert.assertTrue(cd.isAlive());
+        Assert.assertFalse(cd.isFlyweight());
+    }
+
+    @Test
+    public void testVersionUpdate() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent cd = e.add(IntComponent.class);
+
+        Assert.assertEquals(0, cd.getVersion());
+        cd.updateVersion();
+        Assert.assertEquals(1, cd.getVersion());
+    }
+
+    @Test
+    public void testAutomaticVersionUpdate() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent cd = e.add(IntComponent.class);
+
+        int originalVersion = cd.getVersion();
+        cd.setInt(1500);
+        Assert.assertFalse(originalVersion == cd.getVersion());
+    }
+
+    @Test
+    public void testUniqueVersionUpdate() {
+        EntitySystem system = EntitySystem.create();
+        IntComponent cd1 = system.addEntity().add(IntComponent.class);
+        IntComponent cd2 = system.addEntity().add(IntComponent.class);
+
+        Assert.assertEquals(0, cd1.getVersion());
+        Assert.assertEquals(1, cd2.getVersion());
+
+        cd1.updateVersion();
+        cd2.updateVersion();
+
+        Assert.assertEquals(2, cd1.getVersion());
+        Assert.assertEquals(3, cd2.getVersion());
+
+        // assert unique version after a re-add
+        Assert.assertEquals(4, cd1.getEntity().add(IntComponent.class).getVersion());
+    }
+
+    @Test
+    public void testInvalidComponentVersion() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent cd = e.add(IntComponent.class);
+        e.remove(IntComponent.class);
+
+        // sanity check
+        Assert.assertEquals(0, cd.getIndex());
+
+        int oldVersion = cd.getVersion();
+        Assert.assertTrue(oldVersion < 0);
+        cd.updateVersion();
+        Assert.assertEquals(oldVersion, cd.getVersion());
+    }
+
+    @Test
+    public void testBeanMethodInvocation() {
+        EntitySystem system = EntitySystem.create();
+        Entity e1 = system.addEntity();
+        ComplexComponent c1 = e1.add(ComplexComponent.class);
+
+        CustomProperty.Bletch b = new CustomProperty.Bletch();
+        b.value = 19;
+        c1.setBletch(b);
+        c1.setFactoryFloat(23.2f);
+        c1.setLong(4000L);
+        c1.setNamedParamSetter(true);
+        c1.setParams((short) 4, (short) 5);
+        c1.setFloat(2.0f);
+        c1.setInt(140);
+
+        Assert.assertEquals(19, c1.hasBletch().value);
+        Assert.assertEquals(23.2f, c1.getFactoryFloat(), 0.00001f);
+        Assert.assertEquals(4000L, c1.getLong());
+        Assert.assertTrue(c1.isNamedParamGetter());
+        Assert.assertEquals((short) 4, c1.getParam1());
+        Assert.assertEquals((short) 5, c1.getParam2());
+        Assert.assertEquals(2.0f, c1.getFloat(), 0.00001f);
+        Assert.assertEquals(140, c1.getInt());
+
+        // add a second component and make sure things didn't get goofed up
+        Entity e2 = system.addEntity();
+        ComplexComponent c2 = e2.add(ComplexComponent.class);
+
+        Assert.assertEquals(14, c2.hasBletch().value);
+        Assert.assertEquals(FloatPropertyFactory.DEFAULT, c2.getFactoryFloat(), 0.00001f);
+        Assert.assertEquals(Long.MAX_VALUE, c2.getLong());
+        Assert.assertFalse(c2.isNamedParamGetter());
+        Assert.assertEquals((short) 0, c2.getParam1());
+        Assert.assertEquals((short) 0, c2.getParam2());
+        Assert.assertEquals(0f, c2.getFloat(), 0.00001f);
+        Assert.assertEquals(0, c2.getInt());
+    }
+
+    @Test
+    public void testFlyweightIsAliveAfterRemoval() {
+        EntitySystem system = EntitySystem.create();
+
+        IntComponent c1 = system.addEntity().add(IntComponent.class);
+        IntComponent c2 = system.addEntity().add(IntComponent.class);
+
+        ComponentIterator it = system.fastIterator();
+        IntComponent flyweight = it.addRequired(IntComponent.class);
+
+        int count = 0;
+        while (it.next()) {
+            flyweight.getEntity().remove(IntComponent.class);
+            Assert.assertFalse(flyweight.isAlive());
+            count++;
+        }
+
+        Assert.assertFalse(c1.isAlive());
+        Assert.assertFalse(c2.isAlive());
+        Assert.assertEquals(2, count);
+    }
+}

entreri-core/src/test/java/com/lhkbob/entreri/EntitySystemTest.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.components.ComplexComponent;
+import com.lhkbob.entreri.components.FloatComponent;
+import com.lhkbob.entreri.components.IntComponent;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class EntitySystemTest {
+    @Test
+    public void testAddEntity() {
+        // There really isn't much to test with this one, everything else
+        // is validated by other tests in this package
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        int componentCount = 0;
+        for (@SuppressWarnings("unused") Component c : e) {
+            componentCount++;
+        }
+
+        Assert.assertEquals(0, componentCount);
+        Assert.assertEquals(system, e.getEntitySystem());
+        Assert.assertTrue(e.isAlive());
+
+        int entityCount = 0;
+        for (Entity entity : system) {
+            entityCount++;
+            Assert.assertSame(e, entity);
+        }
+        Assert.assertEquals(1, entityCount);
+    }
+
+    @Test
+    public void testAddEntityFromTemplate() {
+        EntitySystem system = EntitySystem.create();
+        Entity template = system.addEntity();
+
+        IntComponent tc1 = template.add(IntComponent.class);
+        tc1.setInt(2);
+        FloatComponent tc2 = template.add(FloatComponent.class);
+        tc2.setFloat(3f);
+
+        Entity fromTemplate = system.addEntity(template);
+        IntComponent c1 = fromTemplate.get(IntComponent.class);
+        FloatComponent c2 = fromTemplate.get(FloatComponent.class);
+
+        Assert.assertEquals(2, c1.getInt());
+        Assert.assertEquals(3f, c2.getFloat(), .0001f);
+        Assert.assertFalse(c1.equals(tc1));
+        Assert.assertFalse(c2.equals(tc2));
+        Assert.assertNotSame(template, fromTemplate);
+    }
+
+    @Test
+    public void testAddEntityFromTemplateInAnotherSystem() {
+        EntitySystem system1 = EntitySystem.create();
+        Entity template = system1.addEntity();
+
+        IntComponent tc1 = template.add(IntComponent.class);
+        tc1.setInt(2);
+        FloatComponent tc2 = template.add(FloatComponent.class);
+        tc2.setFloat(3f);
+
+        EntitySystem system2 = EntitySystem.create();
+        Entity fromTemplate = system2.addEntity(template);
+        IntComponent c1 = fromTemplate.get(IntComponent.class);
+        FloatComponent c2 = fromTemplate.get(FloatComponent.class);
+
+        Assert.assertEquals(2, c1.getInt());
+        Assert.assertEquals(3f, c2.getFloat(), .0001f);
+        Assert.assertFalse(c1.equals(tc1));
+        Assert.assertFalse(c2.equals(tc2));
+        Assert.assertNotSame(template, fromTemplate);
+    }
+
+    @Test
+    public void testRemoveEntity() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent c = e.add(IntComponent.class);
+
+        system.removeEntity(e);
+        Assert.assertFalse(e.isAlive());
+        Assert.assertFalse(c.isAlive());
+        Assert.assertEquals(1, e.getId()); // it's id should remain unchanged
+
+        Assert.assertFalse(system.iterator().hasNext());
+    }
+
+    @Test
+    public void testIteratorRemoveEntity() {
+        EntitySystem system = EntitySystem.create();
+        List<Entity> original = new ArrayList<Entity>();
+
+        List<Entity> removed = new ArrayList<Entity>();
+
+        for (int i = 0; i < 10; i++) {
+            original.add(system.addEntity());
+        }
+
+        Iterator<Entity> it = system.iterator();
+        while (it.hasNext()) {
+            removed.add(it.next());
+            it.remove();
+        }
+
+        Assert.assertEquals(original, removed);
+    }
+
+    @Test
+    public void testIteratorExternalRemoveEntity() {
+        EntitySystem system = EntitySystem.create();
+        List<Entity> original = new ArrayList<Entity>();
+
+        List<Entity> removed = new ArrayList<Entity>();
+
+        for (int i = 0; i < 10; i++) {
+            original.add(system.addEntity());
+        }
+
+        Iterator<Entity> it = system.iterator();
+        while (it.hasNext()) {
+            Entity e = it.next();
+            removed.add(e);
+            system.removeEntity(e);
+        }
+
+        Assert.assertEquals(original, removed);
+    }
+
+    @Test
+    public void testCompactNoOp() {
+        EntitySystem system = EntitySystem.create();
+        for (int i = 0; i < 5; i++) {
+            system.addEntity().add(ComplexComponent.class);
+        }
+
+        system.compact();
+
+        int count = 0;
+        Iterator<Entity> it = system.iterator();
+        while (it.hasNext()) {
+            Entity e = it.next();
+            Assert.assertNotNull(e.get(ComplexComponent.class));
+            count++;
+        }
+
+        Assert.assertEquals(5, count);
+    }
+
+    @Test
+    public void testCompactRepairRemoves() {
+        EntitySystem system = EntitySystem.create();
+        List<Entity> es = new ArrayList<Entity>();
+        List<Float> cs = new ArrayList<Float>();
+        for (int i = 0; i < 100; i++) {
+            es.add(system.addEntity());
+            ComplexComponent c = es.get(es.size() - 1).add(ComplexComponent.class);
+            float f = (float) Math.random();
+            float f2 = (float) Math.random();
+            c.setFloat(f);
+            c.setFactoryFloat(f2);
+
+            cs.add(f);
+            cs.add(f2);
+        }
+
+        int i = 0;
+        Iterator<Entity> it = es.iterator();
+        Iterator<Float> ft = cs.iterator();
+        while (it.hasNext()) {
+            Entity e = it.next();
+            ft.next(); // always advance once
+            if (i % 2 == 0) {
+                it.remove();
+                system.removeEntity(e);
+
+                ft.remove();
+                ft.next();
+                ft.remove(); // remove 2nd element
+            } else {
+                ft.next(); // advance past 2nd element
+            }
+            i++;
+        }
+
+        system.compact();
+
+        it = es.iterator();
+        Iterator<Entity> si = system.iterator();
+        ft = cs.iterator();
+        while (it.hasNext() && si.hasNext()) {
+            Entity e = si.next();
+            Assert.assertEquals(it.next(), e);
+            Assert.assertEquals(ft.next(), e.get(ComplexComponent.class).getFloat(),
+                                .0001f);
+            Assert.assertEquals(ft.next(),
+                                e.get(ComplexComponent.class).getFactoryFloat(), .0001f);
+        }
+        Assert.assertFalse(it.hasNext());
+        Assert.assertFalse(si.hasNext());
+    }
+
+    @Test
+    public void testCompactAddRemoveRepair() {
+        EntitySystem system = EntitySystem.create();
+        List<Entity> es = new ArrayList<Entity>();
+        List<Float> cs = new ArrayList<Float>();
+        for (int i = 0; i < 100; i++) {
+            es.add(system.addEntity());
+            ComplexComponent c = es.get(es.size() - 1).add(ComplexComponent.class);
+            float f = (float) Math.random();
+            float f2 = (float) Math.random();
+            c.setFloat(f);
+            c.setFactoryFloat(f2);
+
+            cs.add(f);
+            cs.add(f2);
+        }
+
+        // remove a bunch of components from the entities
+        int i = 0;
+        Iterator<Entity> it = es.iterator();
+        while (it.hasNext()) {
+            Entity e = it.next();
+            if (i % 2 == 0) {
+                e.remove(ComplexComponent.class);
+            }
+            i++;
+        }
+
+        // now add back in component values for the previously removed entities
+        i = 0;
+        it = es.iterator();
+        Iterator<Float> ft = cs.iterator();
+        while (it.hasNext()) {
+            Entity e = it.next();
+            ComplexComponent c = e.get(ComplexComponent.class);
+
+            float f = ft.next();
+            float f2 = ft.next();
+
+            if (c == null) {
+                c = e.add(ComplexComponent.class);
+                c.setFloat(f);
+                c.setFactoryFloat(f2);
+            }
+            i++;
+        }
+
+        system.compact();
+
+        it = es.iterator();
+        Iterator<Entity> si = system.iterator();
+        ft = cs.iterator();
+        while (it.hasNext() && si.hasNext()) {
+            Entity e = si.next();
+            Assert.assertEquals(it.next(), e);
+            Assert.assertEquals(ft.next(), e.get(ComplexComponent.class).getFloat(),
+                                .0001f);
+            Assert.assertEquals(ft.next(),
+                                e.get(ComplexComponent.class).getFactoryFloat(), .0001f);
+        }
+        Assert.assertFalse(it.hasNext());
+        Assert.assertFalse(si.hasNext());
+    }
+}

entreri-core/src/test/java/com/lhkbob/entreri/EntityTest.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.components.FloatComponent;
+import com.lhkbob.entreri.components.IntComponent;
+import com.lhkbob.entreri.components.RequiresAComponent;
+import com.lhkbob.entreri.components.RequiresBComponent;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+public class EntityTest {
+    @Test
+    public void testGetEntitySystem() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        Assert.assertEquals(system, e.getEntitySystem());
+    }
+
+    @Test
+    public void testAddWithRequiredComponents() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        RequiresBComponent rb = e.add(RequiresBComponent.class);
+        RequiresAComponent ra = e.get(RequiresAComponent.class);
+        Assert.assertNotNull(ra);
+        Assert.assertNotNull(e.get(IntComponent.class));
+        Assert.assertNotNull(e.get(FloatComponent.class));
+
+        Assert.assertSame(rb, ra.getOwner());
+        Assert.assertSame(ra, e.get(IntComponent.class).getOwner());
+        Assert.assertSame(ra, e.get(FloatComponent.class).getOwner());
+
+        e.remove(RequiresBComponent.class);
+
+        Assert.assertNull(e.get(RequiresAComponent.class));
+        Assert.assertNull(e.get(IntComponent.class));
+        Assert.assertNull(e.get(FloatComponent.class));
+    }
+
+    @Test
+    public void testAddWithRequiredComponentsAlreadyPresent() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        IntComponent ci = e.add(IntComponent.class);
+
+        RequiresAComponent ra = e.add(RequiresAComponent.class);
+        Assert.assertSame(ci, e.get(IntComponent.class));
+        Assert.assertNotNull(e.get(FloatComponent.class));
+
+        Assert.assertNull(ci.getOwner());
+        Assert.assertSame(ra, e.get(FloatComponent.class).getOwner());
+
+        e.remove(RequiresAComponent.class);
+
+        Assert.assertSame(ci, e.get(IntComponent.class));
+        Assert.assertNull(e.get(FloatComponent.class));
+    }
+
+    @Test
+    public void testAddRemoveComponent() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        IntComponent c = e.add(IntComponent.class);
+
+        c.setInt(1);
+        Assert.assertEquals(1, c.getInt());
+
+        Assert.assertEquals(c, e.get(IntComponent.class));
+        Assert.assertEquals(1, e.get(IntComponent.class).getInt());
+
+        Assert.assertTrue(e.remove(IntComponent.class));
+
+        Assert.assertNull(e.get(IntComponent.class));
+        Assert.assertNull(e.get(FloatComponent.class));
+
+        Assert.assertFalse(c.isAlive());
+    }
+
+    @Test
+    public void testReAddComponent() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        IntComponent c = e.add(IntComponent.class);
+        IntComponent c2 = e.add(IntComponent.class);
+
+        Assert.assertNotSame(c, c2);
+        Assert.assertFalse(c.isAlive());
+        Assert.assertTrue(c2.isAlive());
+        Assert.assertSame(c2, e.get(IntComponent.class));
+    }
+
+    @Test
+    public void testGetComponent() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+
+        IntComponent c = e.add(IntComponent.class);
+        c.setInt(2);
+
+        int count = 0;
+        for (Entity e2 : system) {
+            Assert.assertSame(e, e2);
+            Assert.assertSame(c, e2.get(IntComponent.class));
+            Assert.assertEquals(2, e2.get(IntComponent.class).getInt());
+            count++;
+        }
+
+        Assert.assertEquals(1, count);
+    }
+
+    @Test
+    public void testIterateComponents() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent ic = e.add(IntComponent.class);
+        FloatComponent fc = e.add(FloatComponent.class);
+
+        boolean intFound = false;
+        boolean floatFound = false;
+        for (Component c : e) {
+            if (ic == c) {
+                Assert.assertFalse(intFound);
+                intFound = true;
+            } else if (fc == c) {
+                Assert.assertFalse(floatFound);
+                floatFound = true;
+            } else {
+                Assert.fail();
+            }
+        }
+
+        Assert.assertTrue(intFound);
+        Assert.assertTrue(floatFound);
+    }
+
+    @Test
+    public void testIterateRemoveComponent() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent ic = e.add(IntComponent.class);
+        FloatComponent fc = e.add(FloatComponent.class);
+
+        Iterator<Component> it = e.iterator();
+        while (it.hasNext()) {
+            Component c = it.next();
+            if (c.getType().equals(IntComponent.class)) {
+                Assert.assertSame(ic, c);
+                it.remove();
+
+                Assert.assertNull(c.getEntity());
+                Assert.assertEquals(0, c.getIndex());
+            } else {
+                Assert.assertSame(fc, c);
+            }
+        }
+
+        Assert.assertNull(e.get(IntComponent.class));
+        Assert.assertNotNull(e.get(FloatComponent.class));
+        Assert.assertFalse(ic.isAlive());
+    }
+}

entreri-core/src/test/java/com/lhkbob/entreri/OwnerTest.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.components.FloatComponent;
+import com.lhkbob.entreri.components.IntComponent;
+import junit.framework.Assert;
+import org.junit.Test;
+
+public class OwnerTest {
+
+    @Test
+    public void testEntitySetOwner() {
+        EntitySystem system = EntitySystem.create();
+
+        Entity e1 = system.addEntity();
+        Entity e2 = system.addEntity();
+
+        Assert.assertNull(e1.getOwner());
+        Assert.assertNull(e2.getOwner());
+
+        e1.setOwner(e2);
+
+        Assert.assertSame(e2, e1.getOwner());
+        Assert.assertNull(e2.getOwner());
+
+        IntComponent c2 = e2.add(IntComponent.class);
+        e1.setOwner(c2);
+
+        Assert.assertSame(c2, e1.getOwner());
+    }
+
+    @Test
+    public void testComponentSetOwner() {
+        EntitySystem system = EntitySystem.create();
+
+        Entity e1 = system.addEntity();
+        Entity e2 = system.addEntity();
+
+        IntComponent c1a = e1.add(IntComponent.class);
+        FloatComponent c1b = e1.add(FloatComponent.class);
+
+        Assert.assertNull(c1a.getOwner());
+        Assert.assertNull(c1b.getOwner());
+
+        c1a.setOwner(c1b);
+
+        Assert.assertSame(c1b, c1a.getOwner());
+        Assert.assertNull(c1b.getOwner());
+
+        c1a.setOwner(e2);
+
+        Assert.assertSame(e2, c1a.getOwner());
+    }
+
+    @Test
+    public void testOwnedEntityRemoval() {
+        EntitySystem system = EntitySystem.create();
+        Entity e1 = system.addEntity();
+
+        final boolean[] revoked = new boolean[1];
+
+        e1.setOwner(new Owner() {
+            @Override
+            public Owner notifyOwnershipGranted(Ownable obj) {
+                return this;
+            }
+
+            @Override
+            public void notifyOwnershipRevoked(Ownable obj) {
+                revoked[0] = true;
+            }
+        });
+
+        system.removeEntity(e1);
+        Assert.assertTrue(revoked[0]);
+    }
+
+    @Test
+    public void testOwnedComponentRemoval() {
+        EntitySystem system = EntitySystem.create();
+        Entity e1 = system.addEntity();
+        IntComponent c1 = e1.add(IntComponent.class);
+
+        final boolean[] revoked = new boolean[1];
+
+        c1.setOwner(new Owner() {
+            @Override
+            public Owner notifyOwnershipGranted(Ownable obj) {
+                return this;
+            }
+
+            @Override
+            public void notifyOwnershipRevoked(Ownable obj) {
+                revoked[0] = true;
+            }
+        });
+
+        e1.remove(IntComponent.class);
+        Assert.assertTrue(revoked[0]);
+    }
+
+    @Test
+    public void testOwnedComponentAdd() {
+        EntitySystem system = EntitySystem.create();
+        Entity e1 = system.addEntity();
+
+        IntComponent c1 = e1.add(IntComponent.class);
+
+        final boolean[] revoked = new boolean[1];
+
+        c1.setOwner(new Owner() {
+            @Override
+            public Owner notifyOwnershipGranted(Ownable obj) {
+                return this;
+            }
+
+            @Override
+            public void notifyOwnershipRevoked(Ownable obj) {
+                revoked[0] = true;
+            }
+        });
+
+        IntComponent c2 = e1.add(IntComponent.class);
+        Assert.assertTrue(revoked[0]);
+        Assert.assertNull(c2.getOwner());
+    }
+
+    @Test
+    public void testEntityRemovalCleanup() {
+        EntitySystem system = EntitySystem.create();
+
+        Entity owner = system.addEntity();
+
+        Entity spare = system.addEntity();
+        IntComponent ownedC = spare.add(IntComponent.class);
+        ownedC.setOwner(owner);
+
+        Entity ownedE = system.addEntity();
+        ownedE.setOwner(owner);
+
+        system.removeEntity(owner);
+        Assert.assertFalse(ownedC.isAlive());
+        Assert.assertFalse(ownedE.isAlive());
+        Assert.assertTrue(spare.isAlive());
+    }
+
+    @Test
+    public void testComponentRemovalCleanup() {
+        EntitySystem system = EntitySystem.create();
+
+        Entity e1 = system.addEntity();
+
+        IntComponent owner = e1.add(IntComponent.class);
+        FloatComponent ownedC = e1.add(FloatComponent.class);
+        ownedC.setOwner(owner);
+
+        Entity ownedE = system.addEntity();
+        ownedE.setOwner(owner);
+
+        e1.remove(IntComponent.class);
+        Assert.assertFalse(ownedC.isAlive());
+        Assert.assertFalse(ownedE.isAlive());
+    }
+
+    @Test
+    public void testComplexOwnershipHierarchyCleanup() {
+        EntitySystem system = EntitySystem.create();
+
+        Entity e1 = system.addEntity();
+        IntComponent c1 = e1.add(IntComponent.class);
+
+        Entity e2 = system.addEntity();
+        IntComponent c2 = e2.add(IntComponent.class);
+
+        Entity e3 = system.addEntity();
+        IntComponent c3 = e3.add(IntComponent.class);
+
+        e1.setOwner(e2);
+        e2.setOwner(e3);
+        e3.setOwner(c1);
+        c1.setOwner(c2);
+        c2.setOwner(c3);
+
+        e3.remove(IntComponent.class);
+
+        Assert.assertFalse(e1.isAlive());
+        Assert.assertFalse(e2.isAlive());
+        Assert.assertFalse(e3.isAlive());
+        Assert.assertFalse(c1.isAlive());
+        Assert.assertFalse(c2.isAlive());
+        Assert.assertFalse(c3.isAlive());
+    }
+
+    @Test
+    public void testComponentOwningParentEntityRemoval() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent c = e.add(IntComponent.class);
+
+        e.setOwner(c);
+
+        system.removeEntity(e);
+        Assert.assertFalse(e.isAlive());
+        Assert.assertFalse(c.isAlive());
+    }
+
+    @Test
+    public void testFlyweightComponentOwnership() {
+        EntitySystem system = EntitySystem.create();
+        Entity e = system.addEntity();
+        IntComponent c = e.add(IntComponent.class);
+
+        Entity e2 = system.addEntity();
+
+        ComponentIterator it = system.fastIterator();
+        IntComponent flyweight = it.addRequired(IntComponent.class);
+        it.next();
+
+        flyweight.setOwner(e2);
+        Assert.assertSame(e2, flyweight.getOwner());
+        Assert.assertSame(e2, c.getOwner());
+
+        e.setOwner(flyweight);
+        Assert.assertSame(c, e.getOwner());
+    }
+}

entreri-core/src/test/java/com/lhkbob/entreri/PropertyFactoryTest.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.Attributes;
+import com.lhkbob.entreri.property.Clone;
+import com.lhkbob.entreri.property.Clone.Policy;
+import com.lhkbob.entreri.property.DoubleProperty;
+import com.lhkbob.entreri.property.DoubleProperty.DefaultDouble;
+import com.lhkbob.entreri.property.ObjectProperty;
+import junit.framework.Assert;
+import org.junit.Test;
+
+@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
+public class PropertyFactoryTest {
+    /*
+     * ObjectProperty fields for Attributes creation
+     */
+    private ObjectProperty<Object> objectPropertyNoPolicy;
+
+    @Clone(Policy.DISABLE)
+    private ObjectProperty<Object> objectPropertyDisabled;
+
+    @Clone(Policy.JAVA_DEFAULT)
+    private ObjectProperty<Object> objectPropertyDefault;
+
+    @Clone(Policy.INVOKE_CLONE)
+    private ObjectProperty<Object> objectPropertyInvoke;
+
+    /*
+     * DoubleProperty fields for Attributes creation
+     */
+    @DefaultDouble(1.0)
+    private DoubleProperty doublePropertyNoPolicy;
+
+    @DefaultDouble(2.0)
+    @Clone(Policy.DISABLE)
+    private DoubleProperty doublePropertyDisabled;
+
+    @Clone(Policy.JAVA_DEFAULT)
+    private DoubleProperty doublePropertyDefault;
+
+    @Clone(Policy.INVOKE_CLONE)
+    private DoubleProperty doublePropertyInvoke;
+
+    private Attributes createAttributes(String fieldName) throws Exception {
+        return new Attributes(getClass().getDeclaredField(fieldName).getAnnotations());
+    }
+
+    @Test
+    public void testObjectPropertyCloneNoPolicy() throws Exception {
+        ObjectProperty.Factory factory = new ObjectProperty.Factory(
+                createAttributes("objectPropertyNoPolicy"));
+
+        ObjectProperty p1 = factory.create();
+        ObjectProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        Object val = new Object();
+        p1.set(val, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertNull(p2.get(0));
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertSame(val, p2.get(0));
+    }
+
+    @Test
+    public void testObjectPropertyCloneDisabled() throws Exception {
+        ObjectProperty.Factory factory = new ObjectProperty.Factory(
+                createAttributes("objectPropertyDisabled"));
+
+        ObjectProperty p1 = factory.create();
+        ObjectProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        Object val = new Object();
+        p1.set(val, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertNull(p2.get(0));
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertNull(p2.get(0));
+    }
+
+    @Test
+    public void testObjectPropertyCloneJavaDefault() throws Exception {
+        ObjectProperty.Factory factory = new ObjectProperty.Factory(
+                createAttributes("objectPropertyDefault"));
+
+        ObjectProperty p1 = factory.create();
+        ObjectProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        Object val = new Object();
+        p1.set(val, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertNull(p2.get(0));
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertSame(val, p2.get(0));
+    }
+
+    @Test
+    public void testObjectPropertyCloneInvoke() throws Exception {
+        ObjectProperty.Factory factory = new ObjectProperty.Factory(
+                createAttributes("objectPropertyInvoke"));
+
+        ObjectProperty p1 = factory.create();
+        ObjectProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        CloneObject val = new CloneObject(5);
+        p1.set(val, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertNull(p2.get(0));
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertSame(val, p1.get(0));
+        Assert.assertNotSame(val, p2.get(0));
+        Assert.assertNotNull(p2.get(0));
+        Assert.assertEquals(5, ((CloneObject) p2.get(0)).foo);
+    }
+
+    @Test
+    public void testPrimitivePropertyCloneNoPolicy() throws Exception {
+        DoubleProperty.Factory factory = new DoubleProperty.Factory(
+                createAttributes("doublePropertyNoPolicy"));
+
+        DoubleProperty p1 = factory.create();
+        DoubleProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        p1.set(4.0, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(1.0, p2.get(0), 0.0001);
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(4.0, p2.get(0), 0.0001);
+    }
+
+    @Test
+    public void testPrimitivePropertyCloneDisabled() throws Exception {
+        DoubleProperty.Factory factory = new DoubleProperty.Factory(
+                createAttributes("doublePropertyDisabled"));
+
+        DoubleProperty p1 = factory.create();
+        DoubleProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        p1.set(4.0, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(2.0, p2.get(0), 0.0001);
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(2.0, p2.get(0), 0.0001);
+    }
+
+    @Test
+    public void testPrimitivePropertyCloneJavaDefault() throws Exception {
+        DoubleProperty.Factory factory = new DoubleProperty.Factory(
+                createAttributes("doublePropertyDefault"));
+
+        DoubleProperty p1 = factory.create();
+        DoubleProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        p1.set(4.0, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(0.0, p2.get(0), 0.0001);
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(4.0, p2.get(0), 0.0001);
+    }
+
+    @Test
+    public void testPrimitivePropertyCloneInvoke() throws Exception {
+        DoubleProperty.Factory factory = new DoubleProperty.Factory(
+                createAttributes("doublePropertyInvoke"));
+
+        DoubleProperty p1 = factory.create();
+        DoubleProperty p2 = factory.create();
+
+        factory.setDefaultValue(p1, 0);
+        factory.setDefaultValue(p2, 0);
+
+        p1.set(4.0, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(0.0, p2.get(0), 0.0001);
+
+        factory.clone(p1, 0, p2, 0);
+
+        Assert.assertEquals(4.0, p1.get(0), 0.0001);
+        Assert.assertEquals(4.0, p2.get(0), 0.0001);
+    }
+
+    public static class CloneObject implements Cloneable {
+        public final int foo;
+
+        public CloneObject(int foo) {
+            this.foo = foo;
+        }
+
+        @Override
+        public CloneObject clone() {
+            return new CloneObject(foo);
+        }
+    }
+}

entreri-core/src/test/java/com/lhkbob/entreri/SchedulerTest.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.task.Job;
+import com.lhkbob.entreri.task.Result;
+import com.lhkbob.entreri.task.Task;
+import org.junit.Assert;
+import org.junit.Test;
+
+// NOTE: this does not test the thread-safety aspects of a job, because
+// it's a little too difficult to right a unit test for that
+public class SchedulerTest {
+    @Test
+    public void testResultsReportedToFutureTasksOnly() {
+        EntitySystem system = EntitySystem.create();
+
+        ResultReportingTask t1 = new ResultReportingTask(null, new ResultA(),
+                                                         new ResultA(), new ResultB());
+        ResultAListeningTask t2 = new ResultAListeningTask(null);
+        ResultBListeningTask t3 = new ResultBListeningTask(null);
+        AllResultListeningTask t4 = new AllResultListeningTask(null);
+
+        // note the reordering of the tasks, t1 doesn't report results until after
+        // t2 and t3 are already run
+        Job j = system.getScheduler().createJob("test", t2, t3, t1, t4);
+
+        j.run();
+        Assert.assertEquals(0, t2.aReceiveCount);
+        Assert.assertEquals(0, t3.bReceiveCount);
+        Assert.assertEquals(2, t4.aReceiveCount);
+        Assert.assertEquals(1, t4.bReceiveCount);
+        Assert.assertEquals(3, t4.receiveCount);
+
+        // run the job a second time to make sure resubmitting results doesn't
+        // screw anything up after a reset
+        j.run();
+        Assert.assertEquals(0, t2.aReceiveCount);
+        Assert.assertEquals(0, t3.bReceiveCount);
+        Assert.assertEquals(2, t4.aReceiveCount);
+        Assert.assertEquals(1, t4.bReceiveCount);
+        Assert.assertEquals(3, t4.receiveCount);
+    }
+
+    @Test
+    public void testPostProcessTasksInvoked() {
+        EntitySystem system = EntitySystem.create();
+
+        BasicTask t1 = new BasicTask(null);
+        BasicTask t2 = new BasicTask(t1);
+        BasicTask t3 = new BasicTask(t2);
+
+        Job j = system.getScheduler().createJob("test", t3);
+        j.run();
+
+        Assert.assertTrue(t1.invoked);
+        Assert.assertTrue(t2.invoked);
+        Assert.assertTrue(t3.invoked);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMultipleSingletonResultsReported() {
+        EntitySystem system = EntitySystem.create();
+
+        ResultReportingTask t1 = new ResultReportingTask(null, new ResultA(),
+                                                         new ResultB(), new ResultB());
+
+        Job j = system.getScheduler().createJob("test", t1);
+        j.run();
+    }
+
+    @Test
+    public void testResultsReported() {
+        EntitySystem system = EntitySystem.create();
+
+        ResultReportingTask t1 = new ResultReportingTask(null, new ResultA(),
+                                                         new ResultA(), new ResultB());
+        ResultAListeningTask t2 = new ResultAListeningTask(null);
+        ResultBListeningTask t3 = new ResultBListeningTask(null);
+        AllResultListeningTask t4 = new AllResultListeningTask(null);
+
+        Job j = system.getScheduler().createJob("test", t1, t2, t3, t4);
+
+        j.run();
+        Assert.assertEquals(2, t2.aReceiveCount);
+        Assert.assertEquals(1, t3.bReceiveCount);
+        Assert.assertEquals(2, t4.aReceiveCount);
+        Assert.assertEquals(1, t4.bReceiveCount);
+        Assert.assertEquals(3, t4.receiveCount);
+
+        // run the job a second time to make sure resubmitting results doesn't
+        // screw anything up after a reset
+        j.run();
+        Assert.assertEquals(2, t2.aReceiveCount);
+        Assert.assertEquals(1, t3.bReceiveCount);
+        Assert.assertEquals(2, t4.aReceiveCount);
+        Assert.assertEquals(1, t4.bReceiveCount);
+        Assert.assertEquals(3, t4.receiveCount);
+    }
+
+    @Test
+    public void testResetInvoked() {
+        EntitySystem system = EntitySystem.create();
+
+        BasicTask t1 = new BasicTask(null);
+        Job j = system.getScheduler().createJob("test", t1);
+        j.run();
+
+        Assert.assertTrue(t1.invoked);
+        Assert.assertTrue(t1.reset);
+    }
+
+    private static class ResultA extends Result {
+        @Override
+        public boolean isSingleton() {
+            return false;
+        }
+    }
+
+    private static class ResultB extends Result {
+        @Override
+        public boolean isSingleton() {
+            return true;
+        }
+    }
+
+    private static class BasicTask implements Task {
+        boolean invoked;
+        boolean reset;
+
+        final BasicTask postProcess;
+
+        public BasicTask(BasicTask postProcess) {
+            this.postProcess = postProcess;
+        }
+
+        @Override
+        public Task process(EntitySystem system, Job job) {
+            invoked = true;
+            return postProcess;
+        }
+
+        @Override
+        public void reset(EntitySystem system) {