Commits

Michael Ludwig committed 02ad253

Fix compilation errors in root entreri package:
* Define and document new Component, update AbstractComponent accordingly
* Fix ComponentRepository, Entity, and EntitySystem to use new definition of Component
* Update ComponentIterator to create flyweight instances instead of receiving them
* Update ownership API to properly handle flyweight components

Comments (0)

Files changed (10)

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

 package com.lhkbob.entreri;
 
 /**
- * <p/>
- * Component represents a grouping of reusable and related states that are added to an
- * {@link Entity}. The specific state of a component is stored
- * and defined in {@link ComponentData} implementations. This separation is to support
- * fast iteration over blocks of packed, managed memory. All of the component data is
- * packed into buffers or arrays for cache locality. A single ComponentData instance can
- * then be used to access multiple Components.
- * <p/>
- * Component instances represent the identity of the conceptual components, while
- * instances of ComponentData can be configured to read and write to specific components.
- * ComponentData's can change which component they reference multiple times throughout
- * their life time.
- * <p/>
- * Component implements both {@link com.lhkbob.entreri.Ownable} and {@link
- * com.lhkbob.entreri.Owner}. This can be used to create hierarchies of both components
- * and entities that share a lifetime. When a component is removed from an entity, all of
- * its owned objects are disowned. If any of them were entities or components, they are
- * also removed from the system.
+ * AbstractComponent is the base class used for all generated proxy implementations of
+ * component subtypes. It provides an implementation for all of the declared methods in
+ * Component as well as equals() and hashCode().
  *
- * @param <T> The ComponentData type defining the data of this component
- *
- * @author Michael Ludwig
+ * @param <T> The type of component the AbstractComponent is safely cast-able to
  */
-class AbstractComponent<T extends Component> implements Component {
-    private final ComponentRepository<T> owner;
+abstract class AbstractComponent<T extends Component> implements Component {
+    protected final ComponentRepository<T> owner;
 
     private int index;
     private int id;
 
 
     /**
-     * Create a new Component stored in the given ComponentRepository, at the given array
-     * position within the ComponentRepository.
+     * Create a new component stored in the given ComponentRepository.
      *
      * @param owner The ComponentRepository owner
      */
     public boolean isAlive() {
         // we have to check the index of the Component because the ComponentRepository
         // does not make sure the data's indices stay within bounds of the repository arrays
-        if (index != 0 && index < owner.getMaxComponentIndex()) {
-            return owner.getId(index) == id;
-        }
-        return false;
+        return index != 0 && index < owner.getMaxComponentIndex() &&
+               owner.getId(index) == id;
     }
 
     @Override
     }
 
     @Override
-    public void notifyOwnershipGranted(Ownable obj) {
+    public Owner notifyOwnershipGranted(Ownable obj) {
         owner.getOwnerDelegate(index).notifyOwnershipGranted(obj);
+        // make sure to return the canonical component at the current index
+        return owner.getComponent(index);
     }
 
     @Override
     }
 
     @Override
+    public Class<T> getType() {
+        return owner.getType();
+    }
+
+    @Override
+    public boolean isFlyweight() {
+        return !isAlive() || owner.getComponent(index) != this;
+    }
+
+    @Override
+    public int hashCode() {
+        if (isAlive()) {
+            return (getType().hashCode() + 17 * (getEntity().getId() + 31));
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof AbstractComponent)) {
+            return false;
+        }
+        AbstractComponent<?> a = (AbstractComponent<?>) o;
+        return a.owner == owner && a.index == index;
+    }
+
+    @Override
     public String toString() {
+        // FIXME improve toString() to include values for properties
         if (index == 0) {
             return "Component(" + getClass().getSimpleName() + ")";
         } else {

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

 
 /**
  * <p/>
- * ComponentData is used to define types of components that can be added to entities. For
- * performance reasons, the identity of a component is represented by instances of {@link
- * com.lhkbob.entreri.Component}. ComponentData instances are used as views into the data of the components.
- * This allows multiple instances to have their data packed together in primitive arrays
- * or direct memory, allowing for significantly faster iteration.
+ * Component represents a grouping of reusable and related states that are added to an
+ * {@link Entity}. An Entity's behavior is defined by the union of all components attached
+ * to it, and the system's configured tasks that process the entities. Tasks must be
+ * implemented and used in order to take advantage of a particular component
+ * configuration.
  * <p/>
+ * An entity can be considered as a collection of aspects represented by component
+ * instances. An entity can have at most one instance of a component type at a time,
+ * although over its lifetime the specific component instance may change.
  * <p/>
- * Additionally, by using a single ComponentData instance during the iteration, there is
- * no need to follow the usual object references needed for each instance.
+ * Logically a component definition is a set of named and typed properties, and a
+ * method-based API to get and set the values of each property. Specific types of
+ * component are defined by creating a sub-interface of Component. Using the {@link Named}
+ * and {@link SharedInstance} annotations and specific conventions the data properties of
+ * the component type are specified in the sub-interface. A declaration model similar to
+ * the Java Bean model is used and is outlined below:
  * <p/>
+ * <ol> <li>Non-void, zero-argument methods starting with 'get', 'is', and 'has' declare a
+ * property. The property type is inspected from the return type of the method. The
+ * property name is the method name minus the 'get'/'is'/'has' prefix with its first
+ * letter made lower-case. The {@link Named} annotation can be used to override the
+ * name.</li> <li>Void, single-argument methods starting with 'set' are assumed to be a
+ * setter corresponding to a property. The single parameter's type must equal the type the
+ * getter. The {@link Named} annotation can be applied to either the setter or the
+ * parameter to specify the property name.</li> <li>Void, multi-argument methods starting
+ * with 'set' are assumed to be a setter that assigns values to multiple property, one for
+ * each argument. Each argument must be annotated with {@link Named} to specify the
+ * property, and the argument type must equal the type of the matching property.</li>
+ * <li>Getters with void return types or arguments, setters without a return type or no
+ * arguments, and any other method not matching the conventions above will cause the
+ * system to throw an {@link IllegalComponentDefinitionException}.</li> </ol>
  * <p/>
- * ComponentData's are defined like any other class, but they are intended to be created
- * and configured by a {@link ComponentDataFactory}. These factories may impose certain
- * restrictions or requirements in what constructors or fields are valid. ComponentData
- * implementations can define a default ComponentDataFactory type with the {@link
- * DefaultFactory} annotation. By default ComponentData implementations are created using
- * The {@link ReflectionComponentDataFactory}
- *
- * @param <T> The self-referencing type of the ComponentData
+ * Internally, the entity system will generate proxy implementations of the component
+ * interfaces that implement the property getters and setters but store all of the values
+ * in {@link com.lhkbob.entreri.property.Property} instances of a compatible type. This
+ * allows iteration over components to have much better cache locality if the component is
+ * defined in terms of primitives or types that have specialized Property implementations
+ * that can pack and unpack an instance. The {@link SharedInstance} annotation can be
+ * added to the getter method of a property to specify that the {@link
+ * com.lhkbob.entreri.property.ShareableProperty} API should be leveraged by the generated
+ * class.
+ * <p/>
+ * The generated proxies will implement equals() and hashCode() based on their type and
+ * the id of their owning entity. The {@link ComponentIterator} class creates flyweight
+ * component instances whose identity changes as iteration proceeds; equals() and
+ * hashCode() will behave appropriately. This means that flyweight components should not
+ * be stored in collections that depend on equality or hashes. Flyweight components are
+ * used for performance reasons when iterating over the entities in a system with specific
+ * component configurations because the JVM does not need to load each unique component
+ * instance into the cache.
+ * <p/>
+ * Component implements both {@link com.lhkbob.entreri.Ownable} and {@link
+ * com.lhkbob.entreri.Owner}. This can be used to create hierarchies of both components
+ * and entities that share a lifetime. When a component is removed from an entity, all of
+ * its owned objects are disowned. If any of them were entities or components, they are
+ * also removed from the system.
  *
  * @author Michael Ludwig
  */
     public Entity getEntity();
 
     /**
+     * <p/>
+     * Get whether or not the component is still attached to an alive entity in an entity
+     * system. This will return false if it or its entity has been removed. If the
+     * component is a flyweight object used in iteration, it can also return false when
+     * iteration has terminated.
+     * <p/>
+     * Using property getters or setters on a dead component produces undefined behavior.
+     *
      * @return True if the component is still attached to an entity in the entity system,
      *         or false if it or its entity has been removed
      */
     public boolean isAlive();
 
     /**
-     * Get the underlying index of this component used to access its properties. In most
-     * cases you can just use {@link ComponentData#getIndex()} because you'll have a
-     * ComponentData instance on hand. However, it can be useful to use this method to
-     * access decorated data to avoid setting the component data to a particular component
-     * just to get its index.
+     * <p/>
+     * Get whether or not this particular instance is a flyweight component object. A
+     * flyweight instance is used by ComponentIterators to efficiently view the underlying
+     * component data iteratively. It is safe to set the owner of a flyweight component or
+     * to set the flyweight as an owner of another object. It will correctly register the
+     * canonical component as the owner.
+     * <p/>
+     * Components returned by {@link Entity#add(Class)}, {@link Entity#get(Class)}, and
+     * its other component returning functions will always return non-flyweight
+     * components. These components are the 'canonical' instances for that component type
+     * and entity.
+     *
+     * @return True if this component is a flyweight instance iterating over a list of
+     *         components
+     */
+    public boolean isFlyweight();
+
+    /**
+     * <p/>
+     * Get the underlying index of this component used to access its properties. For most
+     * uses, you should not need to use this method.  As an EntitySystem manages and
+     * compacts the component data storage, a component's index can change. The index
+     * should be used to access decorated properties, which will be kept in-sync with any
+     * compactions.
+     * <p/>
+     * Additionally, some component instances may be flyweight objects used for iteration.
+     * In this case, every iteration will update its index and the component object's
+     * identity will change as well. See {@link ComponentIterator} for more information.
      *
      * @return The current index of component
      */
 
     /**
      * <p/>
-     * Get the current version of the data accessed by this ComponentData. When data is
-     * mutated by a ComponentData, implementations increment its associated component's
-     * version so comparing a previously cached version number can be used to determine
-     * when changes have been made.
+     * Get the current version of the data of this component. When a component's data is
+     * mutated, implementations increment its version so comparing a previously cached
+     * version number can be used to determine when changes have been made.
      * <p/>
-     * Additionally, for a given component type, versions will be unique. Thus it is
-     * possible to identify when the components are replaced by new components as well.
+     * Within a component type for an entity system, version values will be unique across
+     * component instances. Thus, {@code entity.as(T.class).getVersion() != cachedVersion}
+     * will correctly detect changes to the original component instance as well as the
+     * removal and replacement by a new component of type T.
      *
      * @return The current version, or a negative number if the data is invalid
      */
     public int getVersion();
 
     /**
-     * Increment the version of the component accessed by this instance. It is recommended
-     * for component data implementations to call this automatically from within their
-     * exposed mutators, but if necessary it can be invoked manually as well.
+     * Increment the version of the component accessed by this instance. This will be
+     * automatically called by all exposed setters by the generated proxies, but if
+     * necessary it can be invoked manually as well.
      *
      * @see #getVersion()
      */
     public void updateVersion();
+
+    /**
+     * Get the class identifier for this component. This will be the specific
+     * sub-interface of Component that this object is an instance of. getType() should be
+     * used instead of {@link Object#getClass()} because that will return a specific and
+     * most likely generated proxy implementation of the component type.
+     *
+     * @return The component type
+     */
+    public Class<? extends Component> getType();
 }

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

 
     private int index;
 
-    private Component[] required; // all required except primary
-    private Component[] optional;
+    private AbstractComponent<?>[] required; // all required except primary
+    private AbstractComponent<?>[] optional;
 
-    private Component primary;
+    private AbstractComponent<?> primary;
 
     /**
      * Create a new ComponentIterator that will iterate over components or entities within
             throw new NullPointerException("System cannot be null");
         }
         this.system = system;
-        required = new Component[0];
-        optional = new Component[0];
+        required = new AbstractComponent<?>[0];
+        optional = new AbstractComponent<?>[0];
         primary = null;
         index = 0;
     }
      * will be set to each component of that type during iteration. Thus it is recommended
      * to hold onto instance for later use.
      *
-     * @param data The ComponentData that is used to access the required component type
-     *             data of its type
+     * @return A flyweight instance to access the current values for the component type
      *
-     * @return This iterator to chain
-     *
-     * @throws NullPointerException     if data is null
-     * @throws IllegalArgumentException if data was not created by the EntitySystem of
-     *                                  this iterator
+     * @throws NullPointerException if type is null
      */
     public <T extends Component> T addRequired(Class<T> type) {
         if (type == null) {
             throw new NullPointerException("Component type cannot be null");
         }
-        T data = system.getRepository(type).createDataInstance();
+        AbstractComponent<T> data = system.getRepository(type).createDataInstance();
 
         // check to see if the data should be the new primary
         if (primary == null) {
             // putting one data into the required array
             required = Arrays.copyOf(required, required.length + 1);
 
-            if (data.owner.getMaxComponentIndex() <
-                primary.owner.getMaxComponentIndex()) {
+            if (data.getRepository().getMaxComponentIndex() <
+                primary.getRepository().getMaxComponentIndex()) {
                 // new primary
                 required[required.length - 1] = primary;
                 primary = data;
             }
         }
 
-        return data;
+        return (T) data;
     }
 
     /**
      * will be set to each component of that type during iteration. Thus it is recommended
      * to hold onto instance for later use.
      * <p/>
-     * Entities are not required to have components of this type when reported by this
-     * iterator. It is important to check {@link ComponentData#isValid()} first.
      *
-     * @param data The ComponentData that is used to access the required component type
-     *             data of its type
+     * @return A flyweight instance to access the current values for the component type
      *
-     * @return This iterator to chain
-     *
-     * @throws NullPointerException     if data is null
-     * @throws IllegalArgumentException if data was not created by the EntitySystem of
-     *                                  this iterator
+     * @throws NullPointerException if type is null
      */
     public <T extends Component> T addOptional(Class<T> type) {
         if (type == null) {
             throw new NullPointerException("Component type cannot be null");
         }
 
-        T data = system.getRepository(type).createDataInstance();
+        AbstractComponent<T> data = system.getRepository(type).createDataInstance();
 
         // add the data to the optional array
         optional = Arrays.copyOf(optional, optional.length + 1);
         optional[optional.length - 1] = data;
 
-        return data;
+        return (T) data;
     }
 
     /**
         boolean found;
         int entity;
         int component;
-        int count = primary.owner.getMaxComponentIndex();
+        int count = primary.getRepository().getMaxComponentIndex();
         while (index < count - 1) {
             index++; // always increment one
 
             found = true;
-            entity = primary.owner.getEntityIndex(index);
+            entity = primary.getRepository().getEntityIndex(index);
             if (entity != 0) {
                 // we have a possible entity candidate
-                primary.setFast(index);
-                if (ignoreEnabled || primary.isEnabled()) {
-                    for (int i = 0; i < required.length; i++) {
-                        component = required[i].owner.getComponentIndex(entity);
-                        if (!required[i].setFast(component) ||
-                            (!ignoreEnabled && !required[i].isEnabled())) {
-                            found = false;
-                            break;
-                        }
+                primary.setIndex(index);
+                for (int i = 0; i < required.length; i++) {
+                    component = required[i].getRepository().getComponentIndex(entity);
+                    if (component == 0) {
+                        found = false;
+                        break;
+                    } else {
+                        required[i].setIndex(component);
+                    }
+                }
+
+                if (found) {
+                    // we have satisfied all required components,
+                    // so now set all optional requirements as well
+                    for (int i = 0; i < optional.length; i++) {
+                        component = optional[i].getRepository().getComponentIndex(entity);
+                        optional[i].setIndex(component);
                     }
 
-                    if (found) {
-                        // we have satisfied all required components,
-                        // so now set all optional requirements as well
-                        for (int i = 0; i < optional.length; i++) {
-                            component = optional[i].owner.getComponentIndex(entity);
-                            // we don't care if this is valid or not
-                            optional[i].setFast(component);
-                        }
-
-                        return true;
-                    }
+                    return true;
                 }
             }
         }

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

         this.system = system;
         this.factory = ComponentFactoryProvider.getInstance().getFactory(type);
         this.type = type;
-        requiredTypes = factory.getRequiredTypes().toArray(new Class[factory.getRequiredTypes().size()]);
+        requiredTypes = factory.getRequiredTypes()
+                               .toArray(new Class[factory.getRequiredTypes().size()]);
 
         List<PropertySpecification> spec = factory.getSpecification();
 
         declaredProperties = new ArrayList<DeclaredPropertyStore<?>>();
         decoratedProperties = new ArrayList<DecoratedPropertyStore<?>>(); // empty for now
         for (PropertySpecification p : spec) {
-            DeclaredPropertyStore store = new DeclaredPropertyStore(p.getFactory(), p.getName());
+            DeclaredPropertyStore store = new DeclaredPropertyStore(p.getFactory(),
+                                                                    p.getName());
             declaredProperties.add(store);
         }
 
 
     /**
      * @param componentIndex The component index
+     *
      * @return The OwnerSupport delegate for the component by the given index
      */
     public OwnerSupport getOwnerDelegate(int componentIndex) {
      *
      * @return A new component of type T
      *
-     * @throws NullPointerException     if fromTemplate is null
-     * @throws IllegalStateException    if the template is not live
+     * @throws NullPointerException  if fromTemplate is null
+     * @throws IllegalStateException if the template is not live
      */
     public T addComponent(int entityIndex, T fromTemplate) {
         if (!type.isInstance(fromTemplate)) {
             expandComponentRepository(componentIndex + 1);
         }
 
-        T instance = createDataInstance(componentIndex);
+        T instance = (T) createDataInstance(componentIndex);
         components[componentIndex] = instance;
         componentIndexToEntityIndex[componentIndex] = entityIndex;
         entityIndexToComponentRepository[entityIndex] = componentIndex;
      *
      * @return A new data instance
      */
-    public T createDataInstance() {
+    public AbstractComponent<T> createDataInstance() {
         return createDataInstance(0);
     }
 
-    private T createDataInstance(int forIndex) {
+    private AbstractComponent<T> createDataInstance(int forIndex) {
         // create a new instance from the factory - it will be completely detached
         AbstractComponent<T> t = factory.newInstance(this);
 
         t.setIndex(forIndex);
-        return (T) t;
+        return t;
     }
 
     /**
         AbstractComponent<T> casted = (AbstractComponent<T>) oldComponent;
         if (oldComponent != null) {
             oldComponent.setOwner(null);
-            casted.delegate.disownAndRemoveChildren();
+            getOwnerDelegate(componentIndex).disownAndRemoveChildren();
             casted.setIndex(0);
         }
 
         int[] newComponentRepository = new int[components.length];
         for (int i = 1; i < components.length; i++) {
             if (components[i] != null) {
-                newComponentRepository[i] = entityOldToNewMap[componentIndexToEntityIndex[components[i].getIndex()]];
+                newComponentRepository[i] = entityOldToNewMap[componentIndexToEntityIndex[components[i]
+                        .getIndex()]];
                 ((AbstractComponent<T>) components[i]).setIndex(i);
                 componentInsert = i + 1;
             }

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

 /**
  * <p/>
  * An Entity represents a collection of Components within an EntitySystem. Entities are
- * created by calling {@link com.lhkbob.entreri.EntitySystem#addEntity()} or the similar function that takes
- * another Entity as a template.
+ * created by calling {@link com.lhkbob.entreri.EntitySystem#addEntity()} or the similar
+ * function that takes another Entity as a template.
  * <p/>
- * Entities use instance equality, just like {@link com.lhkbob.entreri.Component}. Once created the Entity
- * object will not change its identity.
+ * Entities use instance equality, just like {@link com.lhkbob.entreri.Component}. Once
+ * created the Entity object will not change its identity.
  * <p/>
  * <p/>
- * Entity implements both {@link com.lhkbob.entreri.Ownable} and {@link com.lhkbob.entreri.Owner}. This can be used to create
- * hierarchies of both components and entities that share a lifetime. When an entity is
- * removed from the system, all of its owned objects are disowned. If any of them were
- * entities or components, they are also removed from the system.
+ * Entity implements both {@link com.lhkbob.entreri.Ownable} and {@link
+ * com.lhkbob.entreri.Owner}. This can be used to create hierarchies of both components
+ * and entities that share a lifetime. When an entity is removed from the system, all of
+ * its owned objects are disowned. If any of them were entities or components, they are
+ * also removed from the system.
  *
  * @author Michael Ludwig
  */
      * <p/>
      * The new component is initialized by cloning the property values from
      * <var>toClone</var> into the values of the new component. This is performed by
-     * invoking {@link com.lhkbob.entreri.property.PropertyFactory#clone(com.lhkbob.entreri.property.Property, int, com.lhkbob.entreri.property.Property, int)} with the
-     * factories that created each property. All default property factories perform a copy
-     * by value (or copy by reference for object types).
+     * invoking {@link com.lhkbob.entreri.property.PropertyFactory#clone(com.lhkbob.entreri.property.Property,
+     * int, com.lhkbob.entreri.property.Property, int)} with the factories that created
+     * each property. All default property factories perform a copy by value (or copy by
+     * reference for object types).
      *
      * @param <T>     The parameterized type of component to add
      * @param toClone The existing T to clone when attaching to this component
             throw new NullPointerException(
                     "ComponentData template, toClone, cannot be null");
         }
-        // FIXME getClass() won't return the right interface getRepo() expects
-        ComponentRepository ci = system.getRepository(toClone.getClass());
-        return ci.addComponent(index, toClone);
+        ComponentRepository ci = system.getRepository(toClone.getType());
+        return (T) ci.addComponent(index, toClone);
     }
 
     /**
     }
 
     @Override
-    public void notifyOwnershipGranted(Ownable obj) {
+    public Owner notifyOwnershipGranted(Ownable obj) {
         delegate.notifyOwnershipGranted(obj);
+        return this;
     }
 
     @Override

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

 
         if (template != null) {
             for (Component c : template) {
-                // FIXME not correct, may need to bring back getType() in Component to return the interface type
-                addFromTemplate(entityIndex, c.getClass(), c);
+                addFromTemplate(entityIndex, c.getType(), c);
             }
         }
 

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

 package com.lhkbob.entreri;
 
+import org.codehaus.janino.SimpleCompiler;
+
 /**
  *
  */
 class JaninoFactoryProvider {
+
+    public static Object createInstanceNativeJanino() throws Exception {
+        //        ClassBodyEvaluator compiler = new ClassBodyEvaluator();
+        SimpleCompiler compiler = new SimpleCompiler();
+        compiler.setParentClassLoader(ClassLoader.getSystemClassLoader());
+        compiler.cook(CODE_TEXT);
+
+        Class<?> cls = compiler.getClassLoader()
+                               .loadClass("com.lhkbob.entreri.JaninoImpl");
+        return cls.newInstance();
+    }
+
+    public static final String CODE_TEXT = "package com.lhkbob.entreri;\n" +
+                                           "public class JaninoImpl implements JaninoTest.API {\n" +
+                                           "        private int value;\n" +
+                                           "\n" +
+                                           "        public int getValue() {\n" +
+                                           "            return value;\n" +
+                                           "        }\n" +
+                                           "\n" +
+                                           "        public void setValue(int v) {\n" +
+                                           "            value = v;\n" +
+                                           "        }\n" +
+                                           "    }";
 }

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

-package com.lhkbob.entreri;
-
-import com.sun.tools.javac.resources.compiler;
-import org.codehaus.janino.ClassBodyEvaluator;
-import org.codehaus.janino.SimpleCompiler;
-
-/**
- *
- */
-public class JaninoTest {
-    public static API createInstanceNativeJanino() throws Exception {
-//        ClassBodyEvaluator compiler = new ClassBodyEvaluator();
-        SimpleCompiler compiler = new SimpleCompiler();
-        compiler.setParentClassLoader(ClassLoader.getSystemClassLoader());
-        compiler.cook(CODE_TEXT);
-
-        Class<?> cls = compiler.getClassLoader().loadClass("com.lhkbob.entreri.JaninoImpl");
-        return (API) cls.newInstance();
-    }
-
-    public static API createInstanceJDKJanino() throws Exception {
-        org.codehaus.commons.compiler.jdk.SimpleCompiler compiler = new org.codehaus.commons.compiler.jdk.SimpleCompiler();
-        compiler.setParentClassLoader(ClassLoader.getSystemClassLoader());
-        compiler.cook(CODE_TEXT);
-
-        Class<?> cls = compiler.getClassLoader().loadClass("com.lhkbob.entreri.JaninoImpl");
-        return (API) cls.newInstance();
-    }
-
-    public static void main(String[] args) throws Exception {
-        APIClone clone = new APIClone();
-        ExplicitImpl impl = new ExplicitImpl();
-
-        API api1 = createInstanceNativeJanino();
-        API api2 = createInstanceJDKJanino();
-
-        System.out.println(clone.getClass().getCanonicalName());
-        System.out.println(impl.getClass().getCanonicalName());
-        System.out.println(api1.getClass().getCanonicalName());
-        System.out.println(api2.getClass().getCanonicalName());
-
-        System.out.println(api1 instanceof API);
-        System.out.println(api2 instanceof API);
-        System.out.println(api1.getClass().equals(api2.getClass()));
-
-        int numTests = 1000;
-        int numIters = 100000;
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testClone(clone, numIters);
-            }
-            System.out.println("CLONE: " + (System.nanoTime() - now) + " " + sum);
-        }
-
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testAPI(api1, numIters);
-            }
-            System.out.println("JANINO: " + (System.nanoTime() - now) + " " + sum);
-        }
-
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testAPI(api2, numIters);
-            }
-            System.out.println("JDK: " + (System.nanoTime() - now) + " " + sum);
-        }
-
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testAPI(impl, numIters);
-            }
-            System.out.println("IMPL: " + (System.nanoTime() - now) + " " + sum);
-        }
-
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testClone(clone, numIters);
-            }
-            System.out.println("CLONE: " + (System.nanoTime() - now) + " " + sum);
-        }
-
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testAPI(api1, numIters);
-            }
-            System.out.println("JANINO: " + (System.nanoTime() - now) + " " + sum);
-        }
-
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testAPI(api2, numIters);
-            }
-            System.out.println("JDK: " + (System.nanoTime() - now) + " " + sum);
-        }
-
-        {
-            int sum = 0;
-            long now = System.nanoTime();
-            for (int i = 0; i < numTests; i++) {
-                sum += testAPI(impl, numIters);
-            }
-            System.out.println("IMPL: " + (System.nanoTime() - now) + " " + sum);
-        }
-    }
-
-    public static int testClone(APIClone instance, int numIters) {
-        int sum = 0;
-        for (int i = 0; i < numIters; i++) {
-            instance.setValue(i);
-            sum += instance.getValue();
-        }
-        return sum;
-    }
-
-    public static int testAPI(API instance, int numIters) {
-        int sum = 0;
-        for (int i = 0; i < numIters; i++) {
-            instance.setValue(i);
-            sum += instance.getValue();
-        }
-        return sum;
-    }
-
-    public static class APIClone {
-        private int value;
-
-        public int getValue() {
-            return value;
-        }
-
-        public void setValue(int v) {
-            value = v;
-        }
-    }
-
-    public static interface API {
-        public int getValue();
-
-        public void setValue(int v);
-    }
-
-    public static class ExplicitImpl implements API {
-        private int value;
-
-        public int getValue() {
-            return value;
-        }
-
-        public void setValue(int v) {
-            value = v;
-        }
-    }
-
-    public static final String CODE_TEXT = "package com.lhkbob.entreri;\n" +
-                                           "public class JaninoImpl implements JaninoTest.API {\n" +
-                                           "        private int value;\n" +
-                                           "\n" +
-                                           "        public int getValue() {\n" +
-                                           "            return value;\n" +
-                                           "        }\n" +
-                                           "\n" +
-                                           "        public void setValue(int v) {\n" +
-                                           "            value = v;\n" +
-                                           "        }\n" +
-                                           "    }";
-}

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

      * Notify this Owner that it is now <var>obj</var>'s owner. This must only be called
      * by {@link Ownable} implementations in response to calls to {@link
      * Ownable#setOwner(Owner)}.
+     * <p/>
+     * This method returns the true Owner instance, to allow for flyweight objects to act
+     * as Owners. In this case, they will return the canonical owner for the datum they
+     * represent. In regular cases, this will return itself after recording ownership.
      *
      * @param obj The newly owned object
+     *
+     * @return The actual owner in the event that the owner was a flyweight object
      */
-    public void notifyOwnershipGranted(Ownable obj);
+    public Owner notifyOwnershipGranted(Ownable obj);
 
     /**
      * <p/>

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

 import java.util.Set;
 
 /**
- * Utility for shared implementation of {@link com.lhkbob.entreri.Ownable} and {@link com.lhkbob.entreri.Owner}
+ * Utility for shared implementation of {@link com.lhkbob.entreri.Ownable} and {@link
+ * com.lhkbob.entreri.Owner}
  *
  * @author Michael Ludwig
  */
-// FIXME how will this work with flyweight components being set as the owner of things?
 class OwnerSupport {
     private final Ownable target;
     private final Set<Ownable> ownedObjects;
     }
 
     /**
-     * @param obj
+     * @param obj The object now owned
      *
      * @see Owner#notifyOwnershipGranted(Ownable)
      */
     }
 
     /**
-     * @param obj
+     * @param obj The object no longer owned
      *
      * @see Owner#notifyOwnershipRevoked(Ownable)
      */
     }
 
     /**
-     * @param owner
+     * @param owner The owner, possibly flyweight
      *
      * @see Ownable#setOwner(Owner)
      */
         if (currentOwner != null) {
             currentOwner.notifyOwnershipRevoked(target);
         }
-        currentOwner = owner;
         if (owner != null) {
-            owner.notifyOwnershipGranted(target);
+            currentOwner = owner.notifyOwnershipGranted(target);
+        } else {
+            currentOwner = null;
         }
     }
 
     /**
-     * @return
+     * @return The owner
      *
      * @see Ownable#getOwner()
      */