From d52be825ecdbb5f994b306e6a897968098836f72 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 22 Dec 2010 01:30:55 +0000 Subject: [PATCH] Abstract the manner by which DObject reads and writes its fields. This allows us to create a Scala trait that handles reading and writing Scala "fields" which are not public fields but are rather private fields with public getters and setters. This also enables the creation of bridges for other JVM-hosted non-Java languages. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6391 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../threerings/admin/client/FieldEditor.java | 7 +- .../admin/server/ConfigRegistry.java | 17 +--- .../admin/web/server/ConfigServlet.java | 9 +- .../threerings/presents/dobj/Accessor.java | 77 +++++++++++++++ .../com/threerings/presents/dobj/DObject.java | 97 ++++++++----------- 5 files changed, 125 insertions(+), 82 deletions(-) create mode 100644 src/main/java/com/threerings/presents/dobj/Accessor.java diff --git a/src/main/java/com/threerings/admin/client/FieldEditor.java b/src/main/java/com/threerings/admin/client/FieldEditor.java index 5a0aa3733..5ff872174 100644 --- a/src/main/java/com/threerings/admin/client/FieldEditor.java +++ b/src/main/java/com/threerings/admin/client/FieldEditor.java @@ -39,7 +39,6 @@ import com.samskivert.swing.HGroupLayout; import com.threerings.presents.dobj.AttributeChangeListener; import com.threerings.presents.dobj.AttributeChangedEvent; import com.threerings.presents.dobj.DObject; -import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.util.PresentsContext; import static com.threerings.admin.Log.log; @@ -215,11 +214,7 @@ public abstract class FieldEditor extends JPanel public void set (Field field, Object value) { - try { - _obj.changeAttribute(field.getName(), value); - } catch (ObjectAccessException oae) { - log.warning("Failed to update field " + field.getName() + ": " + oae); - } + _obj.changeAttribute(field.getName(), value); } public Object get (Field field) diff --git a/src/main/java/com/threerings/admin/server/ConfigRegistry.java b/src/main/java/com/threerings/admin/server/ConfigRegistry.java index 723e20373..a56803d66 100644 --- a/src/main/java/com/threerings/admin/server/ConfigRegistry.java +++ b/src/main/java/com/threerings/admin/server/ConfigRegistry.java @@ -50,7 +50,6 @@ import com.threerings.presents.dobj.ElementUpdatedEvent; import com.threerings.presents.dobj.EntryAddedEvent; import com.threerings.presents.dobj.EntryRemovedEvent; import com.threerings.presents.dobj.EntryUpdatedEvent; -import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.SetListener; import static com.threerings.admin.Log.log; @@ -207,13 +206,7 @@ public abstract class ConfigRegistry // from ElementUpdateListener public void elementUpdated (ElementUpdatedEvent event) { - Object value; - try { - value = object.getAttribute(event.getName()); - } catch (ObjectAccessException oae) { - log.warning("Exception getting field", "name", event.getName(), "exception", oae); - return; - } + Object value = object.getAttribute(event.getName()); updateValue(event.getName(), value); } @@ -349,13 +342,7 @@ public abstract class ConfigRegistry protected void serializeAttribute (String attributeName) { String key = nameToKey(attributeName); - Object value; - try { - value = object.getAttribute(attributeName); - } catch (ObjectAccessException oae) { - log.warning("Exception getting field", "name", attributeName, "error", oae); - return; - } + Object value = object.getAttribute(attributeName); if (value == null || Streamer.isStreamable(value.getClass())) { serialize(attributeName, key, value); diff --git a/src/main/java/com/threerings/admin/web/server/ConfigServlet.java b/src/main/java/com/threerings/admin/web/server/ConfigServlet.java index bfb7a7ea9..e0a4ad766 100644 --- a/src/main/java/com/threerings/admin/web/server/ConfigServlet.java +++ b/src/main/java/com/threerings/admin/web/server/ConfigServlet.java @@ -37,7 +37,6 @@ import com.google.common.collect.Maps; import com.threerings.presents.data.InvocationCodes; import com.threerings.presents.dobj.DObject; -import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.web.gwt.ServiceException; @@ -103,12 +102,8 @@ public abstract class ConfigServlet extends RemoteServiceServlet int updateCount = 0; for (ConfigField update : updates) { - try { - object.changeAttribute(update.name, update.type.toValue(update.valStr)); - updateCount ++; - } catch (ObjectAccessException oae) { - log.warning("Failed to update field", "field", update.name, oae); - } + object.changeAttribute(update.name, update.type.toValue(update.valStr)); + updateCount ++; } object.commitTransaction(); diff --git a/src/main/java/com/threerings/presents/dobj/Accessor.java b/src/main/java/com/threerings/presents/dobj/Accessor.java new file mode 100644 index 000000000..97112d8f6 --- /dev/null +++ b/src/main/java/com/threerings/presents/dobj/Accessor.java @@ -0,0 +1,77 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://code.google.com/p/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.dobj; + +import java.lang.reflect.Field; + +/** + * Used by {@link DObject} to provide dynamic access to its fields. This class is an implementation + * detail that can be safely ignored. It is only a public top-level class to ensure that bindings + * for other languages can make use of it without complications. + */ +public abstract class Accessor implements Comparable { + /** An accessor that assumes DObject fields are public Java fields. */ + public static class ByField extends Accessor { + public final Field field; + + public ByField (Field field) { + super(field.getName()); + this.field = field; + } + + public Object get (DObject obj) { + try { + return field.get(obj); + } catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } + } + + public void set (DObject obj, Object value) { + try { + field.set(obj, value); + } catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } + } + } + + /** The name of this attribute. */ + public final String name; + + /** Gets the current value of this attribute. */ + public abstract Object get (DObject obj); + + /** Updates the current value of this attribute. */ + public abstract void set (DObject obj, Object value); + + // from interface Comparable + public int compareTo (Accessor other) + { + return name.compareTo(other.name); + } + + protected Accessor (String name) + { + this.name = name; + } +} diff --git a/src/main/java/com/threerings/presents/dobj/DObject.java b/src/main/java/com/threerings/presents/dobj/DObject.java index c41f50d25..f0e95e0bb 100644 --- a/src/main/java/com/threerings/presents/dobj/DObject.java +++ b/src/main/java/com/threerings/presents/dobj/DObject.java @@ -23,6 +23,7 @@ package com.threerings.presents.dobj; import java.lang.ref.WeakReference; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Comparator; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Map; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.samskivert.util.ArrayUtil; @@ -124,11 +126,11 @@ public class DObject { public DObject () { - _fields = _ftable.get(getClass()); - if (_fields == null) { - _fields = getClass().getFields(); - Arrays.sort(_fields, FIELD_COMP); - _ftable.put(getClass(), _fields); + _accessors = _atable.get(getClass()); + if (_accessors == null) { + _accessors = createAccessors(); + Arrays.sort(_accessors); + _atable.put(getClass(), _accessors); } } @@ -305,12 +307,8 @@ public class DObject */ public final DSet getSet (String setName) { - try { - @SuppressWarnings("unchecked") DSet casted = (DSet)getField(setName).get(this); - return casted; - } catch (Exception e) { - throw new IllegalArgumentException("No such set: " + setName); - } + @SuppressWarnings("unchecked") DSet casted = (DSet)getAccessor(setName).get(this); + return casted; } /** @@ -520,18 +518,10 @@ public class DObject * distributed fields in a generic manner. */ public void changeAttribute (String name, Object value) - throws ObjectAccessException { - try { - Field f = getField(name); - requestAttributeChange(name, value, f.get(this)); - f.set(this, value); - - } catch (Exception e) { - String errmsg = "changeAttribute() failure [name=" + name + ", value=" + value + - ", vclass=" + StringUtil.shortClassName(value) + "]."; - throw new ObjectAccessException(errmsg, e); - } + Accessor acc = getAccessor(name); + requestAttributeChange(name, value, acc.get(this)); + acc.set(this, value); } /** @@ -540,16 +530,8 @@ public class DObject * attribute setter methods instead. */ public void setAttribute (String name, Object value) - throws ObjectAccessException { - try { - getField(name).set(this, value); - - } catch (Exception e) { - String errmsg = "setAttribute() failure [name=" + name + ", value=" + value + - ", vclass=" + StringUtil.shortClassName(value) + "]."; - throw new ObjectAccessException(errmsg, e); - } + getAccessor(name).set(this, value); } /** @@ -558,13 +540,8 @@ public class DObject * the generated attribute getter methods instead. */ public Object getAttribute (String name) - throws ObjectAccessException { - try { - return getField(name).get(this); - } catch (Exception e) { - throw new ObjectAccessException("getAttribute() failure [name=" + name + "].", e); - } + return getAccessor(name).get(this); } /** @@ -997,15 +974,16 @@ public class DObject } /** - * Returns the {@link Field} with the specified name or null if there is none such. + * Returns the {@link Accessor} for the field with the specified name throws an {@link + * IllegalArgumentException}. */ - protected final Field getField (String name) + protected final Accessor getAccessor (String name) { - int low = 0, high = _fields.length-1; + int low = 0, high = _accessors.length-1; while (low <= high) { int mid = (low + high) >> 1; - Field midVal = _fields[mid]; - int cmp = midVal.getName().compareTo(name); + Accessor midVal = _accessors[mid]; + int cmp = midVal.name.compareTo(name); if (cmp < 0) { low = mid + 1; } else if (cmp > 0) { @@ -1014,7 +992,25 @@ public class DObject return midVal; // key found } } - return null; // key not found. + throw new IllegalArgumentException("No such field " + getClass().getName() + "." + name); + } + + /** + * Creates the accessors that will be used to read and write this object's attributes. The + * default implementation assumes the object's attributes are all public fields and uses + * reflection to get and set their values. + */ + protected Accessor[] createAccessors () + { + Field[] fields = getClass().getFields(); + // assume we have one static field for every non-static field + List accs = Lists.newArrayListWithExpectedSize(fields.length/2); + for (Field field : fields) { + if (!Modifier.isStatic(field.getModifiers())) { // skip static fields + accs.add(new Accessor.ByField(field)); + } + } + return accs.toArray(new Accessor[accs.size()]); } /** @@ -1038,8 +1034,8 @@ public class DObject /** Our object id. */ protected int _oid; - /** An array of our fields, sorted for efficient lookup. */ - protected transient Field[] _fields; + /** An array of our field accessors, sorted for efficient lookup. */ + protected transient Accessor[] _accessors; /** A reference to our object manager. */ protected transient DObjectManager _omgr; @@ -1074,15 +1070,8 @@ public class DObject /** 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(); - - /** Used to sort and search {@link #_fields}. */ - protected static final Comparator FIELD_COMP = new Comparator() { - public int compare (Field f1, Field f2) { - return f1.getName().compareTo(f2.getName()); - } - }; + /** Maintains a mapping of sorted accessor arrays for each distributed object class. */ + protected static Map, Accessor[]> _atable = Maps.newHashMap(); /** Simplifies code for objects that have no local attributes. */ protected static final Object[] NO_ATTRS = {};