diff --git a/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java b/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java index 8fc1a2b6e..3d7c14fef 100644 --- a/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java +++ b/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java @@ -21,6 +21,8 @@ package com.threerings.presents.dobj; +import java.util.HashMap; + import com.samskivert.util.StringUtil; /** @@ -114,9 +116,24 @@ public class AttributeChangedEvent extends NamedEvent // actually apply the attribute change if (_oldValue == UNSET_OLD_VALUE) { _oldValue = target.getAttribute(_name); - // pass the new value on to the object (this uses reflection - // and is slow) - target.setAttribute(_name, _value); + Object value = _value; + if (value != null) { + Class vclass = value.getClass(); + if (vclass.isPrimitive()) { + // do nothing; we check this to avoid the more + // expensive isAssignableFrom check on primitives + // which are far and away the most common case + } else if (vclass.isArray()) { + Cloner cloner = (Cloner)_cloners.get(vclass); + if (cloner != null) { + value = cloner.clone(value); + } + } else if (DSet.class.isAssignableFrom(vclass)) { + value = ((DSet)value).clone(); + } + } + // pass the new value on to the object + target.setAttribute(_name, value); } return true; } @@ -160,6 +177,66 @@ public class AttributeChangedEvent extends NamedEvent StringUtil.toString(buf, _value); } + /** Used to clone object attributes without a zillion calls to + * instanceof. */ + protected static interface Cloner { + public Object clone (Object object); + } + protected Object _value; protected transient Object _oldValue = UNSET_OLD_VALUE; + + /** Contains {@link Cloner}s for types that need cloning. */ + protected static HashMap _cloners = new HashMap(); + + /** Object prototypes we can use to get the appropriate class objects + * for use when setting up {@link #_cloners}. */ + protected static final Object[] ARRAY_PROTOS = new Object[] { + new String[0], new byte[0], new char[0], new short[0], new int[0], + new long[0], new float[0], new double[0] + }; + + // set up our cloners + static { + _cloners.put(ARRAY_PROTOS[0], new Cloner() { + public Object clone (Object obj) { + return ((String[])obj).clone(); + } + }); + _cloners.put(ARRAY_PROTOS[1], new Cloner() { + public Object clone (Object obj) { + return ((byte[])obj).clone(); + } + }); + _cloners.put(ARRAY_PROTOS[2], new Cloner() { + public Object clone (Object obj) { + return ((char[])obj).clone(); + } + }); + _cloners.put(ARRAY_PROTOS[3], new Cloner() { + public Object clone (Object obj) { + return ((short[])obj).clone(); + } + }); + _cloners.put(ARRAY_PROTOS[4], new Cloner() { + public Object clone (Object obj) { + return ((int[])obj).clone(); + } + }); + _cloners.put(ARRAY_PROTOS[5], new Cloner() { + public Object clone (Object obj) { + return ((long[])obj).clone(); + } + }); + _cloners.put(ARRAY_PROTOS[6], new Cloner() { + public Object clone (Object obj) { + return ((float[])obj).clone(); + } + }); + _cloners.put(ARRAY_PROTOS[7], new Cloner() { + public Object clone (Object obj) { + return ((double[])obj).clone(); + } + }); + } } diff --git a/src/java/com/threerings/presents/tools/GenDObjectTask.java b/src/java/com/threerings/presents/tools/GenDObjectTask.java index b17ac440e..8d5aa9e73 100644 --- a/src/java/com/threerings/presents/tools/GenDObjectTask.java +++ b/src/java/com/threerings/presents/tools/GenDObjectTask.java @@ -259,6 +259,8 @@ public class GenDObjectTask extends Task ctx.put("type", GenUtil.simpleName(ftype)); ctx.put("wrapfield", GenUtil.boxArgument(ftype, "value")); ctx.put("wrapofield", GenUtil.boxArgument(ftype, "ovalue")); + ctx.put("clonefield", + GenUtil.cloneArgument(_dsclass, ftype, "value")); ctx.put("capfield", StringUtil.unStudlyName(fname).toUpperCase()); ctx.put("upfield", StringUtils.capitalize(fname)); if (ftype.isArray()) { diff --git a/src/java/com/threerings/presents/tools/GenUtil.java b/src/java/com/threerings/presents/tools/GenUtil.java index 20cf9b0e0..fed91894f 100644 --- a/src/java/com/threerings/presents/tools/GenUtil.java +++ b/src/java/com/threerings/presents/tools/GenUtil.java @@ -116,6 +116,19 @@ public class GenUtil } } + /** + * Potentially clones the supplied argument if it is the type that + * needs such treatment. + */ + public static String cloneArgument (Class dsclazz, Class clazz, String name) + { + if (clazz.isArray() || dsclazz.isAssignableFrom(clazz)) { + return "(" + simpleName(clazz) + ")" + name + ".clone()"; + } else { + return name; + } + } + /** * Reads in the supplied source file and locates the package and class * or interface name and returns a fully qualified class name. diff --git a/src/java/com/threerings/presents/tools/dobject_field.tmpl b/src/java/com/threerings/presents/tools/dobject_field.tmpl index e40f99b62..d48a4d7a1 100644 --- a/src/java/com/threerings/presents/tools/dobject_field.tmpl +++ b/src/java/com/threerings/presents/tools/dobject_field.tmpl @@ -11,7 +11,7 @@ $type ovalue = this.$field; requestAttributeChange( $capfield, $wrapfield, $wrapofield); - this.$field = value; + this.$field = $clonefield; } #if ($elemtype) diff --git a/src/java/com/threerings/presents/tools/dobject_set.tmpl b/src/java/com/threerings/presents/tools/dobject_set.tmpl index 6f59db43a..7afe8063c 100644 --- a/src/java/com/threerings/presents/tools/dobject_set.tmpl +++ b/src/java/com/threerings/presents/tools/dobject_set.tmpl @@ -38,8 +38,8 @@ * change. Proxied copies of this object (on clients) will apply the * value change when they received the attribute changed notification. */ - public void set$upfield ($type $field) + public void set$upfield ($type value) { - requestAttributeChange($capfield, $field, this.$field); - this.$field = $field; + requestAttributeChange($capfield, value, this.$field); + this.$field = $clonefield; }