package org.archstudio.bna.utils;
import static com.google.common.base.Preconditions.checkArgument;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import org.archstudio.bna.IBNAWorld;
import org.archstudio.bna.IThingLogic;
import org.archstudio.bna.IThingLogicManager;
import org.archstudio.bna.IThingLogicManagerListener;
import org.archstudio.bna.ThingLogicManagerEvent;
import org.archstudio.bna.ThingLogicManagerEvent.EventType;
import org.archstudio.sysutils.FastMap;
import org.archstudio.sysutils.FilterableCopyOnWriteArrayList;
import org.archstudio.sysutils.SystemUtils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
public class DefaultThingLogicManager implements IThingLogicManager {
public boolean PROFILE = false;
protected final LoadingCache<Object, AtomicLong> profileStats = !PROFILE ? null : CacheBuilder.newBuilder()
.weakKeys().build(new CacheLoader<Object, AtomicLong>() {
@Override
public AtomicLong load(Object input) {
return new AtomicLong();
}
});
protected final IBNAWorld world;
public DefaultThingLogicManager(IBNAWorld world) {
this.world = world;
}
CopyOnWriteArrayList<IThingLogicManagerListener> listeners = Lists.newCopyOnWriteArrayList();
FilterableCopyOnWriteArrayList<IThingLogic> logics = FilterableCopyOnWriteArrayList.create();
Map<Class<?>, IThingLogic> typedLogics = new FastMap<>(true);
@Override
public void addThingLogicManagerListener(IThingLogicManagerListener l) {
listeners.add(l);
}
@Override
public void removeThingLogicManagerListener(IThingLogicManagerListener l) {
listeners.remove(l);
}
protected void fireThingLogicManagerEvent(EventType eventType, IThingLogic tl) {
if (!listeners.isEmpty()) {
ThingLogicManagerEvent evt = new ThingLogicManagerEvent(eventType, tl);
for (IThingLogicManagerListener l : listeners) {
l.handleThingLogicManagerEvent(evt);
}
}
}
@Override
@SuppressWarnings("unchecked")
public <L extends IThingLogic> L addThingLogic(Class<L> logicClass) {
BNAUtils.checkLock();
try {
L logic = (L) typedLogics.get(logicClass);
if (logic != null) {
return logic;
}
for (Constructor<?> c : logicClass.getConstructors()) {
Class<?>[] paramTypes = c.getParameterTypes();
if (paramTypes.length == 1) {
if (IBNAWorld.class.isAssignableFrom(paramTypes[0])) {
return addThingLogic((L) c.newInstance(world));
}
}
}
}
catch (Exception e) {
throw new IllegalArgumentException("Unable to instantiate logic: " + logicClass, e);
}
throw new IllegalArgumentException("Unable to instantiate logic: " + logicClass);
}
@Override
public <L extends IThingLogic> L addThingLogic(L l) {
BNAUtils.checkLock();
checkArgument(l.getBNAWorld() == world, "Logic is of a different world: %s", l);
checkArgument(!logics.contains(l), "Logic was already added: %s", l);
long time = System.nanoTime();
l.init();
logics.add(l);
typedLogics.put(l.getClass(), l);
if (PROFILE) {
time = System.nanoTime() - time;
profileStats.getUnchecked(l).addAndGet(time);
}
fireThingLogicManagerEvent(EventType.LOGIC_ADDED, l);
return l;
}
@Override
public void removeThingLogic(IThingLogic tl) {
BNAUtils.checkLock();
fireThingLogicManagerEvent(EventType.LOGIC_REMOVING, tl);
long time = System.nanoTime();
logics.remove(tl);
typedLogics.remove(tl.getClass());
tl.dispose();
if (PROFILE) {
time = System.nanoTime() - time;
profileStats.getUnchecked(tl).addAndGet(time);
}
fireThingLogicManagerEvent(EventType.LOGIC_REMOVED, tl);
}
@Override
public List<IThingLogic> getAllThingLogics() {
BNAUtils.checkLock();
return Lists.newArrayList(logics);
}
@Override
public <T> T getThingLogic(Class<T> ofType) {
BNAUtils.checkLock();
return SystemUtils.firstOrNull(Iterables.filter(logics, ofType));
}
@Override
public <T> Iterable<T> getThingLogics(Class<T> ofType) {
BNAUtils.checkLock();
return Iterables.filter(logics, ofType);
}
@Override
public void dispose() {
BNAUtils.checkLock();
if (PROFILE) {
System.err.println("Profile information for: " + this.getClass());
for (IThingLogic logic : logics) {
System.err.println(logic);
}
for (java.util.Map.Entry<Object, AtomicLong> entry : SystemUtils.sortedByValue(profileStats.asMap()
.entrySet())) {
System.err.println(entry.getValue() + "\t" + entry.getKey());
}
}
// perform removals in reverse order since latter logics often depend on former logics
for (IThingLogic logic : Lists.newArrayList(Lists.reverse(logics))) {
removeThingLogic(logic);
}
}
}