package org.archstudio.bna.logics.hints;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
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.facets.IHasAnchorPoint;
import org.archstudio.bna.facets.IHasAngle;
import org.archstudio.bna.facets.IHasBoundingBox;
import org.archstudio.bna.facets.IHasColor;
import org.archstudio.bna.facets.IHasEndpoints;
import org.archstudio.bna.facets.IHasGlow;
import org.archstudio.bna.facets.IHasMidpoints;
import org.archstudio.bna.facets.IHasMutableAnchorPoint;
import org.archstudio.bna.facets.IHasMutableAngle;
import org.archstudio.bna.facets.IHasMutableBoundingBox;
import org.archstudio.bna.facets.IHasMutableColor;
import org.archstudio.bna.facets.IHasMutableEndpoints;
import org.archstudio.bna.facets.IHasMutableGlow;
import org.archstudio.bna.facets.IHasMutableMidpoints;
import org.archstudio.bna.facets.IHasMutableReferencePoint;
import org.archstudio.bna.facets.IHasMutableSize;
import org.archstudio.bna.keys.IThingKey;
import org.archstudio.bna.keys.ThingKey;
import org.archstudio.bna.logics.AbstractThingLogic;
import org.archstudio.bna.logics.editing.ShowHideTagsLogic;
import org.archstudio.bna.logics.hints.IHintRepository.HintValue;
import org.archstudio.bna.logics.hints.synchronizers.PropertyHintSynchronizer;
import org.archstudio.bna.logics.information.HighlightLogic;
import org.archstudio.bna.logics.tracking.ThingValueTrackingLogic;
import org.archstudio.bna.things.labels.AnchoredLabelThing;
import org.archstudio.bna.things.utility.EnvironmentPropertiesThing;
import org.archstudio.bna.utils.Assemblies;
import org.archstudio.bna.utils.BNAUtils;
import org.archstudio.sysutils.Finally;
import org.archstudio.sysutils.SystemUtils;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.graphics.Point;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
public class SynchronizeHintsLogic extends AbstractThingLogic implements IBNAModelListener,
IHintRepositoryChangeListener {
public boolean DEBUG = false;
private static final IThingKey<Object> HINT_CONTEXT_KEY = ThingKey.create(Lists.newArrayList("hints-context",
SynchronizeHintsLogic.class));
private static final IThingKey<Boolean> HINTS_RESTORED_KEY = ThingKey.create(Lists.newArrayList("hints-restored",
SynchronizeHintsLogic.class));
protected final ThingValueTrackingLogic valueLogic;
protected final IHintRepository hintRepository;
public SynchronizeHintsLogic(IBNAWorld world, IHintRepository hintRepository) {
super(world);
this.valueLogic = logics.addThingLogic(ThingValueTrackingLogic.class);
this.hintRepository = hintRepository;
final Function<Object, Object> toPoint2D = new Function<Object, Object>() {
@Override
public Object apply(Object input) {
if (input instanceof Point) {
return BNAUtils.toPoint2D((Point) input);
}
return input;
}
};
final Function<Object, Object> toPoint2Ds = new Function<Object, Object>() {
@Override
public Object apply(Object input) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) input;
for (int i = 0; i < list.size(); i++) {
list.set(i, toPoint2D.apply(list.get(i)));
}
return input;
}
};
addHintSynchronizer(new PropertyHintSynchronizer(world, "local-scale",
EnvironmentPropertiesThing.LOCAL_SCALE_KEY, null, EnvironmentPropertiesThing.class));
addHintSynchronizer(new PropertyHintSynchronizer(world, "local-origin",
EnvironmentPropertiesThing.LOCAL_ORIGIN_KEY, null, EnvironmentPropertiesThing.class));
addHintSynchronizer(new PropertyHintSynchronizer(world, "bounds", IHasBoundingBox.BOUNDING_BOX_KEY, null,
IHasMutableBoundingBox.class, IHasMutableReferencePoint.USER_MAY_MOVE));
addHintSynchronizer(new PropertyHintSynchronizer(world, "bounds", IHasBoundingBox.BOUNDING_BOX_KEY, null,
IHasMutableBoundingBox.class, IHasMutableSize.USER_MAY_RESIZE));
addHintSynchronizer(new PropertyHintSynchronizer(world, "location", IHasAnchorPoint.ANCHOR_POINT_KEY,
toPoint2D, IHasMutableAnchorPoint.class, new Predicate<IThing>() {
@Override
public boolean apply(IThing input) {
return !(input instanceof AnchoredLabelThing);
}
}, IHasMutableReferencePoint.USER_MAY_MOVE));
addHintSynchronizer(new PropertyHintSynchronizer(world, "tag-location", IHasAnchorPoint.ANCHOR_POINT_KEY,
toPoint2D, IHasMutableAnchorPoint.class, new Predicate<IThing>() {
@Override
public boolean apply(IThing input) {
return input instanceof AnchoredLabelThing;
}
}, IHasMutableReferencePoint.USER_MAY_MOVE));
addHintSynchronizer(new PropertyHintSynchronizer(world, "tagged", ShowHideTagsLogic.SHOW_TAG_KEY, null,
IThing.class, ShowHideTagsLogic.USER_MAY_SHOW_HIDE_TAG));
addHintSynchronizer(new PropertyHintSynchronizer(world, "angle", IHasAngle.ANGLE_KEY, null,
IHasMutableAngle.class, IHasMutableAngle.USER_MAY_CHANGE_ANGLE));
addHintSynchronizer(new PropertyHintSynchronizer(world, "endpoint1", IHasEndpoints.ENDPOINT_1_KEY, toPoint2D,
IHasMutableEndpoints.class, IHasMutableEndpoints.USER_MAY_MOVE_ENDPOINT_1));
addHintSynchronizer(new PropertyHintSynchronizer(world, "endpoint2", IHasEndpoints.ENDPOINT_2_KEY, toPoint2D,
IHasMutableEndpoints.class, IHasMutableEndpoints.USER_MAY_MOVE_ENDPOINT_2));
addHintSynchronizer(new PropertyHintSynchronizer(world, "midpoints", IHasMidpoints.MIDPOINTS_KEY, toPoint2Ds,
IHasMutableMidpoints.class, IHasMutableMidpoints.USER_MAY_MOVE_MIDPOINTS));
addHintSynchronizer(new PropertyHintSynchronizer(world, "color", IHasColor.COLOR_KEY, null,
IHasMutableColor.class, IHasMutableColor.USER_MAY_EDIT_COLOR));
addHintSynchronizer(new PropertyHintSynchronizer(world, "highlight", IHasGlow.GLOW_COLOR_KEY, null,
IHasMutableGlow.class, HighlightLogic.USER_MAY_HIGHLIGHT));
hintRepository.addHintRepositoryChangeListener(this);
for (IThing thing : model.getAllThings()) {
// createHintContext automatically restores and stores hints
createHintContext(thing);
}
}
final protected CopyOnWriteArrayList<IHintSynchronizer> hintSynchronizers = Lists.newCopyOnWriteArrayList();
final protected Multimap<IThingKey<?>, IHintSynchronizer> hintSynchronizersByKey = ArrayListMultimap.create();
final protected Multimap<String, IHintSynchronizer> hintSynchronizersByName = ArrayListMultimap.create();
public void addHintSynchronizer(IHintSynchronizer hintSynchronizer) {
hintSynchronizers.add(hintSynchronizer);
for (IThingKey<?> key : hintSynchronizer.getThingPropertiesOfInterest()) {
hintSynchronizersByKey.put(key, hintSynchronizer);
}
for (String name : hintSynchronizer.getRepositoryNamesOfInterest()) {
hintSynchronizersByName.put(name, hintSynchronizer);
}
}
@Override
public void dispose() {
BNAUtils.checkLock();
hintRepository.removeHintRepositoryChangeListener(this);
super.dispose();
}
@Override
public void bnaModelChanged(final BNAModelEvent evt) {
BNAUtils.checkLock();
switch (evt.getEventType()) {
case THING_ADDED: {
IThing thing = evt.getTargetThing();
// createHintContext automatically restores and stores hints
createHintContext(thing);
}
break;
case THING_CHANGED: {
final IThing thing = evt.getTargetThing();
final ThingEvent thingEvent = evt.getThingEvent();
if (!HINT_CONTEXT_KEY.equals(thingEvent.getPropertyName())) {
final Object context = createHintContext(thing);
if (context != null) {
storeHints(thing, context, thingEvent);
}
}
}
break;
default:
// do nothing
}
}
protected @Nullable
Object createHintContext(IThing thing) {
checkNotNull(thing);
Object context = thing.get(HINT_CONTEXT_KEY);
if (context == null) {
context = hintRepository.getContextForThing(world, thing);
if (context != null) {
thing.set(HINT_CONTEXT_KEY, context);
if (DEBUG) {
System.out.println("Restoring hints: " + context + " " + toString(thing));
}
restoreHints(thing, context, null);
if (DEBUG) {
System.out.println("Restored hints : " + context + " " + toString(thing));
}
thing.set(HINTS_RESTORED_KEY, true);
if (DEBUG) {
System.out.println("Storing hints : " + context + " " + toString(thing));
}
storeHints(thing, context, null);
for (IThing t : Assemblies.getParts(model, thing).values()) {
if (t != null) {
createHintContext(t);
}
}
}
}
return context;
}
public void restoreHints(IThing thing) {
BNAUtils.checkLock();
createHintContext(thing);
}
protected void restoreHints(IThing thing, Object context, @Nullable String name) {
BNAUtils.checkLock();
try {
if (name == null) {
for (Entry<String, HintValue> hint : hintRepository.getHints(context).entrySet()) {
_restoreHints(thing, context, hint.getKey(), hint.getValue());
}
}
else {
HintValue hintValue = hintRepository.getHint(context, name);
_restoreHints(thing, context, name, hintValue);
}
}
catch (PropertyDecodeException e) {
e.printStackTrace();
}
}
private void _restoreHints(IThing thing, Object context, String name, HintValue hintValue) {
for (IHintSynchronizer hintSynchronizer : hintSynchronizersByName.get(name)) {
if (DEBUG) {
System.out.println("Restoring hint : " + context + " " + toString(thing) + " " + hintSynchronizer);
}
hintSynchronizer.restoreHints(hintRepository, context, thing, name, hintValue);
}
}
protected void storeHints(IThing thing, Object context, @Nullable ThingEvent evt) {
BNAUtils.checkLock();
if (!thing.has(HINTS_RESTORED_KEY, true)) {
return;
}
if (evt == null) {
for (IHintSynchronizer hintSynchronizer : hintSynchronizers) {
if (DEBUG) {
System.out.println("Store hints : " + context + " " + toString(thing) + " " + hintSynchronizer);
}
hintSynchronizer.storeHints(hintRepository, context, thing, null);
}
}
else {
for (IHintSynchronizer hintSynchronizer : hintSynchronizersByKey.get(evt.getPropertyName())) {
if (DEBUG) {
System.out.println("Store hints : " + context + " " + toString(thing) + " " + hintSynchronizer
+ " " + evt);
}
hintSynchronizer.storeHints(hintRepository, context, thing, evt.getPropertyName());
}
}
}
@Override
public void hintRepositoryChanged(final IHintRepository repository, final Object context,
final @Nullable String name) {
try (Finally lock = BNAUtils.lock()) {
for (IThing t : valueLogic.getThings(HINT_CONTEXT_KEY, context)) {
restoreHints(t, context, name);
}
}
}
private String toString(IThing thing) {
return SystemUtils.simpleName(thing.getClass()) + "." + thing.getID();
}
}