package org.archstudio.bna.logics.tracking;
import static org.archstudio.sysutils.SystemUtils.filter;
import java.util.Collection;
import java.util.Map;
import org.archstudio.bna.BNAModelEvent;
import org.archstudio.bna.IBNAModelListener;
import org.archstudio.bna.IBNAWorld;
import org.archstudio.bna.IThing;
import org.archstudio.bna.ThingEvent;
import org.archstudio.bna.keys.IThingKey;
import org.archstudio.bna.logics.AbstractThingLogic;
import org.archstudio.bna.utils.BNAUtils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
public class ThingValueTrackingLogic extends AbstractThingLogic implements IBNAModelListener {
private final LoadingCache<IThingKey<?>, SetMultimap<Object, Object>> keyToValueToThingIDsCache = CacheBuilder
.newBuilder().build(new CacheLoader<IThingKey<?>, SetMultimap<Object, Object>>() {
@Override
public SetMultimap<Object, Object> load(IThingKey<?> input) {
SetMultimap<Object, Object> m = HashMultimap.create();
for (IThing t : model.getAllThings()) {
Object value = t.get(input);
if (value != null) {
m.put(value, t.getID());
}
}
return m;
}
});
public ThingValueTrackingLogic(IBNAWorld world) {
super(world);
}
@Override
public void dispose() {
BNAUtils.checkLock();
keyToValueToThingIDsCache.invalidateAll();
super.dispose();
}
@Override
public void bnaModelChanged(BNAModelEvent evt) {
BNAUtils.checkLock();
switch (evt.getEventType()) {
case THING_ADDED: {
IThing thing = evt.getTargetThing();
Object thingID = thing.getID();
for (Map.Entry<IThingKey<?>, ?> entry : thing.entrySet()) {
update(thingID, entry.getKey(), null, entry.getValue());
}
}
break;
case THING_CHANGED: {
IThing thing = evt.getTargetThing();
ThingEvent te = evt.getThingEvent();
update(thing.getID(), te.getPropertyName(), te.getOldPropertyValue(), te.getNewPropertyValue());
}
break;
case THING_REMOVED: {
IThing thing = evt.getTargetThing();
Object thingID = thing.getID();
for (Map.Entry<IThingKey<?>, ?> entry : thing.entrySet()) {
update(thingID, entry.getKey(), entry.getValue(), null);
}
}
break;
default:
// do nothing
}
}
private <V> void update(Object thingID, IThingKey<V> forKey, Object oldValue, Object newValue) {
Multimap<Object, Object> valueToThingIDsMap = keyToValueToThingIDsCache.getIfPresent(forKey);
if (valueToThingIDsMap != null) {
if (oldValue != null) {
valueToThingIDsMap.get(oldValue).remove(thingID);
}
if (newValue != null) {
valueToThingIDsMap.get(newValue).add(thingID);
}
}
}
public <V> Collection<Object> getThingIDs(IThingKey<V> withKey, V ofValue) {
BNAUtils.checkLock();
return Lists.newArrayList(keyToValueToThingIDsCache.getUnchecked(withKey).get(ofValue));
}
public <V> Collection<IThing> getThings(IThingKey<V> withKey, V ofValue) {
BNAUtils.checkLock();
return model.getThingsByID(keyToValueToThingIDsCache.getUnchecked(withKey).get(ofValue));
}
public <V, T extends IThing> Collection<T> getThings(IThingKey<V> withKey, V ofValue, Class<T> ofType) {
BNAUtils.checkLock();
return filter(model.getThingsByID(keyToValueToThingIDsCache.getUnchecked(withKey).get(ofValue)), ofType);
}
public <V1, V2> Collection<Object> getThingIDs(IThingKey<V1> withKey1, V1 ofValue1, IThingKey<V2> withKey2,
V2 ofValue2) {
BNAUtils.checkLock();
return Lists.newArrayList(Sets.intersection(keyToValueToThingIDsCache.getUnchecked(withKey1).get(ofValue1),
keyToValueToThingIDsCache.getUnchecked(withKey2).get(ofValue2)));
}
public <V1, V2> Collection<IThing> getThings(IThingKey<V1> withKey1, V1 ofValue1, IThingKey<V2> withKey2,
V2 ofValue2) {
BNAUtils.checkLock();
return model.getThingsByID(Sets.intersection(keyToValueToThingIDsCache.getUnchecked(withKey1).get(ofValue1),
keyToValueToThingIDsCache.getUnchecked(withKey2).get(ofValue2)));
}
public <V1, V2, T extends IThing> Collection<T> getThings(IThingKey<V1> withKey1, V1 ofValue1,
IThingKey<V2> withKey2, V2 ofValue2, Class<T> ofType) {
BNAUtils.checkLock();
return filter(model.getThingsByID(Sets.intersection(
keyToValueToThingIDsCache.getUnchecked(withKey1).get(ofValue1),
keyToValueToThingIDsCache.getUnchecked(withKey2).get(ofValue2))), ofType);
}
public Collection<IThing> getThings(IThingKey<?> withKey) {
BNAUtils.checkLock();
return model.getThingsByID(keyToValueToThingIDsCache.getUnchecked(withKey).values());
}
}