diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java index 18dcf0497..3a65d88d5 100644 --- a/src/java/com/threerings/presents/dobj/DObject.java +++ b/src/java/com/threerings/presents/dobj/DObject.java @@ -29,6 +29,7 @@ import java.util.Map; import com.google.common.collect.Maps; +import com.samskivert.util.ArrayUtil; import com.samskivert.util.ListUtil; import com.samskivert.util.StringUtil; @@ -586,6 +587,88 @@ public class DObject _oid = oid; } + /** + * Configures a local attribute on this object. Local attributes are not sent over the network + * and are thus only available on the server or client that set the attribute. Local attributes + * are keyed by the class of the value being set as an attribute (the expectation is that local + * attributes will be encapsulated into helper classes). + * + *

Also note that it is illegal to replace the value of a local attribute. Attempting to + * set a local attribute that already contains a value will fail. This is intended to catch + * programmer error as early as possible. You may clear a local attribute by setting it to null + * and then it can be set to a new value. + * + *

Lastly, note that key polymorphism is implemented to allow a lower level framework to + * define a local attribute and users of that framework to extend the attribute class and have + * it returned whether the derived or base class is used to look up the attribute. For example: + * + *

+     * class BaseLocalAttr {
+     *     public int foo;
+     * }
+     * class DerivedLocalAttr extends BaseLocalAttr {
+     *     public int bar;
+     * }
+     *
+     * // simple usage
+     * DObject o1 = new DObject();
+     * BaseLocalAttr base = new BaseLocalAttr();
+     * o1.setLocalAttribute(BaseLocalAttr.class, base);
+     * assertSame(o1.getLocalAttribute(BaseLocalAttr.class), base); // true
+     *
+     * // polymorphic usage
+     * DObject o2 = new DObject();
+     * DerivedLocalAttr derived = new DerivedLocalAttr();
+     * o2.setLocalAttribute(DerivedLocalAttr.class, derived);
+     * BaseLocalAttr upcasted = derived;
+     * assertSame(o2.getLocalAttribute(DerivedLocalAttr.class), derived); // true
+     * assertSame(o2.getLocalAttribute(BaseLocalAttr.class), upcasted); // true
+     *
+     * // cannot overwrite already set attribute
+     * DObject o3 = new DObject();
+     * o3.setLocalAttribute(DerivedLocalAttr.class, derived);
+     * o3.setLocalAttribute(DerivedLocalAttr.class, new DerivedLocalAttr()); // will fail
+     * o3.setLocalAttribute(BaseLocalAttr.class, new BaseLocalAttr()); // will fail
+     * 
+ * + * @exception IllegalStateException thrown if an attempt is made to set a local attribute that + * already contains a non-null value with any non-null value. + */ + public void setLocalAttribute (Class key, T attr) + { + // locate any existing attribute that matches our key + for (int ii = 0, ll = _locattrs.length; ii < ll; ii++) { + if (key.isInstance(_locattrs[ii])) { + if (attr != null) { + throw new IllegalStateException( + "Attribute already exists that matches the supplied key " + + "[key=" + key + ", have=" + _locattrs[ii].getClass()); + } + _locattrs = ArrayUtil.splice(_locattrs, ii, 1); + return; + } + } + + // append our attribute to the end of the list + _locattrs = ArrayUtil.append(_locattrs, attr); + } + + /** + * Retrieves a local attribute for the supplied key. See {@link #setLocalAttribute} for + * information on key polymorphism. Returns null if no attribute is found that matches the + * supplied key. + */ + public T getLocalAttribute (Class key) + { + for (Object attr : _locattrs) { + if (key.isInstance(attr)) { + @SuppressWarnings("unchecked") T casted = (T)attr; + return casted; + } + } + return null; + } + /** * Generates a concise string representation of this object. */ @@ -909,6 +992,9 @@ public class DObject /** Indicates whether we want to be destroyed when our last subscriber is removed. */ protected transient boolean _deathWish = false; + /** Any local attributes configured on this object or null. */ + protected transient Object[] _locattrs = NO_ATTRS; + /** Maintains a mapping of sorted field arrays for each distributed object class. */ protected static Map, Field[]> _ftable = Maps.newHashMap(); @@ -918,4 +1004,7 @@ public class DObject return f1.getName().compareTo(f2.getName()); } }; + + /** Simplifies code for objects that have no local attributes. */ + protected static final Object[] NO_ATTRS = {}; } diff --git a/tests/src/java/com/threerings/presents/dobj/LocalAttrTest.java b/tests/src/java/com/threerings/presents/dobj/LocalAttrTest.java new file mode 100644 index 000000000..dd12907b2 --- /dev/null +++ b/tests/src/java/com/threerings/presents/dobj/LocalAttrTest.java @@ -0,0 +1,71 @@ +// +// $Id$ + +package com.threerings.presents.dobj; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests distributed object local attributes. + */ +public class LocalAttrTest +{ + public static class BaseLocalAttr + { + } + + public static class DerivedLocalAttr extends BaseLocalAttr + { + } + + @Test public void testSetGet () + { + DObject o1 = new DObject(); + BaseLocalAttr base = new BaseLocalAttr(); + o1.setLocalAttribute(BaseLocalAttr.class, base); + assertSame(o1.getLocalAttribute(BaseLocalAttr.class), base); + } + + @Test public void testPoly () + { + // polymorphic usage + DObject o2 = new DObject(); + DerivedLocalAttr derived = new DerivedLocalAttr(); + o2.setLocalAttribute(DerivedLocalAttr.class, derived); + BaseLocalAttr upcasted = derived; + assertSame(o2.getLocalAttribute(DerivedLocalAttr.class), derived); + assertSame(o2.getLocalAttribute(BaseLocalAttr.class), upcasted); + + // cannot overwrite already set attribute + DObject o3 = new DObject(); + o3.setLocalAttribute(DerivedLocalAttr.class, derived); + try { + o3.setLocalAttribute(DerivedLocalAttr.class, new DerivedLocalAttr()); + fail("Object allowed attribute overwrite."); + } catch (IllegalStateException ise) { + // good + } + try { + o3.setLocalAttribute(BaseLocalAttr.class, new BaseLocalAttr()); + fail("Object allowed polymorhic attribute overwrite."); + } catch (IllegalStateException ise) { + // good + } + } + + @Test public void testSetClearSet () + { + DObject o1 = new DObject(); + BaseLocalAttr base = new BaseLocalAttr(); + o1.setLocalAttribute(BaseLocalAttr.class, base); + assertSame(o1.getLocalAttribute(BaseLocalAttr.class), base); + o1.setLocalAttribute(BaseLocalAttr.class, null); + assertNull(o1.getLocalAttribute(BaseLocalAttr.class)); + BaseLocalAttr nbase = new BaseLocalAttr(); + o1.setLocalAttribute(BaseLocalAttr.class, nbase); + assertNotSame(o1.getLocalAttribute(BaseLocalAttr.class), base); + assertSame(o1.getLocalAttribute(BaseLocalAttr.class), nbase); + } +}