Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.threerings.presents.dobj.DObject Maven / Gradle / Ivy
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 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.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
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;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.io.Streamable;
import com.threerings.presents.net.Transport;
import static com.threerings.presents.Log.log;
/**
* The distributed object forms the foundation of the Presents system. All information shared among
* users of the system is done via distributed objects. A distributed object has a set of
* listeners. These listeners have access to the object or a proxy of the object and therefore have
* access to the data stored in the object's members at all times.
*
* Additionally, an object has a set of subscribers. Subscribers manage the lifespan of the
* object; while a subscriber is subscribed, the listeners registered with an object will be
* notified of events. When the subscriber unsubscribes, the object becomes non-live and the
* listeners are no longer notified. Note: on the server, object subscription is merely a
* formality as all objects remain live all the time, so do not depend on event
* notifications ceasing when a subscriber has relinquished its subscription. Always unregister all
* listeners when they no longer need to hear from an object.
*
*
When there is any change to the the object's properties (which must be effected via the
* setter methods), an event is generated which is dispatched to all listeners of the object,
* notifying them of that change and effecting that change to the copy of the object maintained at
* each client. In this way, both a repository of shared information and a mechanism for
* asynchronous notification are made available as a fundamental application building blocks.
*
*
Distributed object fields can be any of the following set of primitive types:
*
{@code
* boolean, byte, short, int, long, float, double
* Boolean, Byte, Short, Integer, Long, Float, Double, String
* boolean[], byte[], short[], int[], long[], float[], double[], String[]
* }
* as well as custom types that implement {@link Streamable}.
*/
public class DObject
implements Streamable
{
public DObject ()
{
_accessors = _atable.get(getClass());
if (_accessors == null) {
_accessors = createAccessors();
Arrays.sort(_accessors);
_atable.put(getClass(), _accessors);
}
}
/**
* Returns the object id of this object. All objects in the system have a unique object id.
*/
public int getOid ()
{
return _oid;
}
/**
* Returns the dobject manager under the auspices of which this object operates. This could be
* null
if the object is not active.
*/
public DObjectManager getManager ()
{
return _omgr;
}
/**
* Don't call this function! Go through the distributed object manager instead to ensure that
* everything is done on the proper thread. This function can only safely be called directly
* when you know you are operating on the omgr thread (you are in the middle of a call to
* objectAvailable
or to a listener callback).
*
* @see DObjectManager#subscribeToObject
*/
public void addSubscriber (Subscriber> sub)
{
// only add the subscriber if they're not already there
Object[] subs = ListUtil.testAndAddRef(_subs, sub);
if (subs != null) {
// Log.info("Adding subscriber " + which() + ": " + sub + ".");
_subs = subs;
_scount++;
} else {
log.warning("Refusing subscriber that's already in the list", "dobj", which(),
"subscriber", sub, new Exception());
}
}
/**
* Don't call this function! Go through the distributed object manager instead to ensure that
* everything is done on the proper thread. This function can only safely be called directly
* when you know you are operating on the omgr thread (you are in the middle of a call to
* objectAvailable
or to a listener callback).
*
* @see DObjectManager#unsubscribeFromObject
*/
public void removeSubscriber (Subscriber> sub)
{
if (ListUtil.clearRef(_subs, sub) != null) {
// if we removed something, check to see if we just removed the last subscriber from
// our list; we also want to be sure that we're still active otherwise there's no need
// to notify our objmgr because we don't have one
if (--_scount == 0 && _omgr != null) {
_omgr.removedLastSubscriber(this, _deathWish);
}
}
}
/**
* Instructs this object to request to have a fork stuck in it when its last subscriber is
* removed.
*/
public void setDestroyOnLastSubscriberRemoved (boolean deathWish)
{
_deathWish = deathWish;
}
/**
* Adds an event listener to this object. The listener will be notified when any events are
* dispatched on this object that match their particular listener interface.
*
* Note that the entity adding itself as a listener should have obtained the object
* reference by subscribing to it or should be acting on behalf of some other entity that
* subscribed to the object, and that it must be sure to remove itself from the
* listener list (via {@link #removeListener}) when it is done because unsubscribing from the
* object (done by whatever entity subscribed in the first place) is not guaranteed to result
* in the listeners added through that subscription being automatically removed (in most cases,
* they definitely will not be removed).
*
* @param listener the listener to be added.
*
* @see EventListener
* @see AttributeChangeListener
* @see SetListener
* @see OidListListener
*/
public void addListener (ChangeListener listener)
{
addListener(listener, false);
}
/**
* Adds an event listener to this object. The listener will be notified when any events are
* dispatched on this object that match their particular listener interface.
*
*
Note that the entity adding itself as a listener should have obtained the object
* reference by subscribing to it or should be acting on behalf of some other entity that
* subscribed to the object, and that it must be sure to remove itself from the
* listener list (via {@link #removeListener}) when it is done because unsubscribing from the
* object (done by whatever entity subscribed in the first place) is not guaranteed to result
* in the listeners added through that subscription being automatically removed (in most cases,
* they definitely will not be removed).
*
* @param listener the listener to be added.
* @param weak if true, retain only a weak reference to the listener (do not prevent the
* listener from being garbage-collected).
*
* @see EventListener
* @see AttributeChangeListener
* @see SetListener
* @see OidListListener
*/
public void addListener (ChangeListener listener, boolean weak)
{
// only add the listener if they're not already there
int idx = getListenerIndex(listener);
if (idx == -1) {
_listeners = ListUtil.add(_listeners,
weak ? new WeakReference(listener) : listener);
return;
}
boolean oweak = _listeners[idx] instanceof WeakReference>;
if (weak == oweak) {
log.warning("Refusing repeat listener registration",
"dobj", which(), "list", listener, new Exception());
} else {
log.warning("Updating listener registered under different strength.",
"dobj", which(), "list", listener, "oweak", oweak, "nweak", weak, new Exception());
_listeners[idx] = weak ? new WeakReference(listener) : listener;
}
}
/**
* Removes an event listener from this object. The listener will no longer be notified when
* events are dispatched on this object.
*
* @param listener the listener to be removed.
*/
public void removeListener (ChangeListener listener)
{
int idx = getListenerIndex(listener);
if (idx != -1) {
_listeners[idx] = null;
}
}
/**
* Provides this object with an entity that can be used to validate subscription requests and
* events before they are processed. The access controller is handy for ensuring that clients
* are behaving as expected and for preventing impermissible modifications or event dispatches
* on a distributed object.
*/
public void setAccessController (AccessController controller)
{
_controller = controller;
}
/**
* Returns a reference to the access controller in use by this object or null if none has been
* configured.
*/
public AccessController getAccessController ()
{
return _controller;
}
/**
* Get the DSet with the specified name.
*/
public final DSet getSet (String setName)
{
@SuppressWarnings("unchecked") DSet casted = (DSet)getAccessor(setName).get(this);
return casted;
}
/**
* Request to have the specified item added to the specified DSet.
*/
public void addToSet (String setName, T entry)
{
requestEntryAdd(setName, getSet(setName), entry);
}
/**
* Request to have the specified item updated in the specified DSet.
*/
public void updateSet (String setName, DSet.Entry entry)
{
requestEntryUpdate(setName, getSet(setName), entry);
}
/**
* Request to have the specified key removed from the specified DSet.
*/
public void removeFromSet (String setName, Comparable> key)
{
requestEntryRemove(setName, getSet(setName), key);
}
/**
* At times, an entity on the server may need to ensure that events it has queued up have made
* it through the event queue and are applied to their respective objects before a service may
* safely be undertaken again. To make this possible, it can acquire a lock on a distributed
* object, generate the events in question and then release the lock (via a call to
* releaseLock
) which will queue up a final event, the processing of which will
* release the lock. Thus the lock will not be released until all of the previously generated
* events have been processed. If the service is invoked again before that lock is released,
* the associated call to acquireLock
will fail and the code can respond
* accordingly. An object may have any number of outstanding locks as long as they each have a
* unique name.
*
* @param name the name of the lock to acquire.
*
* @return true if the lock was acquired, false if the lock was not acquired because it has not
* yet been released from a previous acquisition.
*
* @see #releaseLock
*/
public boolean acquireLock (String name)
{
// check for the existence of the lock in the list and add it if it's not already there
Object[] list = ListUtil.testAndAdd(_locks, name);
if (list == null) {
// a null list means the object was already in the list
return false;
} else {
// a non-null list means the object was added
_locks = list;
return true;
}
}
/**
* Queues up an event that when processed will release the lock of the specified name.
*
* @see #acquireLock
*/
public void releaseLock (String name)
{
// queue up a release lock event
postEvent(new ReleaseLockEvent(_oid, name));
}
/**
* Don't call this function! It is called by a remove lock event when that event is processed
* and shouldn't be called at any other time. If you mean to release a lock that was acquired
* with acquireLock
you should be using releaseLock
.
*
* @see #acquireLock
* @see #releaseLock
*/
protected void clearLock (String name)
{
// clear the lock from the list
if (ListUtil.clear(_locks, name) == null) {
// complain if we didn't find the lock
log.info("Unable to clear non-existent lock", "lock", name, "dobj", this);
}
}
/**
* Requests that this distributed object be destroyed. It does so by queueing up an object
* destroyed event which the server will validate and process.
*/
public void destroy ()
{
if (_oid == 0) {
log.warning("Denying request to destroy an uninitialized object!", new Exception());
return;
}
postEvent(new ObjectDestroyedEvent(_oid));
}
/**
* Checks to ensure that the specified subscriber has access to this object. This will be
* called before satisfying a subscription request. If an {@link AccessController} has been
* specified for this object, it will be used to determine whether or not to allow the
* subscription request. If no controller is set, the subscription will be allowed.
*
* @param sub the subscriber that will subscribe to this object.
*
* @return true if the subscriber has access to the object, false if they do not.
*/
public boolean checkPermissions (Subscriber> sub)
{
if (_controller != null) {
return _controller.allowSubscribe(this, sub);
} else {
return true;
}
}
/**
* Checks to ensure that this event which is about to be processed, has the appropriate
* permissions. If an {@link AccessController} has been specified for this object, it will be
* used to determine whether or not to allow the even dispatch. If no controller is set, all
* events are allowed.
*
* @param event the event that will be dispatched, object permitting.
*
* @return true if the event is valid and should be dispatched, false if the event fails the
* permissions check and should be aborted.
*/
public boolean checkPermissions (DEvent event)
{
if (_controller != null) {
return _controller.allowDispatch(this, event);
} else {
return true;
}
}
/**
* Called by the distributed object manager after it has applied an event to this object. This
* dispatches an event notification to all of the listeners registered with this object.
*
* @param event the event that was just applied.
*/
public void notifyListeners (DEvent event)
{
if (_listeners == null) {
return;
}
for (int ii = 0, ll = _listeners.length; ii < ll; ii++) {
Object listener = _listeners[ii];
if (listener == null) {
continue;
}
if (listener instanceof WeakReference>) {
if ((listener = ((WeakReference>)listener).get()) == null) {
_listeners[ii] = null;
continue;
}
}
try {
// do any event specific notifications
event.notifyListener(listener);
// and notify them if they are listening for all events
if (listener instanceof EventListener) {
((EventListener)listener).eventReceived(event);
}
} catch (Exception e) {
log.warning("Listener choked during notification", "list", listener,
"event", event, e);
}
}
}
/**
* Called by the distributed object manager after it has applied an event to this object. This
* dispatches an event notification to all of the proxy listeners registered with this object.
*
* @param event the event that was just applied.
*/
public void notifyProxies (DEvent event)
{
if (_subs == null || event.isPrivate()) {
return;
}
for (Object sub : _subs) {
try {
if (sub != null && sub instanceof ProxySubscriber) {
((ProxySubscriber)sub).eventReceived(event);
}
} catch (Exception e) {
log.warning("Proxy choked during notification", "sub", sub, "event", event, e);
}
}
}
/**
* Requests that the specified attribute be changed to the specified value. Normally the
* generated setter methods should be used but in rare cases a caller may wish to update
* distributed fields in a generic manner.
*/
public void changeAttribute (String name, Object value)
{
Accessor acc = getAccessor(name);
requestAttributeChange(name, value, acc.get(this));
acc.set(this, value);
}
/**
* Sets the named attribute to the specified value. This is only used by the internals of the
* event dispatch mechanism and should not be called directly by users. Use the generated
* attribute setter methods instead.
*/
public void setAttribute (String name, Object value)
{
getAccessor(name).set(this, value);
}
/**
* Looks up the named attribute and returns a reference to it. This should only be used by the
* internals of the event dispatch mechanism and should not be called directly by users. Use
* the generated attribute getter methods instead.
*/
public Object getAttribute (String name)
{
return getAccessor(name).get(this);
}
/**
* Posts a message event on this distributed object.
*/
public void postMessage (String name, Object... args)
{
postMessage(Transport.DEFAULT, name, args);
}
/**
* Posts a message event on this distributed object.
*
* @param transport a hint as to the type of transport desired for the message.
*/
public void postMessage (Transport transport, String name, Object... args)
{
postEvent(new MessageEvent(_oid, name, args).setTransport(transport));
}
/**
* Posts the specified event either to our dobject manager or to the compound event for which
* we are currently transacting.
*/
public void postEvent (DEvent event)
{
if (_tevent != null) {
_tevent.postEvent(event);
} else if (_omgr != null) {
_omgr.postEvent(event);
} else {
log.info("Dropping event for non- or no longer managed object", "oid", getOid(),
"class", getClass().getName(), "event", event);
}
}
/**
* Returns true if this object is active and registered with the distributed object system. If
* an object is created via DObjectManager.createObject
it will be active until
* such time as it is destroyed.
*/
public final boolean isActive ()
{
return _omgr != null;
}
/**
* Don't call this function! It initializes this distributed object with the supplied
* distributed object manager. This is called by the distributed object manager when an object
* is created and registered with the system.
*
* @see RootDObjectManager#registerObject(DObject)
*/
public void setManager (DObjectManager omgr)
{
_omgr = omgr;
}
/**
* Don't call this function. It is called by the distributed object manager when an object is
* created and registered with the system.
*
* @see RootDObjectManager#registerObject(DObject)
*/
public void setOid (int oid)
{
_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.setLocal(BaseLocalAttr.class, base);
* assertSame(o1.getLocal(BaseLocalAttr.class), base); // true
*
* // polymorphic usage
* DObject o2 = new DObject();
* DerivedLocalAttr derived = new DerivedLocalAttr();
* o2.setLocal(DerivedLocalAttr.class, derived);
* BaseLocalAttr upcasted = derived;
* assertSame(o2.getLocal(DerivedLocalAttr.class), derived); // true
* assertSame(o2.getLocal(BaseLocalAttr.class), upcasted); // true
*
* // cannot overwrite already set attribute
* DObject o3 = new DObject();
* o3.setLocal(DerivedLocalAttr.class, derived);
* o3.setLocal(DerivedLocalAttr.class, new DerivedLocalAttr()); // will fail
* o3.setLocal(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 setLocal (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;
}
}
// if we were trying to clear out an attribute but didn't find it, then stop here
if (attr == null) {
return;
}
// otherwise append our attribute to the end of the list
_locattrs = ArrayUtil.append(_locattrs, attr);
}
/**
* Retrieves a local attribute for the supplied key. See {@link #setLocal} for
* information on key polymorphism. Returns null if no attribute is found that matches the
* supplied key.
*/
public T getLocal (Class key)
{
for (Object attr : _locattrs) {
if (key.isInstance(attr)) {
return key.cast(attr);
}
}
return null;
}
/**
* Returns an array containing our local attributes.
*/
public List getLocals ()
{
return ImmutableList.copyOf(_locattrs);
}
/**
* Generates a concise string representation of this object.
*/
public String which ()
{
StringBuilder buf = new StringBuilder();
which(buf);
return buf.toString();
}
@Override // from Object
public String toString ()
{
StringBuilder buf = new StringBuilder();
toString(buf);
return buf.append("]").toString();
}
/**
* Used to briefly describe this distributed object.
*/
protected void which (StringBuilder buf)
{
buf.append(StringUtil.shortClassName(this));
buf.append(":").append(_oid);
}
/**
* Generates a string representation of this object.
*/
protected void toString (StringBuilder buf)
{
StringUtil.fieldsToString(buf, this, "\n");
if (buf.length() > 0) {
buf.insert(0, "\n");
}
buf.insert(0, _oid);
buf.insert(0, "[oid=");
}
/**
* Begins a transaction on this distributed object. In some situations, it is desirable to
* cause multiple changes to distributed object fields in one unified operation. Starting a
* transaction causes all subsequent field modifications to be stored in a single compound
* event which can then be committed, dispatching and applying all included events in a single
* group. Additionally, the events are dispatched over the network in a single unit which can
* significantly enhance network efficiency.
*
* When the transaction is complete, the caller must call {@link #commitTransaction} or
* {@link CompoundEvent#commit} to commit the transaction and release the object back to its
* normal non-transacting state. If the caller decides not to commit their transaction, they
* must call {@link #cancelTransaction} or {@link CompoundEvent#cancel} to cancel the
* transaction. Failure to do so will cause the pooch to be totally screwed.
*
*
Note: like all other distributed object operations, transactions are not thread safe. It
* is expected that a single thread will handle all distributed object operations and that
* thread will begin and complete a transaction before giving up control to unknown code which
* might try to operate on the transacting distributed object.
*
*
Note also: if the object is already engaged in a transaction, a transaction participant
* count will be incremented to note that an additional call to {@link #commitTransaction} is
* required before the transaction should actually be committed. Thus every call to
* {@link #startTransaction} must be accompanied by a call to either {@link #commitTransaction}
* or {@link #cancelTransaction}. Additionally, if any transaction participant cancels the
* transaction, the entire transaction is cancelled for all participants, regardless of whether
* the other participants attempted to commit the transaction.
*/
public void startTransaction ()
{
if (_tevent != null) {
_tcount++;
} else {
_tevent = new CompoundEvent(this, _omgr);
}
}
/**
* Commits the transaction in which this distributed object is involved.
*
* @see CompoundEvent#commit
*/
public void commitTransaction ()
{
if (_tevent == null) {
String errmsg = "Cannot commit: not involved in a transaction [dobj=" + this + "]";
throw new IllegalStateException(errmsg);
}
// if we are nested, we decrement our nesting count rather than committing the transaction
if (_tcount > 0) {
_tcount--;
} else {
// we may actually be doing our final commit after someone already cancelled this
// transaction, so we need to perform the appropriate action at this point
if (_tcancelled) {
_tevent.cancel();
} else {
_tevent.commit();
}
}
}
/**
* Returns true if this object is in the middle of a transaction or false if it is not.
*/
public boolean inTransaction ()
{
return (_tevent != null);
}
/**
* Cancels the transaction in which this distributed object is involved.
*
* @see CompoundEvent#cancel
*/
public void cancelTransaction ()
{
if (_tevent == null) {
String errmsg = "Cannot cancel: not involved in a transaction [dobj=" + this + "]";
throw new IllegalStateException(errmsg);
}
// if we're in a nested transaction, make a note that it is to be cancelled when all
// parties commit and decrement the nest count
if (_tcount > 0) {
_tcancelled = true;
_tcount--;
} else {
_tevent.cancel();
}
}
/**
* Removes this object from participation in any transaction in which it might be taking part.
*/
protected void clearTransaction ()
{
// sanity check
if (_tcount != 0) {
log.warning("Transaction cleared with non-zero nesting count", "dobj", this);
_tcount = 0;
}
// clear our transaction state
_tevent = null;
_tcancelled = false;
}
/**
* Called by derived instances when an attribute setter method was called.
*/
protected void requestAttributeChange (String name, Object value, Object oldValue)
{
requestAttributeChange(name, value, oldValue, Transport.DEFAULT);
}
/**
* Called by derived instances when an attribute setter method was called.
*/
protected void requestAttributeChange (
String name, Object value, Object oldValue, Transport transport)
{
// dispatch an attribute changed event
postEvent(new AttributeChangedEvent(_oid, name, value).
setOldValue(oldValue).setTransport(transport));
}
/**
* Called by derived instances when an element updater method was called.
*/
protected void requestElementUpdate (String name, int index, Object value, Object oldValue)
{
requestElementUpdate(name, index, value, oldValue, Transport.DEFAULT);
}
/**
* Called by derived instances when an element updater method was called.
*/
protected void requestElementUpdate (
String name, int index, Object value, Object oldValue, Transport transport)
{
// dispatch an attribute changed event
postEvent(new ElementUpdatedEvent(_oid, name, value, index).
setOldValue(oldValue).setTransport(transport));
}
/**
* Calls by derived instances when an oid adder method was called.
*/
protected void requestOidAdd (String name, OidList list, int oid)
{
// if we're on the authoritative server, we update the set immediately
boolean applyImmediately = isAuthoritative();
if (applyImmediately) {
list.add(oid);
}
postEvent(new ObjectAddedEvent(_oid, name, oid).setAlreadyApplied(applyImmediately));
}
/**
* Calls by derived instances when an oid remover method was called.
*/
protected void requestOidRemove (String name, OidList list, int oid)
{
// if we're on the authoritative server, we update the set immediately
boolean applyImmediately = isAuthoritative();
if (applyImmediately) {
list.remove(oid);
}
// dispatch an object removed event
postEvent(new ObjectRemovedEvent(_oid, name, oid).setAlreadyApplied(applyImmediately));
}
/** @deprecated Regenerate your DObject to remove this warning. */
@Deprecated protected void requestOidAdd (String name, int oid)
{
postEvent(new ObjectAddedEvent(_oid, name, oid));
}
/** @deprecated Regenerate your DObject to remove this warning. */
@Deprecated protected void requestOidRemove (String name, int oid)
{
postEvent(new ObjectRemovedEvent(_oid, name, oid));
}
/**
* Calls by derived instances when a set adder method was called.
*/
protected void requestEntryAdd (String name, DSet set, T entry)
{
// if we're on the authoritative server, we update the set immediately
boolean applyImmediately = isAuthoritative();
if (applyImmediately) {
set.add(entry);
}
// dispatch an entry added event
postEvent(new EntryAddedEvent(_oid, name, entry).setAlreadyApplied(applyImmediately));
}
/**
* Calls by derived instances when a set remover method was called.
*/
protected void requestEntryRemove (
String name, DSet set, Comparable> key)
{
// if we're on the authoritative server, we update the set immediately
T oldEntry = null;
if (isAuthoritative()) {
oldEntry = set.removeKey(key);
if (oldEntry == null) {
log.warning("Requested to remove non-element", "set", name, "key", key,
new Exception());
}
}
// dispatch an entry removed event
postEvent(new EntryRemovedEvent(_oid, name, key).setOldEntry(oldEntry));
}
/**
* Calls by derived instances when a set updater method was called.
*/
protected void requestEntryUpdate (String name, DSet set, T entry)
{
requestEntryUpdate(name, set, entry, Transport.DEFAULT);
}
/**
* Calls by derived instances when a set updater method was called.
*/
protected void requestEntryUpdate (
String name, DSet set, T entry, Transport transport)
{
// if we're on the authoritative server, we update the set immediately
T oldEntry = null;
if (isAuthoritative()) {
oldEntry = set.update(entry);
if (oldEntry == null) {
log.warning("Set update had no old entry", "name", name, "entry", entry,
new Exception());
}
}
// dispatch an entry updated event
postEvent(new EntryUpdatedEvent(_oid, name, entry).
setOldEntry(oldEntry).setTransport(transport));
}
protected boolean isAuthoritative ()
{
return _omgr != null && _omgr.isManager(this);
}
/**
* Returns the {@link Accessor} for the field with the specified name throws an {@link
* IllegalArgumentException}.
*/
protected final Accessor getAccessor (String name)
{
int low = 0, high = _accessors.length-1;
while (low <= high) {
int mid = (low + high) >> 1;
Accessor midVal = _accessors[mid];
int cmp = midVal.name.compareTo(name);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
return midVal; // key 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()]);
}
/**
* Returns the index of the identified listener, or -1 if not found.
*/
protected int getListenerIndex (ChangeListener listener)
{
if (_listeners == null) {
return -1;
}
for (int ii = 0, ll = _listeners.length; ii < ll; ii++) {
Object olistener = _listeners[ii];
if (olistener == listener || (olistener instanceof WeakReference> &&
((WeakReference>)olistener).get() == listener)) {
return ii;
}
}
return -1;
}
/** Our object id. */
protected int _oid;
/** An array of our field accessors, sorted for efficient lookup. */
protected transient Accessor[] _accessors;
/** A reference to our object manager. */
protected transient DObjectManager _omgr;
/** The entity that tells us if an event or subscription request should be allowed. */
protected transient AccessController _controller;
/** A list of outstanding locks. */
protected transient Object[] _locks;
/** Our subscribers list. */
protected transient Object[] _subs;
/** Our event listeners list. */
protected transient Object[] _listeners;
/** Our subscriber count. */
protected transient int _scount;
/** The compound event associated with our transaction, if we're currently in a transaction. */
protected transient CompoundEvent _tevent;
/** The nesting depth of our current transaction. */
protected transient int _tcount;
/** Whether or not our nested transaction has been cancelled. */
protected transient boolean _tcancelled;
/** 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. */
protected transient Object[] _locattrs = ArrayUtil.EMPTY_OBJECT;
/** Maintains a mapping of sorted accessor arrays for each distributed object class. */
protected static Map, Accessor[]> _atable = Maps.newHashMap();
}