package org.archstudio.xadl.bna.logics.mapping;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.archstudio.bna.BNAModelEvent;
import org.archstudio.bna.BNAModelEvent.EventType;
import org.archstudio.bna.IBNAModelListener;
import org.archstudio.bna.IBNAWorld;
import org.archstudio.bna.IThing;
import org.archstudio.bna.keys.IThingRefKey;
import org.archstudio.bna.logics.AbstractThingLogic;
import org.archstudio.bna.logics.events.ProxyLogic;
import org.archstudio.bna.logics.hints.SynchronizeHintsLogic;
import org.archstudio.bna.logics.mapping.IBNAMappingLogic;
import org.archstudio.bna.logics.tracking.ThingValueTrackingLogic;
import org.archstudio.bna.utils.Assemblies;
import org.archstudio.bna.utils.BNAPath;
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.IXArchADTModelListener;
import org.archstudio.xarchadt.ObjRef;
import org.archstudio.xarchadt.XArchADTModelEvent;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.collect.Lists;
/**
* 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. This class facilitates the synchronization by preventing update cycles.
*
* @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 AbstractXADLToBNAThingLogic<T extends IThing> extends AbstractThingLogic
implements IBNAMappingLogic<T>, IBNAModelListener, IXArchADTModelListener, IXArchRelativePathTrackerListener {
protected final ThingValueTrackingLogic valueLogic;
protected final IXArchADT xarch;
protected final String relativePath;
/**
* The {@link XArchRelativePathTracker} used to identify the set of ObjRefs that will be mapped to BNA Assemblies
*/
protected final XArchRelativePathTracker tracker;
public AbstractXADLToBNAThingLogic(IBNAWorld world, IXArchADT xarch, ObjRef rootObjRef, String relativePath) {
super(world);
valueLogic = logics.addThingLogic(ThingValueTrackingLogic.class);
this.xarch = xarch;
this.relativePath = relativePath.replaceAll("\\[[^\\]]*\\]", "");
this.tracker = new XArchRelativePathTracker(xarch, rootObjRef, relativePath, false);
tracker.addTrackerListener(this);
}
protected void setProgressInfo(String description) {
// TODO: implement progress bar
}
@Override
public void applyDefaults(T thing) {
}
@Override
public void init() {
BNAUtils.checkLock();
super.init();
logics.addThingLogic(ProxyLogic.class).getProxyForInterface(IXArchADTModelListener.class);
tracker.startScanning();
}
@Override
public void dispose() {
BNAUtils.checkLock();
tracker.stopScanning();
super.dispose();
}
@Override
public void handleXArchADTModelEvent(XArchADTModelEvent evt) {
// No BNAUtils.checkLock()
tracker.handleXArchADTModelEvent(evt);
}
/**
* Gets the things that have been added by this logic.
*/
@SuppressWarnings("unchecked")
public Collection<T> getAddedThings() {
return (Collection<T>) valueLogic.getThings(MAPPING_KEY, this);
}
/**
* Takes the newly added ObjRef, translates it into a BNA Assembly through a call to {@link #addThing(List, ObjRef)}
* and updates the BNA Assembly through a call to {@link #updateThing(List, ObjRef, XArchADTModelEvent, IThing)}
*/
@Override
public void processAdd(final List<ObjRef> descendantRefs, final ObjRef objRef) {
try (Finally lock = BNAUtils.lock(); Finally bulkChange = model.beginBulkChange();) {
// start by creating and updating the thing
T thing = addThing(descendantRefs, objRef);
if (thing != null) {
applyDefaults(thing);
updateThing(descendantRefs, objRef, null, thing);
// note which ObjRef the thing represents
thing.set(IHasObjRef.OBJREF_KEY, objRef);
// mark the thing as originating from, and being by this logic
thing.set(MAPPING_KEY, AbstractXADLToBNAThingLogic.this);
// restore hints now, so as to avoid extra processing later
SynchronizeHintsLogic hintsLogic = logics.getThingLogic(SynchronizeHintsLogic.class);
if (hintsLogic != null) {
Queue<IThing> restoreHintsThings = new LinkedList<>();
restoreHintsThings.add(thing);
while (!restoreHintsThings.isEmpty()) {
IThing restoreHintsThing = restoreHintsThings.poll();
restoreHintsThings.addAll(Assemblies.getParts(model, restoreHintsThing).values());
hintsLogic.restoreHints(restoreHintsThing);
}
}
}
}
}
/**
* Finds any BNA Assemblies that correspond to the modified ObjRef and updates them through calls to
* {@link #updateThing(List, ObjRef, XArchADTModelEvent, IThing)}
*/
@Override
@SuppressWarnings("unchecked")
public void processUpdate(final List<ObjRef> descendantRefs, final ObjRef objRef, final XArchADTModelEvent evt) {
try (Finally lock = BNAUtils.lock(); Finally bulkChange = model.beginBulkChange();) {
for (IThing t : valueLogic.getThings(IHasObjRef.OBJREF_KEY, objRef, MAPPING_KEY,
AbstractXADLToBNAThingLogic.this)) {
updateThing(descendantRefs, objRef, evt, (T) t);
}
}
}
/**
* Removes all BNA Assemblies that were created by this logic and correspond to the removed ObjRef
*/
@Override
public void processRemove(final List<ObjRef> descendantRefs, final ObjRef objRef) {
try (Finally lock = BNAUtils.lock(); Finally bulkChange = model.beginBulkChange();) {
for (IThing t : valueLogic.getThings(IHasObjRef.OBJREF_KEY, objRef, MAPPING_KEY,
AbstractXADLToBNAThingLogic.this)) {
Assemblies.removeRootAndParts(model, t);
}
}
}
/**
* Monitors BNA model events to see if they represent changes to a BNA Assembly added by this logic. When found,
* updates the ObjRef data through calls to {@link #storeThingData(ObjRef, IThing, BNAPath, BNAModelEvent)}.
*/
@Override
@SuppressWarnings("unchecked")
public void bnaModelChanged(final BNAModelEvent evt) {
BNAUtils.checkLock();
if (evt.getEventType() == EventType.THING_CHANGED) {
IThing thing = evt.getTargetThing();
List<IThingRefKey<?>> bnaPathSegments = Lists.newArrayList();
while (thing != null) {
// look for a BNA Assembly that we've mapped from an ObjRef
if (thing.has(MAPPING_KEY, AbstractXADLToBNAThingLogic.this)) {
ObjRef objRef = thing.get(IHasObjRef.OBJREF_KEY);
if (objRef != null) {
storeThingData(objRef, (T) thing, BNAPath.create(Lists.reverse(bnaPathSegments)), evt);
break;
}
}
// now check to see if this thing is part of an assembly
bnaPathSegments.add(Assemblies.getPartName(thing));
thing = Assemblies.getRootWithPart(model, thing);
}
}
}
/**
* Creates the BNA Assembly that represents the given ObjRef. Note that this class should merely create the assembly
* and not configure it with information from the ObjRef, that functionality is reserved for
* {@link #updateThing(List, ObjRef, XArchADTModelEvent, IThing)}
*
* @param descendantRefs
* The ObjRefs from the rootObjRef to the objRef
* @param objRef
* The objRef that is to be represented as a Thing in the BNA model
* @return The thing that was created to represent the ObjRef, or <code>null</code> if no thing should be added for
* the given objRef.
*/
protected abstract @Nullable T addThing(List<ObjRef> descendantRefs, ObjRef objRef);
/**
* Updates the Thing returned from {@link #addThing(List, ObjRef)} to reflect information stored in the ObjRef and
* its children. This will be called upon initial creation of the BNA Assembly, and when the ObjRef or its children
* are modified.
*
* @param descendantRefs
* The ObjRefs from the rootObjRef to the objRef
* @param objRef
* The objRef that is represented by the Thing in the BNA model
* @param evt
* The event that caused the update, <code>null</code> when called initially from
* {@link #addThing(List, ObjRef)}
* @param thing
* The BNA Thing that represents the ObjRef.
*/
protected void updateThing(List<ObjRef> descendantRefs, ObjRef objRef, @Nullable XArchADTModelEvent evt, T thing) {
}
/**
* Updates the ObjRef to reflect information in the BNA Assembly returned from {@link #addThing(List, ObjRef)}. This
* will be called when the BNA Assembly's root thing or any of its parts are modified.
*
* @param objRef
* The objRef corresponding to the BNA Assembly
* @param thing
* The BNA Assembly (or plain old Thing) that represents the ObjRef.
* @param relativeBNAPath
* The path within the BNA Assembly to the specific thing that was modified, empty if the BNA Assembly's
* root thing is modified
* @param evt
* The original {@link BNAModelEvent} that triggered the update. Note that this event may be for a part
* in the BNA Assembly and not for the BNA Assembly root thing.
*/
protected void storeThingData(ObjRef objRef, T thing, BNAPath relativeBNAPath, BNAModelEvent evt) {
}
}