package org.archstudio.xadl.bna.logics.mapping;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.archstudio.bna.BNAModelEvent;
import org.archstudio.bna.IBNAWorld;
import org.archstudio.bna.IThing;
import org.archstudio.bna.keys.IThingKey;
import org.archstudio.bna.utils.BNAPath;
import org.archstudio.bna.utils.BNAPathKey;
import org.archstudio.bna.utils.BNAUtils;
import org.archstudio.sysutils.Finally;
import org.archstudio.xadl.IXArchRelativePathTrackerListener;
import org.archstudio.xadl.XArchRelativePathTracker;
import org.archstudio.xadl.bna.facets.IHasObjRef;
import org.archstudio.xarchadt.IXArchADT;
import org.archstudio.xarchadt.ObjRef;
import org.archstudio.xarchadt.XArchADTModelEvent;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
/**
* Synchronizes a xADL ObjRef with a single BNA Assembly (or plain Thing). Changes to the xADL ObjRef and its children
* will be reflected in the BNA Assembly, and changes to things in the BNA Assembly will be reflected in the xADL
* ObjRef. As opposed to {@link AbstractXADLToBNAThingLogic} which synchronizes ObjRefs and BNA Assemblies at a very
* coarse level, this class breaks synchronization into little pieces--an attribute, an ancestor, etc.
*
* @param <T>
* The type of BNA Assembly/Thing that will be created by this class to represent targeted ObjRefs, see
* {@link #addThing(List, ObjRef)}
*/
public abstract class AbstractXADLToBNAPathLogic<T extends IThing> extends AbstractXADLToBNAThingLogic<T> {
/**
* Translates XADL attribute values (e.g., a Direction) to BNA values (e.g., a Flow) and vice versa.
*
* @param <X>
* the xADL type
* @param <B>
* the BNA type
*/
public static interface IXADLToBNATranslator<X extends Serializable, B> {
public B toBNAValue(X xadlValue);
public X toXadlValue(B value);
}
/**
* Updates the BNA Assembly/Thing from the xADL ObjRef, see
* {@link #updateBNA(ObjRef, String, XArchADTModelEvent, IThing)} for a description.
*/
public abstract class IBNAUpdater {
/**
* Updates the BNA Assembly/Thing from the xADL ObjRef
*
* @see AbstractXADLToBNAThingLogic#updateThing(List, ObjRef, XArchADTModelEvent, IThing)
*/
public void updateBNA(ObjRef objRef, String xadlPath, XArchADTModelEvent evt, T rootThing) {
}
}
public abstract class IXADLUpdater {
/**
* Updates the xADL ObjRef from the BNA Assembly/Thing
*
* @see AbstractXADLToBNAThingLogic#storeThingData(ObjRef, IThing, BNAPath, BNAModelEvent)
*/
public void updateXADL(T rootThing, BNAPath relativeBNAPath, BNAModelEvent evt, ObjRef objRef) {
}
}
public AbstractXADLToBNAPathLogic(IBNAWorld world, IXArchADT xarch, ObjRef rootObjRef, String objRefPath) {
super(world, xarch, rootObjRef, objRefPath);
}
private final Multimap<String, IBNAUpdater> xADLPathBNAUpdaters = ArrayListMultimap.create();
private final Map<XArchRelativePathTracker, IBNAUpdater> xADLXPathBNAUpdaters = Maps.newHashMap();
private final Multimap<BNAPathKey, IXADLUpdater> bnaPathXADLUpdaters = ArrayListMultimap.create();
protected void addBNAUpdater(String xADLPath, IBNAUpdater bnaUpdater) {
xADLPathBNAUpdaters.put(xADLPath, bnaUpdater);
}
protected void addXADLUpdater(BNAPath bnaPath, IThingKey<?> key, IXADLUpdater xadlUpdater) {
bnaPathXADLUpdaters.put(BNAPathKey.create(bnaPath, key), xadlUpdater);
}
protected void addXPathBNAUpdater(final String xADLXPath, final IBNAUpdater bnaUpdater) {
XArchRelativePathTracker subTracker = new XArchRelativePathTracker(xarch, tracker.getRootObjRef(),
tracker.getXPath() + "/" + xADLXPath, false);
subTracker.addTrackerListener(new IXArchRelativePathTrackerListener() {
@Override
public void processUpdate(List<ObjRef> descendantRefs, ObjRef modifiedRef, XArchADTModelEvent relativeEvt) {
fireUpdate(descendantRefs, relativeEvt);
}
@Override
public void processRemove(List<ObjRef> descendantRefs, ObjRef removedRef) {
fireUpdate(descendantRefs, null);
}
@Override
public void processAdd(List<ObjRef> descendantRefs, ObjRef addedRef) {
fireUpdate(descendantRefs, null);
}
@SuppressWarnings("unchecked")
private void fireUpdate(List<ObjRef> descendantRefs, XArchADTModelEvent relativeEvt) {
try (Finally lock = BNAUtils.lock()) {
ObjRef objRef = descendantRefs.get(tracker.getNumXPathSegments());
for (IThing t : valueLogic.getThings(IHasObjRef.OBJREF_KEY, objRef, MAPPING_KEY,
AbstractXADLToBNAPathLogic.this)) {
bnaUpdater.updateBNA(objRef, xADLXPath, relativeEvt, (T) t);
}
}
}
});
xADLXPathBNAUpdaters.put(subTracker, bnaUpdater);
}
@Override
public void init() {
super.init();
for (XArchRelativePathTracker subTracker : xADLXPathBNAUpdaters.keySet()) {
subTracker.startScanning();
}
}
@Override
public void handleXArchADTModelEvent(XArchADTModelEvent evt) {
super.handleXArchADTModelEvent(evt);
for (XArchRelativePathTracker subTracker : xADLXPathBNAUpdaters.keySet()) {
subTracker.handleXArchADTModelEvent(evt);
}
}
/**
* Propagate the xADL event to all of the {@link IBNAUpdater}s registered with this class, which each do small
* updates.
*
* @see AbstractXADLToBNAThingLogic#updateThing(List, ObjRef, XArchADTModelEvent, IThing)
*/
@Override
protected void updateThing(List<ObjRef> descendantRefs, ObjRef objRef, XArchADTModelEvent evt, T thing) {
if (evt == null) {
for (IBNAUpdater bnaUpdater : xADLPathBNAUpdaters.values()) {
bnaUpdater.updateBNA(objRef, null, evt, thing);
}
for (IBNAUpdater bnaUpdater : xADLXPathBNAUpdaters.values()) {
bnaUpdater.updateBNA(objRef, null, evt, thing);
}
}
else {
String path = evt.getSourcePath() + "/" + evt.getFeatureName() + "/";
if (path.charAt(0) == '/') {
path = path.substring(1);
}
while (path.length() > 0) {
int lastDelimiter = path.lastIndexOf('/');
if (lastDelimiter > 0) {
path = path.substring(0, lastDelimiter);
}
else {
path = "";
}
for (IBNAUpdater bnaUpdater : xADLPathBNAUpdaters.get(path)) {
bnaUpdater.updateBNA(objRef, path, evt, thing);
}
}
}
}
/**
* Propagate the BNA event to the relevant {@link IXADLUpdater}s, each of which knows how to respond to a specific
* Thing within the BNA Assembly.
*
* @see AbstractXADLToBNAThingLogic#storeThingData(ObjRef, IThing, BNAPath, BNAModelEvent)
*/
@Override
protected void storeThingData(ObjRef objRef, T rootThing, BNAPath relativeBNAPath, BNAModelEvent evt) {
for (IXADLUpdater xadlUpdater : bnaPathXADLUpdaters
.get(BNAPathKey.create(relativeBNAPath, evt.getThingEvent().getPropertyName()))) {
xadlUpdater.updateXADL(rootThing, relativeBNAPath, evt, objRef);
}
}
/**
* Adds the appropriate updaters to synchronize a single xADL attribute or ObjRef with a BNA Thing property,
* possibly translating the value in the process. Cycles are prevented as described in
* {@link AbstractXADLToBNAThingLogic}
*
* @param xADLAttributeName
* The name of the xADL attribute to synchronize with the BNA Thing's property
* @param translator
* An optional translator that converts between xADL attributes and BNA property values
* @param defaultBNAValue
* The default BNA property value to use when the xADL attribute is not defined
* @param targetThingPath
* The path from the root BNA Assembly to the specific thing part that is to store the translated
* property
* @param thingValueKey
* The property name into which to store the value
* @param reverse
* The BNA Thing's value is always updated when the xADL value is modified, if <code>true</code> then the
* reverse mapping is also performed--i.e., the xADL value is updated when the Thing property is
* modified.
*/
protected <X extends Serializable, B> void syncValue(final String xADLAttributeName,
@Nullable final IXADLToBNATranslator<X, B> translator, @Nullable final B defaultBNAValue,
final BNAPath targetThingPath, final IThingKey<B> thingValueKey, final boolean reverse) {
xADLPathBNAUpdaters.put(xADLAttributeName, new IBNAUpdater() {
@Override
@SuppressWarnings("unchecked")
public void updateBNA(ObjRef objRef, String xadlPath, XArchADTModelEvent evt, T rootThing) {
// this maps updates from the xADL attribute to the BNA Thing's property
IThing targetThing = BNAPath.resolve(model, rootThing, targetThingPath);
if (targetThing != null) {
B thingValue;
X xadlValue = (X) xarch.get(objRef, xADLAttributeName);
if (xadlValue == null) {
thingValue = defaultBNAValue;
}
else {
if (translator != null) {
thingValue = translator.toBNAValue(xadlValue);
}
else {
thingValue = (B) xadlValue;
}
}
if (thingValue != null) {
targetThing.set(thingValueKey, thingValue);
}
else {
targetThing.remove(thingValueKey);
}
}
}
});
if (reverse) {
bnaPathXADLUpdaters.put(BNAPathKey.create(targetThingPath, thingValueKey), new IXADLUpdater() {
@Override
@SuppressWarnings("unchecked")
public void updateXADL(T rootThing, BNAPath relativeBNAPath, BNAModelEvent evt, ObjRef objRef) {
// this updates xADL attributes from the BNA Thing's property value
Object value = evt.getThingEvent().getNewPropertyValue();
if (translator != null) {
value = translator.toXadlValue((B) value);
}
if (value == null) {
xarch.clear(objRef, xADLAttributeName);
}
else {
xarch.set(objRef, xADLAttributeName, (Serializable) value);
}
}
});
}
}
/**
* Specifies a specific value to set on all mapped things.
*
* @param targetThingPath
* The path from the root BNA Assembly to the specific thing part that is to store the translated
* property
* @param thingValueKey
* The key on which to set the value
* @param value
* The value to set
*/
protected <V> void setValue(final BNAPath targetThingPath, final IThingKey<V> thingValueKey, final V value) {
xADLPathBNAUpdaters.put("", new IBNAUpdater() {
@Override
public void updateBNA(ObjRef objRef, String xadlPath, XArchADTModelEvent evt, T rootThing) {
IThing targetThing = BNAPath.resolve(model, rootThing, targetThingPath);
if (targetThing != null) {
targetThing.set(thingValueKey, value);
}
}
});
}
/**
* Sets the objRefKey property to the specified ancestor of the mapped thing.
*
* @param targetThingPath
* The path from the root BNA Assembly to the specific thing part that is to store the translated
* property
* @param objRefKey
* The key on which to set the ObjRef
* @param index
* The ancestor index starting from the mapped objRef (>>=0) or from the root ObjRef (<0)
*/
protected void setAncestorObjRef(final BNAPath targetThingPath, final IThingKey<ObjRef> objRefKey,
final int index) {
xADLPathBNAUpdaters.put("", new IBNAUpdater() {
@Override
public void updateBNA(ObjRef objRef, String xadlPath, XArchADTModelEvent evt, T rootThing) {
IThing targetThing = BNAPath.resolve(model, rootThing, targetThingPath);
if (targetThing != null) {
List<ObjRef> ancestors = xarch.getAllAncestors(objRef);
int actualIndex = index;
if (actualIndex < 0) {
ancestors = Lists.reverse(ancestors);
actualIndex = -actualIndex - 1;
}
targetThing.set(objRefKey, ancestors.get(actualIndex));
}
}
});
}
}