More progress on the distributed object stuff.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@19 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
//
|
//
|
||||||
// $Id: AttributeChangedEvent.java,v 1.1 2001/06/01 07:12:13 mdb Exp $
|
// $Id: AttributeChangedEvent.java,v 1.2 2001/06/01 19:56:13 mdb Exp $
|
||||||
|
|
||||||
package com.threerings.cocktail.cher.dobj;
|
package com.threerings.cocktail.cher.dobj;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import com.threerings.cocktail.cher.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute changed event is dispatched when a single attribute of a
|
* An attribute changed event is dispatched when a single attribute of a
|
||||||
* distributed object has changed. It can also be constructed to request
|
* distributed object has changed. It can also be constructed to request
|
||||||
@@ -92,6 +95,24 @@ public class AttributeChangedEvent extends DEvent
|
|||||||
return ((Double)_value).doubleValue();
|
return ((Double)_value).doubleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this attribute change to the object.
|
||||||
|
*/
|
||||||
|
public boolean applyToObject (DObject target)
|
||||||
|
throws ObjectAccessException
|
||||||
|
{
|
||||||
|
// look up the setter for this object
|
||||||
|
Method setter = DEventUtil.getSetter(target.getClass(), _name);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setter.invoke(target, new Object[] { _value });
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ObjectAccessException("Reflection error: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected String _name;
|
protected String _name;
|
||||||
protected Object _value;
|
protected Object _value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// $Id: DEvent.java,v 1.2 2001/06/01 07:12:13 mdb Exp $
|
// $Id: DEvent.java,v 1.3 2001/06/01 19:56:13 mdb Exp $
|
||||||
|
|
||||||
package com.threerings.cocktail.cher.dobj;
|
package com.threerings.cocktail.cher.dobj;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ package com.threerings.cocktail.cher.dobj;
|
|||||||
* notification purposes, without making any modifications to the object
|
* notification purposes, without making any modifications to the object
|
||||||
* that defines the delivery group (the object's subscribers).
|
* that defines the delivery group (the object's subscribers).
|
||||||
*/
|
*/
|
||||||
public class DEvent
|
public abstract class DEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Returns the oid of the object that is the target of this event.
|
* Returns the oid of the object that is the target of this event.
|
||||||
@@ -19,6 +19,22 @@ public class DEvent
|
|||||||
return _toid;
|
return _toid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the attribute modifications represented by this event to
|
||||||
|
* the specified target object. This is called by the distributed
|
||||||
|
* object manager in the course of dispatching events and should not
|
||||||
|
* be called directly.
|
||||||
|
*
|
||||||
|
* @exception ObjectAccessException thrown if there is any problem
|
||||||
|
* applying the event to the object (invalid attribute, etc.).
|
||||||
|
*
|
||||||
|
* @return true if the object manager should go on to notify the
|
||||||
|
* object's subscribers of this event, false if the event should be
|
||||||
|
* treated silently and the subscribers should not be notified.
|
||||||
|
*/
|
||||||
|
public abstract boolean applyToObject (DObject target)
|
||||||
|
throws ObjectAccessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new distributed object event that pertains to the
|
* Constructs a new distributed object event that pertains to the
|
||||||
* specified distributed object.
|
* specified distributed object.
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
//
|
||||||
|
// $Id: DEventUtil.java,v 1.1 2001/06/01 19:56:13 mdb Exp $
|
||||||
|
|
||||||
|
package com.threerings.cocktail.cher.dobj;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository for distributed object event related utility functions.
|
||||||
|
*/
|
||||||
|
public class DEventUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Looks up and returns the method used to set an attribute of the
|
||||||
|
* specified name on an instance of the specified distributed object
|
||||||
|
* class.
|
||||||
|
*/
|
||||||
|
public static Method getSetter (Class clazz, String name)
|
||||||
|
throws ObjectAccessException
|
||||||
|
{
|
||||||
|
// first see if it's cached
|
||||||
|
Method setter = (Method)_setterCache.get(name);
|
||||||
|
if (setter != null) {
|
||||||
|
return setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the attribute name into a setter name
|
||||||
|
String sname = setterName(name);
|
||||||
|
try {
|
||||||
|
// look up that method (we don't have the parameter types, so
|
||||||
|
// we have to do our own name matching)
|
||||||
|
Method[] methods = clazz.getMethods();
|
||||||
|
for (int i = 0; i < methods.length; i++) {
|
||||||
|
if (methods[i].getName().equals(sname)) {
|
||||||
|
setter = methods[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
throw new ObjectAccessException("Reflection error: " + se);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we found a matching method
|
||||||
|
if (setter == null) {
|
||||||
|
String errmsg = "No setter for attribute '" + name + "'.";
|
||||||
|
throw new ObjectAccessException(errmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and cache it
|
||||||
|
_setterCache.put(name, setter);
|
||||||
|
return setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String setterName (String name)
|
||||||
|
{
|
||||||
|
StringBuffer sname = new StringBuffer();
|
||||||
|
sname.append("get");
|
||||||
|
sname.append(Character.toUpperCase(name.charAt(0)));
|
||||||
|
sname.append(name.substring(1));
|
||||||
|
return sname.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static HashMap _setterCache = new HashMap();
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// $Id: DObject.java,v 1.6 2001/06/01 05:17:16 mdb Exp $
|
// $Id: DObject.java,v 1.7 2001/06/01 19:56:13 mdb Exp $
|
||||||
|
|
||||||
package com.threerings.cocktail.cher.dobj;
|
package com.threerings.cocktail.cher.dobj;
|
||||||
|
|
||||||
@@ -73,17 +73,17 @@ import java.util.ArrayList;
|
|||||||
public class DObject
|
public class DObject
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructs a new distributed object with the supplied object id.
|
* Initializes this distributed object with the supplied object id and
|
||||||
* This should not be called directly, rather objects should be
|
* distributed object manager. This is called by the distributed
|
||||||
* created via the distributed object manager.
|
* object manager when an object is created and registered with the
|
||||||
|
* system. Don't call this function yourself.
|
||||||
*
|
*
|
||||||
* @see DObjectManager.createObject
|
* @see DObjectManager.createObject
|
||||||
*/
|
*/
|
||||||
public DObject (int oid, DObjectManager manager)
|
public void init (int oid, DObjectManager mgr)
|
||||||
{
|
{
|
||||||
_oid = oid;
|
_oid = oid;
|
||||||
_mgr = manager;
|
_mgr = mgr;
|
||||||
_subscribers = new ArrayList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,10 +106,10 @@ public class DObject
|
|||||||
* <p> If the specified subscriber is already subscribed to this
|
* <p> If the specified subscriber is already subscribed to this
|
||||||
* object, they will not be added to the list a second time.
|
* object, they will not be added to the list a second time.
|
||||||
*/
|
*/
|
||||||
public void addSubscriber (Subscriber subscriber)
|
public void addSubscriber (Subscriber sub)
|
||||||
{
|
{
|
||||||
if (!_subscribers.contains(subscriber)) {
|
if (!_subscribers.contains(sub)) {
|
||||||
_subscribers.add(subscriber);
|
_subscribers.add(sub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,12 +121,66 @@ public class DObject
|
|||||||
* subscriber is not currently on the list of subscribers for this
|
* subscriber is not currently on the list of subscribers for this
|
||||||
* object, nothing happens.
|
* object, nothing happens.
|
||||||
*/
|
*/
|
||||||
public void removeSubscriber (Subscriber subscriber)
|
public void removeSubscriber (Subscriber sub)
|
||||||
{
|
{
|
||||||
_subscribers.remove(subscriber);
|
_subscribers.remove(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to ensure that the specified subscriber has access to this
|
||||||
|
* object. This will be called before satisfying any fetch or
|
||||||
|
* subscription request. By default objects are accessible to all
|
||||||
|
* subscriber, but certain objects may wish to implement more fine
|
||||||
|
* grained access control.
|
||||||
|
*
|
||||||
|
* @param sub the subscriber that will fetch or subscribe to this
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* @return true if the subscriber has access to the object, false if
|
||||||
|
* they do not.
|
||||||
|
*/
|
||||||
|
public boolean checkPermissions (Subscriber sub)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to ensure that this event which is about to be processed,
|
||||||
|
* has the appropriate permissions. By default objects accept all
|
||||||
|
* manner of events, but certain objects may wish to implement more
|
||||||
|
* fine grained access control.
|
||||||
|
*
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
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 subscribers of this object.
|
||||||
|
*
|
||||||
|
* @param event the event that was just applied.
|
||||||
|
*/
|
||||||
|
public void notifySubscribers (DEvent event)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _subscribers.size(); i++) {
|
||||||
|
Subscriber sub = (Subscriber)_subscribers.get(i);
|
||||||
|
// notify the subscriber
|
||||||
|
if (!sub.handleEvent(event, this)) {
|
||||||
|
// if they return false, we need to remove them from the
|
||||||
|
// subscriber list
|
||||||
|
_subscribers.remove(i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int _oid;
|
protected int _oid;
|
||||||
protected DObjectManager _mgr;
|
protected DObjectManager _mgr;
|
||||||
protected ArrayList _subscribers;
|
protected ArrayList _subscribers = new ArrayList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
//
|
||||||
|
// $Id: PresentsDObjectMgr.java,v 1.1 2001/06/01 19:56:13 mdb Exp $
|
||||||
|
|
||||||
|
package com.threerings.cocktail.cher.server;
|
||||||
|
|
||||||
|
import com.samskivert.util.Queue;
|
||||||
|
|
||||||
|
import com.threerings.cocktail.cher.Log;
|
||||||
|
import com.threerings.cocktail.cher.dobj.*;
|
||||||
|
import com.threerings.cocktail.cher.util.IntMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cher distributed object manager implements the
|
||||||
|
* <code>DObjectManager</code> interface, providing an object manager that
|
||||||
|
* runs on the server. By virtue of running on the server, it manages its
|
||||||
|
* objects directly rather than managing proxies of objects which is what
|
||||||
|
* is done on the client. Thus it simply queues up events and dispatches
|
||||||
|
* them to subscribers.
|
||||||
|
*
|
||||||
|
* <p> The server object manager is meant to run on the main thread of the
|
||||||
|
* server application and thus provides a method to be invoked by the
|
||||||
|
* application main thread which won't return until the manager has been
|
||||||
|
* requested to shut down.
|
||||||
|
*/
|
||||||
|
public class CherDObjectMgr implements DObjectManager
|
||||||
|
{
|
||||||
|
// inherit documentation from the interface
|
||||||
|
public void createObject (Class dclass, Subscriber target,
|
||||||
|
boolean subscribe)
|
||||||
|
{
|
||||||
|
// queue up a create object event
|
||||||
|
postEvent(new CreateObjectEvent(dclass, target, subscribe));
|
||||||
|
}
|
||||||
|
|
||||||
|
// inherit documentation from the interface
|
||||||
|
public void subscribeToObject (int oid, Subscriber target)
|
||||||
|
{
|
||||||
|
// queue up an access object event
|
||||||
|
postEvent(new AccessObjectEvent(oid, target, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// inherit documentation from the interface
|
||||||
|
public void fetchObject (int oid, Subscriber target)
|
||||||
|
{
|
||||||
|
// queue up an access object event
|
||||||
|
postEvent(new AccessObjectEvent(oid, target, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// inherit documentation from the interface
|
||||||
|
public void postEvent (DEvent event)
|
||||||
|
{
|
||||||
|
// just append it to the queue
|
||||||
|
_evqueue.append(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the dobjmgr and prepares it for operation.
|
||||||
|
*/
|
||||||
|
public void init ()
|
||||||
|
{
|
||||||
|
// we create a dummy object to live as oid zero and we'll use that
|
||||||
|
// for some internal event trickery
|
||||||
|
DObject dummy = new DObject();
|
||||||
|
dummy.init(0, this);
|
||||||
|
_objects.put(0, new DObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the dobjmgr event loop until it is requested to exit. This
|
||||||
|
* should be called from the main application thread.
|
||||||
|
*/
|
||||||
|
public void run ()
|
||||||
|
{
|
||||||
|
Log.info("DOMGR running.");
|
||||||
|
|
||||||
|
while (isRunning()) {
|
||||||
|
// pop the next event off the queue
|
||||||
|
DEvent event = (DEvent)_evqueue.get();
|
||||||
|
|
||||||
|
// look up the target object
|
||||||
|
DObject target = (DObject)_objects.get(event.getTargetOid());
|
||||||
|
if (target == null) {
|
||||||
|
Log.warning("Event target no longer exists " +
|
||||||
|
"[event=" + event + "].");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the event's permissions
|
||||||
|
if (!target.checkPermissions(event)) {
|
||||||
|
Log.warning("Event failed permissions check " +
|
||||||
|
"[event=" + event + ", target=" + target + "].");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// everything's good so far, apply the event to the object
|
||||||
|
if (!event.applyToObject(target)) {
|
||||||
|
// if the event returns false from applyToObject, this
|
||||||
|
// means it's a silent event and we shouldn't notify
|
||||||
|
// the subscribers
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warning("Failure applying event [event=" + event +
|
||||||
|
", target=" + target + ", error=" + e + "].");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// and notify the object's subscribers
|
||||||
|
target.notifySubscribers(event);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warning("Failure dispatching event [event=" + event +
|
||||||
|
", target=" + target + "].");
|
||||||
|
Log.logStackTrace(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.info("DOMGR exited.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that the dobjmgr shut itself down. It will exit the event
|
||||||
|
* processing loop which cause <code>run()</code> to return.
|
||||||
|
*/
|
||||||
|
public void shutdown ()
|
||||||
|
{
|
||||||
|
_running = false;
|
||||||
|
// stick a bogus object on the event queue to ensure that the mgr
|
||||||
|
// wakes up and smells the coffee
|
||||||
|
_evqueue.append(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized boolean isRunning ()
|
||||||
|
{
|
||||||
|
return _running;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getNextOid ()
|
||||||
|
{
|
||||||
|
// look for the next unused oid. in theory if we had two billion
|
||||||
|
// objects, this would loop infinitely, but the world would have
|
||||||
|
// come to an end long before we had two billion objects
|
||||||
|
do {
|
||||||
|
_nextOid = (_nextOid + 1) % Integer.MAX_VALUE;
|
||||||
|
} while (_objects.contains(_nextOid));
|
||||||
|
|
||||||
|
return _nextOid;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean _running = true;
|
||||||
|
protected Queue _evqueue = new Queue();
|
||||||
|
protected IntMap _objects = new IntMap();
|
||||||
|
protected int _nextOid = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to create a distributed object and register it with the
|
||||||
|
* system.
|
||||||
|
*/
|
||||||
|
protected class CreateObjectEvent extends DEvent
|
||||||
|
{
|
||||||
|
public CreateObjectEvent (Class clazz, Subscriber target,
|
||||||
|
boolean subscribe)
|
||||||
|
{
|
||||||
|
super(0); // target the fake object
|
||||||
|
_class = clazz;
|
||||||
|
_target = target;
|
||||||
|
_subscribe = subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean applyToObject (DObject target)
|
||||||
|
throws ObjectAccessException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// create a new instance of this object
|
||||||
|
DObject obj = (DObject)_class.newInstance();
|
||||||
|
int oid = getNextOid();
|
||||||
|
|
||||||
|
// initialize this object
|
||||||
|
obj.init(oid, CherDObjectMgr.this);
|
||||||
|
// insert it into the table
|
||||||
|
_objects.put(oid, obj);
|
||||||
|
|
||||||
|
if (_target != null) {
|
||||||
|
// add the subscriber to this object's subscriber list
|
||||||
|
// if they requested it
|
||||||
|
if (_subscribe) {
|
||||||
|
obj.addSubscriber(_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let the target subscriber know that their object is
|
||||||
|
// available
|
||||||
|
_target.objectAvailable(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warning("Object creation failure " +
|
||||||
|
"[class=" + _class.getName() +
|
||||||
|
", error=" + e + "].");
|
||||||
|
|
||||||
|
// let the subscriber know shit be fucked
|
||||||
|
if (_target != null) {
|
||||||
|
String errmsg = "Object instantiation failed: " + e;
|
||||||
|
_target.requestFailed(new ObjectAccessException(errmsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// and return false to ensure that this event is not
|
||||||
|
// dispatched to the fake object's subscriber list (even
|
||||||
|
// though it's empty)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Class _class;
|
||||||
|
protected Subscriber _target;
|
||||||
|
protected boolean _subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to make an object available to a subscriber (with or without
|
||||||
|
* the associated subscription).
|
||||||
|
*/
|
||||||
|
protected class AccessObjectEvent extends DEvent
|
||||||
|
{
|
||||||
|
public AccessObjectEvent (int oid, Subscriber target,
|
||||||
|
boolean subscribe)
|
||||||
|
{
|
||||||
|
super(0); // target the bogus object
|
||||||
|
_oid = oid;
|
||||||
|
_target = target;
|
||||||
|
_subscribe = subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean applyToObject (DObject target)
|
||||||
|
throws ObjectAccessException
|
||||||
|
{
|
||||||
|
// look up the target object
|
||||||
|
DObject obj = (DObject)_objects.get(_oid);
|
||||||
|
|
||||||
|
// if it don't exist, let them know
|
||||||
|
if (obj == null) {
|
||||||
|
_target.requestFailed(new NoSuchObjectException(_oid));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check permissions
|
||||||
|
if (!obj.checkPermissions(_target)) {
|
||||||
|
String errmsg = "m.access_denied\t" + _oid;
|
||||||
|
_target.requestFailed(new ObjectAccessException(errmsg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if they wanted to subscribe, do so
|
||||||
|
if (_subscribe) {
|
||||||
|
obj.addSubscriber(_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let them know that things are groovy
|
||||||
|
_target.objectAvailable(obj);
|
||||||
|
|
||||||
|
// return false to ensure that this event is not dispatched to
|
||||||
|
// the fake object's subscriber list (even though it's empty)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int _oid;
|
||||||
|
protected Subscriber _target;
|
||||||
|
protected boolean _subscribe;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
//
|
||||||
|
// $Id: IntMap.java,v 1.1 2001/06/01 19:56:13 mdb Exp $
|
||||||
|
|
||||||
|
package com.threerings.cocktail.cher.util;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An int map is like a hash map, but with ints as keys. We avoid the
|
||||||
|
* annoyance of having to create <code>Integer</code> objects every time
|
||||||
|
* we want to lookup or insert values. Like <code>java.util.HashMap</code>
|
||||||
|
* it is not thread safe.
|
||||||
|
*/
|
||||||
|
public class IntMap
|
||||||
|
{
|
||||||
|
public final static int DEFAULT_BUCKETS = 64;
|
||||||
|
|
||||||
|
public IntMap (int buckets)
|
||||||
|
{
|
||||||
|
_buckets = new Record[buckets];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntMap ()
|
||||||
|
{
|
||||||
|
this(DEFAULT_BUCKETS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size ()
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put (int key, Object value)
|
||||||
|
{
|
||||||
|
int index = Math.abs(key)%_buckets.length;
|
||||||
|
Record rec = _buckets[index];
|
||||||
|
|
||||||
|
// either we start a new chain
|
||||||
|
if (rec == null) {
|
||||||
|
_buckets[index] = new Record(key, value);
|
||||||
|
_size++; // we're bigger
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// or we replace an element in an existing chain
|
||||||
|
Record prev = rec;
|
||||||
|
for (; rec != null; rec = rec.next) {
|
||||||
|
if (rec.key == key) {
|
||||||
|
rec.value = value; // we're not bigger
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prev = rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// or we append it to this chain
|
||||||
|
prev.next = new Record(key, value);
|
||||||
|
_size++; // we're bigger
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object get (int key)
|
||||||
|
{
|
||||||
|
int index = Math.abs(key)%_buckets.length;
|
||||||
|
for (Record rec = _buckets[index]; rec != null; rec = rec.next) {
|
||||||
|
if (rec.key == key) {
|
||||||
|
return rec.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains (int key)
|
||||||
|
{
|
||||||
|
return get(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object remove (int key)
|
||||||
|
{
|
||||||
|
int index = Math.abs(key)%_buckets.length;
|
||||||
|
Record rec = _buckets[index];
|
||||||
|
|
||||||
|
// if there's no chain, there's no object
|
||||||
|
if (rec == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe it's the first one in this chain
|
||||||
|
if (rec.key == key) {
|
||||||
|
_buckets[index] = rec.next;
|
||||||
|
_size--;
|
||||||
|
return rec.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// or maybe it's an element further down the chain
|
||||||
|
for (Record prev = rec; rec != null; rec = rec.next) {
|
||||||
|
if (rec.key == key) {
|
||||||
|
prev.next = rec.next;
|
||||||
|
_size--;
|
||||||
|
return rec.value;
|
||||||
|
}
|
||||||
|
prev = rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear ()
|
||||||
|
{
|
||||||
|
// abandon all of our hash chains (the joy of garbage collection)
|
||||||
|
for (int i = 0; i < _buckets.length; i++) {
|
||||||
|
_buckets[i] = null;
|
||||||
|
}
|
||||||
|
// zero out our size
|
||||||
|
_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration keys ()
|
||||||
|
{
|
||||||
|
return new Enumerator(_buckets, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration elements ()
|
||||||
|
{
|
||||||
|
return new Enumerator(_buckets, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Record
|
||||||
|
{
|
||||||
|
public Record next;
|
||||||
|
public int key;
|
||||||
|
public Object value;
|
||||||
|
|
||||||
|
public Record (int key, Object value)
|
||||||
|
{
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Enumerator implements Enumeration
|
||||||
|
{
|
||||||
|
public Enumerator (Record[] buckets, boolean returnKeys)
|
||||||
|
{
|
||||||
|
_buckets = buckets;
|
||||||
|
_index = buckets.length;
|
||||||
|
_returnKeys = returnKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoreElements ()
|
||||||
|
{
|
||||||
|
// if we're pointing to an entry, we've got more entries
|
||||||
|
if (_record != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search backward through the buckets looking for the next
|
||||||
|
// non-empty hash chain
|
||||||
|
while (_index-- > 0) {
|
||||||
|
if ((_record = _buckets[_index]) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// found no non-empty hash chains, we're done
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object nextElement ()
|
||||||
|
{
|
||||||
|
// if we're not pointing to an entry, search for the next
|
||||||
|
// non-empty hash chain
|
||||||
|
if (_record == null) {
|
||||||
|
while ((_index-- > 0) &&
|
||||||
|
((_record = _buckets[_index]) == null));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found a record, return it's value and move our record
|
||||||
|
// reference to it's successor
|
||||||
|
if (_record != null) {
|
||||||
|
Record r = _record;
|
||||||
|
_record = r.next;
|
||||||
|
return _returnKeys ? new Integer(r.key) : r.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NoSuchElementException("IntMapEnumerator");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _index;
|
||||||
|
private Record _record;
|
||||||
|
private Record[] _buckets;
|
||||||
|
private boolean _returnKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static void main (String[] args)
|
||||||
|
// {
|
||||||
|
// IntMap table = new IntMap();
|
||||||
|
|
||||||
|
// System.out.print("Inserting: ");
|
||||||
|
// for (int i = 10; i < 100; i++) {
|
||||||
|
// Integer value = new Integer(i);
|
||||||
|
// table.put(i, value);
|
||||||
|
// System.out.print("(" + i + "," + value + ")");
|
||||||
|
// }
|
||||||
|
// System.out.println("");
|
||||||
|
|
||||||
|
// System.out.print("Looking up: ");
|
||||||
|
// for (int i = 10; i < 100; i++) {
|
||||||
|
// System.out.print("(" + i + "," + table.get(i) + ")");
|
||||||
|
// }
|
||||||
|
// System.out.println("");
|
||||||
|
|
||||||
|
// System.out.print("Removing: ");
|
||||||
|
// for (int i = 10; i < 100; i++) {
|
||||||
|
// System.out.print("(" + i + "," + table.remove(i) + ")");
|
||||||
|
// }
|
||||||
|
// System.out.println("");
|
||||||
|
// }
|
||||||
|
|
||||||
|
private Record[] _buckets;
|
||||||
|
private int _size;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user