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