package org.archstudio.myx.fw;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.archstudio.myx.fw.MyxRegistryEvent.EventType;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
/**
* A thread safe registry for managing arbitrary objects associated with Myx bricks.
* <p/>
* The Myx brick and its objects are stored using weak references and thus garbage collected when no longer reachable.
* The registry is used, among other things, as a source of listeners for service objects passed through "out"
* interfaces.
*
* @author sahendrickson@gmail.com (Scott A. Hendrickson)
*/
public final class MyxRegistry {
/** The singleton MyxRegistry object. */
private static final MyxRegistry singleton = new MyxRegistry();
/** Returns the singleton MyxRegistry. */
public static final MyxRegistry getSharedInstance() {
return singleton;
}
/** Maintains the collection of Myx bricks and objects associated with them. */
private final Map<IMyxBrick, List<WeakReference<Object>>> brickToObjectsMap =
Collections.synchronizedMap(new WeakHashMap<IMyxBrick, List<WeakReference<Object>>>());
/** The listeners to be informed of changes to the MyxRegistry. */
private List<IMyxRegistryListener> listeners = new CopyOnWriteArrayList<IMyxRegistryListener>();
/** Prevent instantiation. Instead use {@link #getSharedInstance()}. */
private MyxRegistry() {
}
/**
* Registers a brick with the registry.
*
* @param brick The brick to register.
*/
public void registerBrick(IMyxBrick brick) {
Preconditions.checkNotNull(brick);
synchronized (this) {
if (brickToObjectsMap.get(brick) == null) {
brickToObjectsMap.put(brick, new CopyOnWriteArrayList<WeakReference<Object>>());
}
notifyAll();
fireMyxRegistryEvent(MyxRegistryEvent.EventType.BRICK_REGISTERED, brick, null);
}
}
/**
* Use {@link #registerBrick(IMyxBrick)}.
*/
@Deprecated
public void register(IMyxBrick brick) {
registerBrick(brick);
}
/**
* Unregisters a brick with the registry.
*
* @param brick The brick to unregister.
*/
public void unregisterBrick(IMyxBrick brick) {
Preconditions.checkNotNull(brick);
synchronized (this) {
brickToObjectsMap.remove(brick);
}
}
/**
* User {@link #unregisterBrick(IMyxBrick)}.
*/
@Deprecated
public void unregister(IMyxBrick brick) {
unregisterBrick(brick);
}
/**
* Returns the brick with the given name, or <code>null</code> if none with that name are registered.
*
* @param name The Myx name to look for.
* @return the brick with the given name, or <code>null</code> if none with that name are registered.
*/
public synchronized IMyxBrick getBrick(IMyxName name) {
Preconditions.checkNotNull(name);
Preconditions.checkNotNull(name.getName());
for (IMyxBrick brick : brickToObjectsMap.keySet()) {
if (name.equals(MyxUtils.getName(brick))) {
return brick;
}
}
return null;
}
/**
* Waits for and returns the brick with the given name, blocking until available.
*
* @param name The Myx name to look for.
* @return the brick with the given name.
*/
@SuppressWarnings("unchecked")
public <B extends IMyxBrick> B waitForBrick(IMyxName name) {
Preconditions.checkNotNull(name);
Preconditions.checkNotNull(name.getName());
IMyxBrick brick = null;
while ((brick = getBrick(name)) == null) {
synchronized (this) {
try {
wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return (B) brick;
}
/**
* Returns the brick of the given type, or <code>null</code> if none with that type are registered.
*
* @param brickClass The type of the brick to retrieve.
* @return the brick of the given type, or <code>null</code> if none with that type are registered.
*/
@SuppressWarnings("unchecked")
public synchronized <B extends IMyxBrick> B getBrick(Class<B> brickClass) {
Preconditions.checkNotNull(brickClass);
for (IMyxBrick brick : brickToObjectsMap.keySet()) {
if (brickClass.equals(brick.getClass())) {
return (B) brick;
}
}
return null;
}
/**
* Returns the brick of the given type, blocking until available.
*
* @param brickClass The type of the brick to retrieve.
* @return the brick of the given type.
*/
public <B extends IMyxBrick> B waitForBrick(Class<B> brickClass) {
Preconditions.checkNotNull(brickClass);
B brick = null;
while ((brick = getBrick(brickClass)) == null) {
synchronized (this) {
try {
wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return brick;
}
/**
* Adds an object to the given brick, registering the brick if it is not already registered.
*
* @param brick The brick to associate the object with.
* @param object The object to associate with the brick.
*/
public void registerObject(IMyxBrick brick, Object object) {
Preconditions.checkNotNull(brick);
Preconditions.checkNotNull(object);
synchronized (this) {
registerBrick(brick);
List<WeakReference<Object>> objectsList = brickToObjectsMap.get(brick);
for (WeakReference<Object> objectListEntry : objectsList) {
Object weakObject = objectListEntry.get();
if (object == weakObject) {
return;
}
}
objectsList.add(new WeakReference<>(object));
notifyAll();
fireMyxRegistryEvent(MyxRegistryEvent.EventType.OBJECT_REGISTERED, brick, object);
}
}
/**
* Use {@link #registerObject(IMyxBrick, Object)}.
*/
@Deprecated
public void map(IMyxBrick brick, Object object) {
registerObject(brick, object);
}
/**
* Removes an object from the given brick, registering the brick if it is not already registered.
*
* @param brick The brick with the associated object.
* @param object The object to remove.
*/
public void unregisterObject(IMyxBrick brick, Object object) {
Preconditions.checkNotNull(brick);
Preconditions.checkNotNull(object);
synchronized (this) {
registerBrick(brick);
List<WeakReference<Object>> weakObjects = brickToObjectsMap.get(brick);
for (WeakReference<Object> weakReference : weakObjects) {
Object weakObject = weakReference.get();
// Clean up garbage collected references.
if (weakObject == null) {
weakObjects.remove(weakReference);
continue;
}
if (object.equals(weakObject)) {
weakObjects.remove(weakReference);
continue;
}
}
}
}
/**
* Use {@link #unregisterObject(IMyxBrick, Object)}.
*/
@Deprecated
public void unmap(IMyxBrick brick, Object object) {
unregisterObject(brick, object);
}
/**
* Returns the objects associated with the brick, registering the brick if it is not already registered.
*
* @param brick The brick with the associated objects.
* @return a list of all objects associated with the brick.
*/
public synchronized List<? extends Object> getObjects(IMyxBrick brick) {
Preconditions.checkNotNull(brick);
registerBrick(brick);
List<WeakReference<Object>> weakObjects = brickToObjectsMap.get(brick);
List<Object> objects = Lists.newArrayListWithCapacity(weakObjects.size());
for (Iterator<WeakReference<Object>> i = weakObjects.iterator(); i.hasNext();) {
WeakReference<Object> weakReference = i.next();
Object weakObject = weakReference.get();
// Clean up garbage collected references.
if (weakObject == null) {
weakObjects.remove(weakReference);
continue;
}
objects.add(weakObject);
}
return objects;
}
/**
* Returns the objects associated with the brick of the given type, registering the brick if it is not already
* registered.
*
* @param brick The brick with the associated objects.
* @param objectClass The required type for filtered objects.
* @return a list of all objects associated with the brick.
*/
@SuppressWarnings("unchecked")
public <T> Iterable<T> getObjects(IMyxBrick brick, Class<T> objectClass) {
Preconditions.checkNotNull(brick);
Preconditions.checkNotNull(objectClass);
registerBrick(brick);
List<WeakReference<Object>> weakObjects = brickToObjectsMap.get(brick);
List<T> objects = Lists.newArrayListWithCapacity(weakObjects.size());
for (Iterator<WeakReference<Object>> i = weakObjects.iterator(); i.hasNext();) {
WeakReference<Object> weakReference = i.next();
Object weakObject = weakReference.get();
// Clean up garbage collected references.
if (weakObject == null) {
weakObjects.remove(weakReference);
continue;
}
if (objectClass.isInstance(weakObject)) {
objects.add((T) weakObject);
}
}
return objects;
}
/**
* Adds the listener to the collection of listeners that will be notified when a brick or object is registered with
* the registry.
*
* @param listener The listener.
*/
public synchronized void addMyxRegistryListener(IMyxRegistryListener listener) {
Preconditions.checkNotNull(listeners);
listeners.add(listener);
}
/**
* Removes the listener from the collection of listeners that will be notified when a brick or object is registered
* with the registry.
*
* @param listener The listener.
*/
public synchronized void removeMyxRegistryListener(IMyxRegistryListener l) {
Preconditions.checkNotNull(listeners);
listeners.remove(l);
}
/**
* Fires a event to listeners that should be notified when a brick or object is registered with the registry.
*
* @param eventType The type of event.
* @param brick The brick that was modified.
* @param object The object that was added.
*/
private void fireMyxRegistryEvent(EventType eventType, IMyxBrick brick, Object object) {
Preconditions.checkNotNull(eventType);
Preconditions.checkNotNull(brick);
Preconditions.checkState(Thread.holdsLock(this));
MyxRegistryEvent evt = new MyxRegistryEvent(eventType, brick, object);
for (IMyxRegistryListener listener : listeners) {
listener.handleMyxRegistryEvent(evt);
}
}
}