package org.archstudio.xarchadt.variability.core; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.archstudio.sysutils.SystemUtils; import org.archstudio.sysutils.UIDGenerator; import org.archstudio.xadl3.hints_3_0.Hints_3_0Package; import org.archstudio.xadl3.variability_3_0.AttributeChange; import org.archstudio.xadl3.variability_3_0.Change; import org.archstudio.xadl3.variability_3_0.ChangeSet; import org.archstudio.xadl3.variability_3_0.ChangeSetOfChanges; import org.archstudio.xadl3.variability_3_0.ElementChange; import org.archstudio.xadl3.variability_3_0.ElementManyChange; import org.archstudio.xadl3.variability_3_0.Variability; import org.archstudio.xadl3.variability_3_0.Variability_3_0Factory; import org.archstudio.xadl3.variability_3_0.Variability_3_0Package; import org.archstudio.xadl3.xadlcore_3_0.DocumentRoot; import org.archstudio.xadl3.xadlcore_3_0.XADLType; import org.archstudio.xadl3.xadlcore_3_0.Xadlcore_3_0Factory; import org.archstudio.xadl3.xadlcore_3_0.Xadlcore_3_0Package; import org.archstudio.xarchadt.IXArchADT; import org.archstudio.xarchadt.ObjRef; import org.archstudio.xarchadt.XArchADTModelEvent; import org.archstudio.xarchadt.XArchADTModelEvent.EventType; import org.archstudio.xarchadt.XArchADTProxy; import org.archstudio.xarchadt.core.XArchADTImpl; import org.archstudio.xarchadt.variability.IXArchADTVariability; import org.archstudio.xarchadt.variability.IXArchADTVariabilityListener; import org.archstudio.xarchadt.variability.XArchADTVariabilityEvent; import org.archstudio.xarchadt.variability.XArchADTVariabilityEvent.Type; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.xml.type.internal.DataValue.Base64; import org.eclipse.jdt.annotation.Nullable; import org.osgi.framework.Bundle; import org.xml.sax.SAXException; import com.google.common.base.Joiner; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public class XArchADTVariabilityImpl extends XArchADTImpl implements IXArchADTVariability { private enum Status { EXPLICITLY_ADDED(ChangeStatus.EXPLICITLY_ADDED, true, true), // EXPLICITLY_ADDED_BUT_REALLY_REMOVED(ChangeStatus.EXPLICITLY_ADDED_BUT_REALLY_REMOVED, true, true), // EXPLICITLY_MODIFIED(ChangeStatus.EXPLICITLY_MODIFIED, true, true), // EXPLICITLY_MODIFIED_BUT_REALLY_REMOVED(ChangeStatus.EXPLICITLY_MODIFIED_BUT_REALLY_REMOVED, true, true), // EXPLICITLY_REMOVED(ChangeStatus.EXPLICITLY_REMOVED, true, true), // EXPLICITLY_REMOVED_BUT_REALLY_ADDED(ChangeStatus.EXPLICITLY_REMOVED_BUT_REALLY_ADDED, true, true), // OVERVIEW(ChangeStatus.OVERVIEW, true, false), // ATTACHED(ChangeStatus.ATTACHED, true, false), // DETACHED(ChangeStatus.ATTACHED, false, false); public final ChangeStatus status; public final boolean attached; public final boolean explicit; private Status(ChangeStatus status, boolean attached, boolean explicit) { this.status = status; this.attached = attached; this.explicit = explicit; } } private class VariabilityStatus { final ObjRef documentRootRef; Variability variability = null; boolean isChangeSetsEnabled = false; boolean isOverview = false; ChangeSet activeChangeSet = null; List<ChangeSet> allChangeSets = Lists.newArrayList(); List<ChangeSet> appliedChangeSets = Lists.newArrayList(); Set<ChangeSet> explicitChangeSets = Sets.newHashSet(); List<ChangeSet> workingChangeSets = Lists.newArrayList(); int workingChangeSetsBeginIndex = 0; public VariabilityStatus(ObjRef documentRootRef) { this.documentRootRef = documentRootRef; refreshFromXadl(); } public void refreshFromXadl() { variability = null; DocumentRoot documentRoot = (DocumentRoot) get(documentRootRef); XADLType xadlType = documentRoot.getXADL(); if (xadlType != null) { for (EObject eObject : xadlType.getTopLevelElement()) { if (eObject instanceof Variability) { variability = (Variability) eObject; break; } } } isChangeSetsEnabled = false; isOverview = false; activeChangeSet = null; allChangeSets.clear(); appliedChangeSets.clear(); workingChangeSets.clear(); workingChangeSetsBeginIndex = 0; if (variability != null) { isChangeSetsEnabled = true; isOverview = variability.isOverview(); activeChangeSet = variability.getActiveChangeSet(); allChangeSets = Lists.newArrayList(variability.getChangeSet()); appliedChangeSets = Lists.newArrayList(variability.getAppliedChangeSets()); // TODO: make these implied? isOverview |= !appliedChangeSets.containsAll(explicitChangeSets); if (!isOverview) { workingChangeSets.addAll(allChangeSets); Set<ChangeSet> filter = Sets.newHashSet(); filter.addAll(appliedChangeSets); workingChangeSets.retainAll(filter); } else { workingChangeSets.addAll(allChangeSets); Set<ChangeSet> filter = Sets.newHashSet(); filter.addAll(appliedChangeSets); workingChangeSets.removeAll(filter); workingChangeSetsBeginIndex = workingChangeSets.size(); workingChangeSets.addAll(appliedChangeSets); } } } // the following are used during a synchronization public List<ChangeSet> _changeSets; public int _activeChangeSetIndex; public int _beginChangeSetIndex; public int[] _synchronizeIndecies; public boolean[] _explicitChangeSet; public void setup(List<ChangeSet> changeSets, int beginChangeSetIndex, int activeChangeSetIndex, int[] synchronizeIndecies) { checkArgument(0 <= beginChangeSetIndex && beginChangeSetIndex <= changeSets.size()); checkArgument(activeChangeSetIndex == -1 || 0 <= activeChangeSetIndex && activeChangeSetIndex < changeSets.size()); for (int i : synchronizeIndecies) { checkArgument(0 <= i && i < changeSets.size()); } this._changeSets = changeSets; this._activeChangeSetIndex = activeChangeSetIndex; this._beginChangeSetIndex = beginChangeSetIndex; this._synchronizeIndecies = synchronizeIndecies; this._explicitChangeSet = new boolean[changeSets.size()]; for (int csIndex = isOverview ? 0 : beginChangeSetIndex; csIndex < changeSets.size(); csIndex++) { _explicitChangeSet[csIndex] = explicitChangeSets.contains(changeSets.get(csIndex)); } } } private class SynchAttributeHelper { @Nullable public AttributeChange createChange(VariabilityStatus vs, EObject eObject, EStructuralFeature eAttribute) { return createAttributeChange(vs.activeChangeSet, eObject, eAttribute.getName()); } } private class SynchElementHelper { boolean isImplicitlyResolvable; public SynchElementHelper(boolean isImplicitlyResolvable) { this.isImplicitlyResolvable = isImplicitlyResolvable; } public void clear(EObject oldEObject) { EStructuralFeature eFeature = oldEObject.eContainmentFeature(); EObject eContainer = oldEObject.eContainer(); if (eFeature != null && eContainer != null) { if (eFeature.isMany()) { ((EList<?>) eContainer.eGet(eFeature)).remove(oldEObject); } else { eContainer.eSet(eFeature, null); } } } @SuppressWarnings("unchecked") public void set(EObject newEObject) { EStructuralFeature eFeature = newEObject.eContainmentFeature(); EObject eContainer = newEObject.eContainer(); if (eFeature != null && eContainer != null) { if (eFeature.isMany()) { ((EList<EObject>) eContainer.eGet(eFeature)).add(newEObject); } else { eContainer.eSet(eFeature, newEObject); } } } @Nullable public ElementChange createChange(VariabilityStatus vs, EObject newEObject) { return createElementChange(vs.activeChangeSet, newEObject); } public boolean isImplicitlyResolvable() { return isImplicitlyResolvable; } } private final LoadingCache<ObjRef, VariabilityStatus> variabilityStatusCache = CacheBuilder.newBuilder().build( new CacheLoader<ObjRef, VariabilityStatus>() { @Override public VariabilityStatus load(@Nullable ObjRef documentRootRef) throws Exception { if (documentRootRef == null) { throw new NullPointerException(); } return new VariabilityStatus(documentRootRef); } }); private final List<IXArchADTVariabilityListener> variabilityListeners = Lists.newCopyOnWriteArrayList(); public void addXArchADTVariabilityListener(IXArchADTVariabilityListener listener) { wLock.lock(); try { variabilityListeners.add(listener); } finally { wLock.unlock(); } } public void removeXArchADTVariabilityListener(IXArchADTVariabilityListener listener) { wLock.lock(); try { variabilityListeners.remove(listener); } finally { wLock.unlock(); } } protected void fireVariabilityEvent(XArchADTVariabilityEvent.Type type, VariabilityStatus vs, ObjRef changedObjRef, ChangeStatus changeStatus) { XArchADTVariabilityEvent event = new XArchADTVariabilityEvent(type, vs.documentRootRef, putNullable(vs.activeChangeSet), putAll(vs.appliedChangeSets), putAll(vs.explicitChangeSets), vs.isOverview, changedObjRef, changeStatus); for (IXArchADTVariabilityListener l : variabilityListeners) { l.handleXArchADTVariabilityEvent(event); } } private final Map<EObject, Status> eObjectStatus = Maps.newHashMap(); protected void setStatus(VariabilityStatus vs, EObject eObj, Status status) { Status oldStatus = getStatus(eObj); if (status == Status.ATTACHED) { eObjectStatus.remove(eObj); } else { eObjectStatus.put(eObj, status); } if (oldStatus != status) { if (oldStatus.attached != status.attached) { fireXArchADTModelEvent(eObj, status != Status.DETACHED); } fireVariabilityEvent(Type.STATUS, vs, put(eObj), status.status); } } protected Status getStatus(EObject eObject) { Status status = eObjectStatus.get(eObject); return status != null ? status : Status.ATTACHED; } private boolean isAttached(EObject eObject) { return getStatus(eObject).attached; } @Override public ChangeStatus getChangeStatus(ObjRef objRef) { VariabilityStatus vs = getVariabilityStatusCache(objRef); if (vs == null || !vs.isChangeSetsEnabled) { return ChangeStatus.NOT_ENABLED; } return getStatus(get(objRef)).status; } private void fireXArchADTModelEvent(EObject eObj, boolean attached) { EStructuralFeature eFeature = eObj.eContainingFeature(); if (eFeature == null) { return; } EObject src = eObj.eContainer(); EventType eventType; ObjRef oldChange = null; String oldChangePath = null; ObjRef newChange = null; String newChangePath = null; if (attached) { eventType = eFeature.isMany() ? EventType.ADD : EventType.SET; newChange = put(eObj); newChangePath = getTagsOnlyPathString(newChange); } else { eventType = eFeature.isMany() ? EventType.REMOVE : EventType.CLEAR; oldChange = put(eObj); oldChangePath = Joiner.on('/').join(getTagsOnlyPathString(put(src)), super.getTagName(oldChange)); } fireXArchADTModelEvent(new XArchADTModelEvent(eventType, put(src), getAllAncestors(put(src)), getTagsOnlyPathString(put(src)), eFeature.getName(), oldChange, oldChangePath, newChange, newChangePath)); } protected boolean isAttachedObjRef(@Nullable Serializable element) { if (element instanceof ObjRef) { return isAttached(get((ObjRef) element)); } return true; } @Nullable protected Serializable filterDetached(@Nullable Serializable element) { return isAttachedObjRef(element) ? element : null; } protected List<Serializable> filterDetached(List<Serializable> elements) { for (Iterator<Serializable> i = elements.iterator(); i.hasNext();) { if (!isAttachedObjRef(i.next())) { i.remove(); } } return elements; } @Nullable protected ObjRef filterDetachedByAncestors(@Nullable ObjRef objRef) { if (objRef == null) { return null; } for (ObjRef aRef : getAllAncestors(objRef)) { if (!isAttachedObjRef(aRef)) { return null; } } return objRef; } @Override public void setChangeSetsEnabled(ObjRef documentRootRef, boolean enabled) { wLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled && enabled) { DocumentRoot documentRoot = (DocumentRoot) get(documentRootRef); XADLType xadlType = documentRoot.getXADL(); if (xadlType == null) { xadlType = Xadlcore_3_0Factory.eINSTANCE.createXADLType(); documentRoot.setXADL(xadlType); } Variability variability = null; for (EObject eObject : xadlType.getTopLevelElement()) { if (eObject instanceof Variability) { variability = (Variability) eObject; break; } } if (variability == null) { variability = Variability_3_0Factory.eINSTANCE.createVariability(); xadlType.getTopLevelElement().add(variability); } ChangeSetOfChanges changeSet = Variability_3_0Factory.eINSTANCE.createChangeSetOfChanges(); changeSet.setId(UIDGenerator.generateUID()); changeSet.setName("Baseline"); variability.getChangeSet().add(changeSet); variability.getAppliedChangeSets().add(changeSet); variability.setActiveChangeSet(changeSet); vs.refreshFromXadl(); fireVariabilityEvent(Type.ENABLED, vs, null, null); vs.setup(vs.workingChangeSets, 0, vs.workingChangeSets.indexOf(vs.activeChangeSet), new int[0]); synchronizeDocumentRoot(vs, documentRoot); } else if (vs.isChangeSetsEnabled && !enabled) { DocumentRoot documentRoot = (DocumentRoot) get(documentRootRef); XADLType xadlType = documentRoot.getXADL(); if (xadlType == null) { xadlType = Xadlcore_3_0Factory.eINSTANCE.createXADLType(); documentRoot.setXADL(xadlType); } for (EObject eObject : xadlType.getTopLevelElement()) { if (eObject instanceof Variability) { xadlType.getTopLevelElement().remove(eObject); } } vs.refreshFromXadl(); fireVariabilityEvent(Type.DISABLED, vs, null, null); } } finally { wLock.unlock(); } } @Override public boolean isChangeSetsEnabled(ObjRef documentRootRef) { rLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); return vs.isChangeSetsEnabled; } finally { rLock.unlock(); } } @Override public void setActiveChangeSet(ObjRef documentRootRef, @Nullable ObjRef changeSetRef) { wLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); checkArgument(changeSetRef == null || isInstanceOf(changeSetRef, Variability_3_0Package.eNS_URI, Variability_3_0Package.Literals.CHANGE_SET.getName())); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } if (!SystemUtils .nullEquals(vs.activeChangeSet, changeSetRef != null ? (ChangeSet) get(changeSetRef) : null)) { vs.variability.setActiveChangeSet(changeSetRef != null ? (ChangeSet) get(changeSetRef) : null); vs.refreshFromXadl(); fireVariabilityEvent(Type.ACTIVE, vs, null, null); } } finally { wLock.unlock(); } } @Override @Nullable public ObjRef getActiveChangeSet(ObjRef documentRootRef) { rLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } return putNullable(vs.activeChangeSet); } finally { rLock.unlock(); } } // if this needs to be optimized later, see: http://www.cs.dartmouth.edu/~doug/diff.ps static final <T> Set<T> diff(T[] o1, T[] o2) { int[][] c = new int[o1.length + 1][o2.length + 1]; for (int i = 1; i <= o1.length; i++) { for (int j = 1; j <= o2.length; j++) { if (SystemUtils.nullEquals(o1[i - 1], o2[j - 1])) { c[i][j] = c[i - 1][j - 1] + 1; } else { c[i][j] = Math.max(c[i][j - 1], c[i - 1][j]); } } } int i = o1.length; int j = o2.length; Set<T> diff = new HashSet<T>(); while (i >= 0 && j >= 0) { if (i > 0 && j > 0 && SystemUtils.nullEquals(o1[i - 1], o2[j - 1])) { i--; j--; continue; } if (j > 0 && (i == 0 || c[i][j - 1] >= c[i - 1][j])) { j--; diff.add(o2[j]); continue; } if (i > 0 && (j == 0 || c[i][j - 1] < c[i - 1][j])) { i--; diff.add(o1[i]); continue; } break; } return diff; } @Override public void applyChangeSets(ObjRef documentRootRef, List<ObjRef> changeSetRefs) { wLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); for (ObjRef changeSetRef : changeSetRefs) { checkArgument(isInstanceOf(changeSetRef, Variability_3_0Package.eNS_URI, Variability_3_0Package.Literals.CHANGE_SET.getName())); } VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } if (!putAll(vs.appliedChangeSets).equals(changeSetRefs)) { List<ChangeSet> changeSets = Lists.newArrayList(); for (ObjRef changeSetRef : changeSetRefs) { changeSets.add((ChangeSet) get(changeSetRef)); } // only update based on change sets that were (un)applied recently Set<ChangeSet> changeSetDiff = diff(changeSets.toArray(new ChangeSet[0]), vs.appliedChangeSets.toArray(new ChangeSet[0])); vs.variability.getAppliedChangeSets().clear(); vs.variability.getAppliedChangeSets().addAll(changeSets); vs.refreshFromXadl(); fireVariabilityEvent(Type.APPLIED, vs, null, null); Set<Integer> ints = Sets.newHashSet(); for (ChangeSet changeSet : changeSetDiff) { ints.add(vs.workingChangeSets.indexOf(changeSet)); } //TODO: use ints vs.setup(vs.workingChangeSets, vs.workingChangeSetsBeginIndex, -1, new int[0]); // Ints.toArray(ints)); synchronizeDocumentRoot(vs, get(documentRootRef)); } } finally { wLock.unlock(); } } @Override public List<ObjRef> getAppliedChangeSets(ObjRef documentRootRef) { rLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } List<ObjRef> changeSetRefs = Lists.newArrayListWithCapacity(vs.appliedChangeSets.size()); for (ChangeSet changeSet : vs.appliedChangeSets) { changeSetRefs.add(put(changeSet)); } return changeSetRefs; } finally { rLock.unlock(); } } @Override public void setExplicitChangeSets(ObjRef documentRootRef, Iterable<ObjRef> changeSetRefs) { wLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); for (ObjRef changeSetRef : changeSetRefs) { checkArgument(isInstanceOf(changeSetRef, Variability_3_0Package.eNS_URI, Variability_3_0Package.Literals.CHANGE_SET.getName())); } VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } if (!Sets.newHashSet(putAll(vs.explicitChangeSets)).equals(Sets.newHashSet(changeSetRefs))) { vs.explicitChangeSets.clear(); for (ObjRef changeSetRef : changeSetRefs) { vs.explicitChangeSets.add((ChangeSet) get(changeSetRef)); } vs.refreshFromXadl(); fireVariabilityEvent(Type.EXPLICIT, vs, null, null); // TODO: set ints[0] to include the explicit change set diff change sets vs.setup(vs.workingChangeSets, vs.workingChangeSetsBeginIndex, -1, new int[0]); synchronizeDocumentRoot(vs, get(documentRootRef)); } } finally { wLock.unlock(); } } @Override public Set<ObjRef> getExplicitChangeSets(ObjRef documentRootRef) { rLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } return Sets.newHashSet(putAll(vs.explicitChangeSets)); } finally { rLock.unlock(); } } @Override public boolean isOverviewModeEnabled(ObjRef documentRootRef) { rLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } return vs.isOverview; } finally { rLock.unlock(); } } @Override public void setOverviewModeEnabled(ObjRef documentRootRef, boolean enabled) { wLock.lock(); try { checkArgument(documentRootRef.equals(getDocumentRootRef(documentRootRef))); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (!vs.isChangeSetsEnabled) { throw new RuntimeException("Change sets are not enabled"); } vs.variability.setOverview(enabled); vs.refreshFromXadl(); fireVariabilityEvent(Type.OVERVIEW, vs, null, null); if (enabled) { // TODO make ints the new change sets vs.setup(vs.workingChangeSets, vs.workingChangeSetsBeginIndex, -1, new int[0]); synchronizeDocumentRoot(vs, get(documentRootRef)); } else { // TODO make ints the old change sets List<ChangeSet> changeSets = Lists.newArrayList(vs.allChangeSets); Set<ChangeSet> appliedChangeSets = Sets.newHashSet(vs.appliedChangeSets); changeSets.removeAll(appliedChangeSets); int beginChangeSetIndex = changeSets.size(); changeSets.addAll(vs.appliedChangeSets); vs.setup(changeSets, beginChangeSetIndex, -1, new int[0]); synchronizeDocumentRoot(vs, get(documentRootRef)); } } finally { wLock.unlock(); } } @Nullable private VariabilityStatus getVariabilityStatusCache(ObjRef baseObjRef) { ObjRef documentRootRef = super.getDocumentRootRef(baseObjRef); if (documentRootRef != null) { return variabilityStatusCache.getUnchecked(documentRootRef); } return null; } IXArchADT elementXArch = new XArchADTVariabilityElementImpl(this); @Nullable private String getKey(EObject eObject) { // don't store change sets for change sets themselves if (Variability_3_0Package.Literals.VARIABILITY.isInstance(eObject)) { return null; } // use id if it exists EAttribute idAttribute = eObject.eClass().getEIDAttribute(); if (idAttribute != null) { return (String) eObject.eGet(idAttribute); } // use extension type if (Xadlcore_3_0Package.Literals.EXTENSION.isInstance(eObject)) { return typeToString(eObject.eClass()); } // use hint name if (Hints_3_0Package.Literals.HINT.isInstance(eObject)) { return (String) eObject.eGet(Hints_3_0Package.Literals.HINT__NAME); } return null; } private class TypeMerger { EClass type = null; boolean mergedNull = false; boolean isResolvable = false; public TypeMerger(boolean isResolvable) { this.isResolvable = isResolvable; } public void mergeHigherPriorityType(@Nullable EClass newType, boolean isResolvable) { if (newType == null) { mergedNull = true; } else if (type == null || !newType.isSuperTypeOf(type)) { type = newType; } this.isResolvable |= isResolvable; } } private class AttributeMerger { Serializable value = null; boolean mergedChange = false; void mergeChange(@Nullable Serializable higherPrecedenceAttribute) { value = higherPrecedenceAttribute; mergedChange = true; } } @Nullable private EObject promote(@Nullable EObject eObj, @Nullable EClass type) { if (type == null) { return null; } if (eObj != null) { EClass eClass = eObj.eClass(); if (eClass.equals(type)) { return eObj; } EObject newEObj = get(create(type.getEPackage().getNsURI(), type.getName())); // TODO copy over values return newEObj; } return get(create(type.getEPackage().getNsURI(), type.getName())); } @Nullable static String serialize(@Nullable Serializable value) { try { if (value == null) { return null; } if (value instanceof String) { return "String:" + value; } if (value instanceof Byte) { return "byte:" + value; } if (value instanceof Short) { return "short:" + value; } if (value instanceof Integer) { return "int:" + value; } if (value instanceof Long) { return "long:" + value; } if (value instanceof Float) { return "float:" + value; } if (value instanceof Double) { return "double:" + value; } if (value instanceof Enum<?>) { return "enum:" + value.getClass().getName() + ":" + ((Enum<?>) value).name(); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(value); oos.flush(); oos.close(); return "object:" + value.getClass().getName() + ":" + Base64.encode(baos.toByteArray()); } catch (Exception e) { throw new RuntimeException(e); } } @Nullable @SuppressWarnings({ "rawtypes", "unchecked" }) static Serializable deserialize(@Nullable String value) { if (value == null) { return null; } try { String[] values = value.split(":", 2); if (value.equals("null")) { return null; } if (values[0].equals("String")) { return values[1]; } if (values[0].equals("byte")) { return Byte.valueOf(values[1]); } if (values[0].equals("short")) { return Short.valueOf(values[1]); } if (values[0].equals("int")) { return Integer.valueOf(values[1]); } if (values[0].equals("long")) { return Long.valueOf(values[1]); } if (values[0].equals("float")) { return Float.valueOf(values[1]); } if (values[0].equals("double")) { return Double.valueOf(values[1]); } values = value.split(":", 3); if (values[0].equals("enum")) { return Enum.valueOf((Class<? extends Enum>) classForNameCache.getUnchecked(values[1]), values[2]); } ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(values[2])); ObjectInputStream ois = new ObjectInputStream(bais); return (Serializable) ois.readObject(); } catch (Exception e) { throw new RuntimeException(e); } } private static final LoadingCache<String, Class<?>> classForNameCache = CacheBuilder.newBuilder().build( new CacheLoader<String, Class<?>>() { @Override public Class<?> load(@Nullable String className) throws Exception { if (className == null) { throw new NullPointerException(); } try { return Class.forName(className); } catch (Throwable t) { } for (Bundle bundle : Activator.getSingleton().getContext().getBundles()) { try { return bundle.loadClass(className); } catch (Throwable t) { } } throw new ClassNotFoundException(className); } }); ////////////////////////////////////////////////////////////////////////// @Nullable private AttributeChange createAttributeChange(@Nullable ChangeSet changeSet, EObject eObject, String name) { if (changeSet == null) { return null; } checkNotNull(eObject); checkNotNull(name); assert super.isAttached(put(eObject)); if (getChangePath(eObject).contains(null)) { return null; } return createAttributeChange(createElementChange(changeSet, eObject), name); } @Nullable private ElementChange createElementChange(@Nullable ChangeSet changeSet, EObject eObject) { if (changeSet == null) { return null; } checkNotNull(eObject); assert super.isAttached(put(eObject)); if (getChangePath(eObject).contains(null)) { return null; } ElementChange elementChange; if (eObject.eContainer() == null) { // DocumentRoot elementChange = ((ChangeSetOfChanges) changeSet).getElementChange(); if (elementChange == null) { elementChange = Variability_3_0Factory.eINSTANCE.createElementChange(); elementChange.setType(eObject.eClass().getEPackage().getNsURI() + "#" + eObject.eClass().getName()); ((ChangeSetOfChanges) changeSet).setElementChange(elementChange); } return elementChange; } elementChange = createElementChange(changeSet, eObject.eContainer()); EStructuralFeature eFeature = eObject.eContainingFeature(); if (!eFeature.isMany()) { return createElementChange(elementChange, eObject.eContainingFeature().getName(), eObject); } return createElementChange(elementChange, eObject.eContainingFeature().getName(), getKey(eObject), eObject); } ////////////////////////////////////////////////////////////////////////// utility methods @Nullable private Change lookup(ElementChange parent, String childName) { for (Change child : parent.getChange()) { if (childName.equals(child.getName())) { return child; } } return null; } @Nullable private Change lookup(ElementManyChange parent, String childName) { for (Change child : parent.getChange()) { if (childName.equals(child.getName())) { return child; } } return null; } private String typeToString(EClass eClass) { return eClass.getEPackage().getNsURI() + "#" + eClass.getName(); } @Nullable private EClass typeFromString(@Nullable String type) { if (type == null) { return null; } String[] typeParts = type.split("#"); return (EClass) EPackage.Registry.INSTANCE.getEPackage(typeParts[0]).getEClassifier(typeParts[1]); } private void run(List<Runnable> runnables) { for (Runnable r : runnables) { r.run(); } } private boolean doSync(List<? extends Change> values, int[] synchronizeIndecies) { if (synchronizeIndecies.length == 0) { return true; } for (int i : synchronizeIndecies) { if (values.get(i) != null) { return true; } } return false; } private List<String> getChangePath(EObject eObject) { checkNotNull(eObject); assert super.isAttached(put(eObject)); if (eObject.eContainer() == null || eObject.eContainer().eContainingFeature() == null) { return Lists.newArrayListWithCapacity(20); } List<String> changePath = getChangePath(eObject.eContainer()); EStructuralFeature eFeature = eObject.eContainingFeature(); if (eFeature instanceof EAttribute) { changePath.add(eFeature.getName()); } else if (!eFeature.isMany()) { changePath.add(eFeature.getName()); } else { changePath.add(eFeature.getName()); changePath.add(getKey(eObject)); } return changePath; } //@SuppressWarnings("unchecked") //private ElementChange resolveElementChange(ChangeSet changeSet, List<String> changePath) { // checkNotNull(changeSet); // checkNotNull(changePath); // checkArgument(!changePath.contains(null)); // // Object value = ((ChangeSetOfChanges) changeSet).getElementChange(); // for (String change : changePath) { // if (value == null) // return null; // else if (value instanceof EMap) { // value = ((EMap<String, Change>) value).get(change); // } // else if (value instanceof ElementChange) { // value = ((ElementChange) value).getChange().get(change); // } // else if (value instanceof ElementManyChange) { // value = ((ElementManyChange) value).getChange().get(change); // } // else // checkArgument(false, "Cannot resolve change path %s in change set %s", changePath, changeSet); // } // return (ElementChange) value; //} @Nullable private Serializable getSerializable(EObject eObject, EStructuralFeature eFeature) { if (eFeature instanceof EAttribute) { return (Serializable) (eObject.eIsSet(eFeature) ? eObject.eGet(eFeature) : null); } if (eFeature instanceof EReference && !((EReference) eFeature).isContainment()) { EObject eRef = (EObject) eObject.eGet(eFeature); return eRef != null ? (Serializable) eRef.eGet(eRef.eClass().getEIDAttribute()) : null; } throw new IllegalArgumentException(eFeature.toString()); } private void setSerializable(final EObject eObject, final EStructuralFeature eFeature, @Nullable final Serializable newChange, List<Runnable> endRunnables) { if (eFeature instanceof EAttribute) { eObject.eSet(eFeature, newChange); return; } if (eFeature instanceof EReference && !((EReference) eFeature).isContainment()) { endRunnables.add(new Runnable() { @Override public void run() { ObjRef objRef = newChange != null ? XArchADTVariabilityImpl.super.getByID((String) newChange) : null; EObject eRef = objRef != null ? get(objRef) : null; eObject.eSet(eFeature, eRef); } }); return; } throw new IllegalArgumentException(eFeature.toString()); } ////////////////////////////////////////////////////////////////////////// create methods @Nullable private AttributeChange createAttributeChange(@Nullable ElementChange elementChange, String name) { if (elementChange == null) { return null; } checkNotNull(elementChange); checkNotNull(name); Change attributeChange = lookup(elementChange, name); if (!(attributeChange instanceof AttributeChange)) { attributeChange = Variability_3_0Factory.eINSTANCE.createAttributeChange(); attributeChange.setName(name); elementChange.getChange().add(attributeChange); } return (AttributeChange) attributeChange; } @Nullable private ElementChange createElementChange(@Nullable ElementChange elementChange, String name, EObject eObject) { if (elementChange == null) { return null; } checkNotNull(elementChange); checkNotNull(name); checkNotNull(eObject); Change childElementChange = lookup(elementChange, name); if (!(childElementChange instanceof ElementChange)) { childElementChange = Variability_3_0Factory.eINSTANCE.createElementChange(); childElementChange.setName(name); ((ElementChange) childElementChange).setType(typeToString(eObject.eClass())); elementChange.getChange().add(childElementChange); } return (ElementChange) childElementChange; } @Nullable private ElementChange createElementChange(@Nullable ElementChange elementChange, String name, @Nullable String key, EObject eObject) { if (elementChange == null) { return null; } checkNotNull(name); if (key == null) { return null; } Change elementManyChange = lookup(elementChange, name); if (!(elementManyChange instanceof ElementManyChange)) { elementManyChange = Variability_3_0Factory.eINSTANCE.createElementManyChange(); elementManyChange.setName(name); elementChange.getChange().add(elementManyChange); } Change childElementChange = lookup((ElementManyChange) elementManyChange, key); if (!(childElementChange instanceof ElementChange)) { childElementChange = Variability_3_0Factory.eINSTANCE.createElementChange(); childElementChange.setName(key); ((ElementChange) childElementChange).setType(typeToString(eObject.eClass())); ((ElementManyChange) elementManyChange).getChange().add((ElementChange) childElementChange); } return (ElementChange) childElementChange; } ////////////////////////////////////////////////////////////////////////// resolve methods private List<AttributeChange> resolveAttributeChanges(List<ChangeSet> changeSets, EObject eObject, String name) { checkNotNull(changeSets); checkNotNull(eObject); checkNotNull(name); assert super.isAttached(put(eObject)); return resolveAttributeChanges(resolveElementChanges(changeSets, eObject), name); } private List<ElementChange> resolveElementChanges(List<ChangeSet> changeSets, EObject eObject) { checkNotNull(changeSets); checkNotNull(eObject); assert super.isAttached(put(eObject)); List<ElementChange> elementChanges; if (eObject.eContainer() == null) { // DocumentRoot elementChanges = Lists.newArrayListWithCapacity(changeSets.size()); for (ChangeSet changeSet : changeSets) { elementChanges.add(((ChangeSetOfChanges) changeSet).getElementChange()); } return elementChanges; } elementChanges = resolveElementChanges(changeSets, eObject.eContainer()); EStructuralFeature eFeature = eObject.eContainmentFeature(); if (!eFeature.isMany()) { return resolveElementChanges(elementChanges, eFeature.getName()); } return resolveElementChanges(elementChanges, eFeature.getName(), getKey(eObject)); } private List<AttributeChange> resolveAttributeChanges(List<ElementChange> elementChanges, String name) { checkNotNull(elementChanges); checkNotNull(name); List<AttributeChange> attributeChanges = Lists.newArrayListWithCapacity(elementChanges.size()); for (ElementChange elementChange : elementChanges) { if (elementChange != null) { Change attributeChange = lookup(elementChange, name); if (attributeChange instanceof AttributeChange) { attributeChanges.add((AttributeChange) attributeChange); continue; } } attributeChanges.add(null); } return attributeChanges; } private List<ElementChange> resolveElementChanges(List<ElementChange> elementChanges, String name) { checkNotNull(elementChanges); checkNotNull(name); List<ElementChange> childElementChanges = Lists.newArrayListWithCapacity(elementChanges.size()); for (ElementChange elementChange : elementChanges) { if (elementChange != null) { Change childElementChange = lookup(elementChange, name); if (childElementChange instanceof ElementChange) { childElementChanges.add((ElementChange) childElementChange); continue; } } childElementChanges.add(null); } return childElementChanges; } private List<ElementChange> resolveElementChanges(List<ElementChange> elementChanges, String name, @Nullable String key) { checkNotNull(elementChanges); checkNotNull(name); List<ElementChange> childElementChanges = Lists.newArrayListWithCapacity(elementChanges.size()); if (key == null) { for (int i = 0; i < elementChanges.size(); i++) { childElementChanges.add(null); } return childElementChanges; } for (ElementChange elementChange : elementChanges) { if (elementChange != null) { Change elementManyChange = lookup(elementChange, name); if (elementManyChange instanceof ElementManyChange) { Change childElementChange = lookup((ElementManyChange) elementManyChange, key); if (childElementChange instanceof ElementChange) { childElementChanges.add((ElementChange) childElementChange); continue; } } } childElementChanges.add(null); } return childElementChanges; } ////////////////////////////////////////////////////////////////////////// synchronize methods protected void synchronizeAttribute(VariabilityStatus vs, EObject eObject, EStructuralFeature eFeature, List<AttributeChange> resolveAttributeChanges, SynchAttributeHelper synchAttributeHelper) { List<Runnable> endRunnables = Lists.newArrayList(); vs.setup(vs.workingChangeSets, vs.workingChangeSetsBeginIndex, vs.workingChangeSets.indexOf(vs.activeChangeSet), new int[0]); synchronizeAttribute(vs, vs._synchronizeIndecies, eObject, eFeature, resolveAttributeChanges, synchAttributeHelper, endRunnables); run(endRunnables); } protected void synchronizeElement(VariabilityStatus vs, EObject eObject, List<ElementChange> elementChanges, SynchElementHelper synchElementHelper) { List<Runnable> endRunnables = Lists.newArrayList(); vs.setup(vs.workingChangeSets, vs.workingChangeSetsBeginIndex, vs.workingChangeSets.indexOf(vs.activeChangeSet), new int[0]); synchronizeElement(vs, vs._synchronizeIndecies, eObject, elementChanges, synchElementHelper, endRunnables); run(endRunnables); } protected void synchronizeElement(VariabilityStatus vs, EObject eObject, List<String> preChangePath, @Nullable ElementChange preElementChange, List<String> postChangePath) { List<Runnable> endRunnables = Lists.newArrayList(); vs.setup(vs.workingChangeSets, vs.workingChangeSetsBeginIndex, vs.workingChangeSets.indexOf(vs.activeChangeSet), new int[0]); synchronizeElement(vs, vs._synchronizeIndecies, eObject, preChangePath, preElementChange, postChangePath, endRunnables); run(endRunnables); } private void synchronizeAttribute(VariabilityStatus vs, int[] synchronizeIndecies, EObject eObject, EStructuralFeature eAttribute, List<AttributeChange> values, SynchAttributeHelper synchHelper, List<Runnable> endRunnables) { if (!doSync(values, synchronizeIndecies)) { return; } checkArgument(vs._changeSets.size() == values.size()); /* * Determine the attribute value according to (1) the change sets as if the active change set had the current * attribute value (aMergedType), and (2) the changes sets as they are (oMergedType) */ AttributeMerger aMergedChange = new AttributeMerger(); AttributeMerger oMergedChange = new AttributeMerger(); boolean explicitlyModified = false; // calculated the merged value for (int csIndex = vs.isOverview ? 0 : vs._beginChangeSetIndex, length = vs._changeSets.size(); csIndex < length; csIndex++) { AttributeChange attributeChange = values.get(csIndex); // use the current attribute for the active change set if (csIndex == vs._activeChangeSetIndex) { aMergedChange.mergeChange(getSerializable(eObject, eAttribute)); if (vs._explicitChangeSet[vs._activeChangeSetIndex]) { explicitlyModified = true; } } // now merge the value from the change set if (attributeChange != null) { Serializable newChange = deserialize(attributeChange.getValue()); oMergedChange.mergeChange(newChange); if (csIndex >= vs._beginChangeSetIndex) { if (csIndex != vs._activeChangeSetIndex) { aMergedChange.mergeChange(newChange); } } } } // update the change set to reflect the new value if (vs._activeChangeSetIndex != -1) { if (!SystemUtils.nullEquals(aMergedChange.value, oMergedChange.value)) { AttributeChange value = synchHelper.createChange(vs, eObject, eAttribute); if (value != null) { value.setValue(serialize(aMergedChange.value)); } } } // update the model with the merged value (if one was merged) Serializable newChange = null; if (vs._activeChangeSetIndex != -1 && aMergedChange.mergedChange) { newChange = aMergedChange.value; } else if (vs._activeChangeSetIndex == -1 && oMergedChange.mergedChange) { newChange = oMergedChange.value; } if (!SystemUtils.nullEquals(newChange, getSerializable(eObject, eAttribute))) { setSerializable(eObject, eAttribute, newChange, endRunnables); } if (explicitlyModified) { mergeStatus(vs, eObject, Status.EXPLICITLY_MODIFIED); } } private void synchronizeDocumentRoot(VariabilityStatus vs, EObject documentRoot) { List<Runnable> endRunnables = Lists.newArrayList(); synchronizeElement(vs, vs._synchronizeIndecies, documentRoot, resolveElementChanges(vs._changeSets, documentRoot), new SynchElementHelper(true), endRunnables); run(endRunnables); } private void synchronizeElement(VariabilityStatus vs, int[] synchronizeIndecies, @Nullable EObject eObject, List<ElementChange> values, SynchElementHelper synchHelper, List<Runnable> endRunnables) { if (!doSync(values, synchronizeIndecies)) { return; } // keep document root and top level element EObject newEObject = eObject; if (eObject == null || eObject.eContainer() != null && eObject.eContainer().eContainer() != null) { newEObject = synchronizeElementType(vs, synchronizeIndecies, eObject, values, synchHelper, endRunnables); } if (newEObject != null) { // if the object was created, we need to reconstruct it using all the change sets if (eObject == null) { synchronizeIndecies = new int[0]; } synchronizeElementContent(vs, synchronizeIndecies, newEObject, values, synchHelper, endRunnables); } } @Nullable private EObject synchronizeElementType(VariabilityStatus vs, int[] synchronizeIndecies, @Nullable EObject oldEObject, List<ElementChange> values, SynchElementHelper synchHelper, List<Runnable> endRunnables) { if (!doSync(values, synchronizeIndecies)) { return oldEObject; } checkArgument(vs._changeSets.size() == values.size()); /* * Determine the type according to (1) the change sets as if the active change set had the current objRef type * (aMergedType), (2) as if all change sets were applied (for the overview - oMergedType), and (3) for explicit * change sets (eMergedType) */ TypeMerger aMergedType = new TypeMerger(synchHelper.isImplicitlyResolvable()); TypeMerger cMergedType = new TypeMerger(synchHelper.isImplicitlyResolvable()); TypeMerger oMergedType = new TypeMerger(synchHelper.isImplicitlyResolvable()); TypeMerger eMergedType = new TypeMerger(synchHelper.isImplicitlyResolvable()); // merge type information for (int csIndex = vs.isOverview ? 0 : vs._beginChangeSetIndex, length = vs._changeSets.size(); csIndex < length; csIndex++) { ElementChange elementChange = values.get(csIndex); // use the current type for the active change set if (csIndex == vs._activeChangeSetIndex) { if (oldEObject != null) { aMergedType.mergeHigherPriorityType(oldEObject.eClass(), true); if (vs._explicitChangeSet[csIndex]) { eMergedType.mergeHigherPriorityType(oldEObject.eClass(), true); } } else { aMergedType.mergeHigherPriorityType(null, false); if (vs._explicitChangeSet[csIndex]) { eMergedType.mergeHigherPriorityType(null, false); } } } // now merge the type from the change set if (elementChange != null) { EClass eClass = typeFromString(elementChange.getType()); boolean isResolvable = eClass != null ? getKey(XArchADTProxy.proxy(elementXArch, put(elementChange))) != null : false; oMergedType.mergeHigherPriorityType(eClass, isResolvable); if (csIndex >= vs._beginChangeSetIndex) { cMergedType.mergeHigherPriorityType(eClass, isResolvable); if (csIndex != vs._activeChangeSetIndex) { aMergedType.mergeHigherPriorityType(eClass, isResolvable); } } if (vs._explicitChangeSet[csIndex]) { if (csIndex != vs._activeChangeSetIndex) { eMergedType.mergeHigherPriorityType(eClass, isResolvable); } } } } // update model EObject newEObject = aMergedType.isResolvable && aMergedType.type != null ? promote(oldEObject, aMergedType.type) : oMergedType.isResolvable && oMergedType.type != null ? promote(oldEObject, oMergedType.type) : null; if (newEObject != oldEObject) { if (oldEObject != null) { setStatus(vs, oldEObject, Status.DETACHED); } if (newEObject != null) { if (oldEObject != null) { synchHelper.clear(oldEObject); } synchHelper.set(newEObject); } } if (newEObject != null) { Status status; if (eMergedType.mergedNull) { status = !aMergedType.mergedNull && aMergedType.isResolvable ? Status.EXPLICITLY_REMOVED_BUT_REALLY_ADDED : Status.EXPLICITLY_REMOVED; } else if (eMergedType.isResolvable && eMergedType.type != null) { status = aMergedType.mergedNull || !aMergedType.isResolvable ? Status.EXPLICITLY_ADDED_BUT_REALLY_REMOVED : Status.EXPLICITLY_ADDED; } else if (eMergedType.type != null) { status = aMergedType.mergedNull || !aMergedType.isResolvable ? Status.EXPLICITLY_MODIFIED_BUT_REALLY_REMOVED : Status.EXPLICITLY_MODIFIED; } else if (aMergedType.isResolvable && !aMergedType.mergedNull) { status = Status.ATTACHED; } else if (vs.isOverview && oMergedType.isResolvable) { status = Status.OVERVIEW; } else { status = Status.DETACHED; } setStatus(vs, newEObject, mergeStatus(vs, newEObject.eContainer(), status)); } // update the change set if (vs._activeChangeSetIndex != -1) { if (aMergedType.mergedNull && !oMergedType.mergedNull) { if (oldEObject != null) { ElementChange elementChange = synchHelper.createChange(vs, oldEObject); if (elementChange != null) { elementChange.setType(null); elementChange.getChange().clear(); } } } else if (!SystemUtils.nullEquals(aMergedType.type, cMergedType.type) && !aMergedType.mergedNull) { if (newEObject != null) { ElementChange elementChange = synchHelper.createChange(vs, newEObject); if (elementChange != null) { elementChange.setType(typeToString(aMergedType.type)); elementChange.getChange().clear(); } } } } return newEObject; } private Status mergeStatus(VariabilityStatus vs, EObject eContainer, Status status) { // mark parents as modified, if necessary EObject parent = eContainer; if (status.explicit) { OUTER: while (parent != null && parent.eContainer() != null && parent.eContainer().eContainer() != null) { switch (getStatus(parent)) { case ATTACHED: setStatus(vs, parent, Status.EXPLICITLY_MODIFIED); break; case OVERVIEW: setStatus(vs, parent, Status.EXPLICITLY_MODIFIED_BUT_REALLY_REMOVED); break; default: break OUTER; } parent = parent.eContainer(); } } switch (getStatus(eContainer)) { case OVERVIEW: return Status.OVERVIEW; case EXPLICITLY_REMOVED: return Status.EXPLICITLY_REMOVED; case EXPLICITLY_REMOVED_BUT_REALLY_ADDED: return Status.EXPLICITLY_REMOVED_BUT_REALLY_ADDED; case EXPLICITLY_MODIFIED_BUT_REALLY_REMOVED: return status == Status.ATTACHED ? vs.isOverview ? Status.OVERVIEW : Status.DETACHED : Status.EXPLICITLY_MODIFIED_BUT_REALLY_REMOVED; case EXPLICITLY_ADDED_BUT_REALLY_REMOVED: return Status.EXPLICITLY_ADDED_BUT_REALLY_REMOVED; default: return status; } } private void synchronizeElementContent(final VariabilityStatus vs, int[] synchronizeIndecies, final EObject eObject, List<ElementChange> values, final SynchElementHelper synchHelper, List<Runnable> endRunnables) { if (!doSync(values, synchronizeIndecies)) { return; } checkArgument(vs._changeSets.size() == values.size()); boolean isDocumentRoot = eObject.eContainer() == null; for (final EStructuralFeature eFeature : eObject.eClass().getEAllStructuralFeatures()) { if (isDocumentRoot) { if (eFeature.getName().equals("mixed")) { continue; } if (eFeature.getName().equals("xMLNSPrefixMap")) { continue; } if (eFeature.getName().equals("xSISchemaLocation")) { continue; } if (eFeature.getName().equals("topLevelElement")) { continue; } } if (eFeature instanceof EAttribute || eFeature instanceof EReference && !((EReference) eFeature).isContainment()) { synchronizeAttribute(vs, synchronizeIndecies, eObject, eFeature, resolveAttributeChanges(values, eFeature.getName()), new SynchAttributeHelper() { @Override @Nullable public AttributeChange createChange(VariabilityStatus vs, EObject eObject, EStructuralFeature eAttribute) { return createAttributeChange(synchHelper.createChange(vs, eObject), eAttribute.getName()); } }, endRunnables); } else if (!eFeature.isMany()) { synchronizeElement(vs, synchronizeIndecies, (EObject) eObject.eGet(eFeature), resolveElementChanges(values, eFeature.getName()), new SynchElementHelper(true) { @Override @Nullable public ElementChange createChange(VariabilityStatus vs, EObject newEObject) { return createElementChange(synchHelper.createChange(vs, eObject), eFeature.getName(), newEObject); } }, endRunnables); } else { synchronizeElementMany(vs, synchronizeIndecies, eObject, eFeature, values, new SynchElementHelper(false) { @Override @Nullable public ElementChange createChange(VariabilityStatus vs, EObject newEObject) { return createElementChange(synchHelper.createChange(vs, eObject), eFeature.getName(), getKey(newEObject), newEObject); } }, endRunnables); } } } private void synchronizeElement(VariabilityStatus vs, int[] synchronizeIndecies, EObject eObject, List<String> preChangePath, @Nullable ElementChange preElementChange, List<String> postChangePath, List<Runnable> endRunnables) { checkArgument(!preChangePath.equals(postChangePath)); if (preElementChange != null) { preElementChange.setType(null); preElementChange.getChange().clear(); } if (!postChangePath.contains(null)) { int diffIndex = 0; while (diffIndex < preChangePath.size() && diffIndex < postChangePath.size()) { if (!SystemUtils.nullEquals(preChangePath.get(diffIndex), postChangePath.get(diffIndex))) { break; } diffIndex++; } while (getChangePath(eObject).size() > diffIndex) { eObject = eObject.eContainer(); } final EStructuralFeature eFeature = eObject.eContainingFeature(); synchronizeElement(vs, synchronizeIndecies, eObject, resolveElementChanges(vs._changeSets, eObject), new SynchElementHelper(!eFeature.isMany()), endRunnables); } } @SuppressWarnings("unchecked") private void synchronizeElementMany(VariabilityStatus vs, int[] synchronizeIndecies, final EObject eObject, final EStructuralFeature eFeature, List<ElementChange> values, final SynchElementHelper synchHelper, List<Runnable> endRunnables) { if (!doSync(values, synchronizeIndecies)) { return; } checkArgument(vs._changeSets.size() == values.size()); // get references to all children Map<String, EObject> referenceToOldChild = Maps.newHashMap(); for (EObject childEObject : Lists.newArrayList((EList<EObject>) eObject.eGet(eFeature))) { String childKey = getKey(childEObject); if (childKey != null) { EObject duplicateChildEObject = referenceToOldChild.put(childKey, childEObject); if (duplicateChildEObject != null) { // we have a duplicate reference, remove it ((EList<EObject>) eObject.eGet(eFeature)).remove(duplicateChildEObject); } } } // process the recorded references Set<String> changesKeys = Sets.newHashSet(); for (ElementChange elementChange : values) { if (elementChange != null) { Change value = lookup(elementChange, eFeature.getName()); if (value instanceof ElementManyChange) { for (ElementChange childElementChange : ((ElementManyChange) value).getChange()) { changesKeys.add(childElementChange.getName()); } } } } for (String changeKey : changesKeys) { synchronizeElement(vs, synchronizeIndecies, referenceToOldChild.remove(changeKey), resolveElementChanges(values, eFeature.getName(), changeKey), synchHelper, endRunnables); } // process the remaining references if (vs.activeChangeSet != null) { // synchronize with the active change set for (Entry<String, EObject> entry : referenceToOldChild.entrySet()) { synchronizeElement(vs, synchronizeIndecies, entry.getValue(), resolveElementChanges(values, eFeature.getName(), entry.getKey()), synchHelper, endRunnables); } } else { // there's no active change set so just remove for (Entry<String, EObject> entry : referenceToOldChild.entrySet()) { setStatus(vs, entry.getValue(), Status.DETACHED); } } } ////////////////////////////////////////////////////////////////////////// @Override public void set(ObjRef baseObjRef, String typeOfThing, @Nullable Serializable value) { wLock.lock(); try { final VariabilityStatus vs = getVariabilityStatusCache(baseObjRef); if (vs == null || !vs.isChangeSetsEnabled || !super.isAttached(baseObjRef)) { super.set(baseObjRef, typeOfThing, value); } else { final EObject eObject = get(baseObjRef); final EStructuralFeature eFeature = getEFeature(eObject, typeOfThing, false); List<String> preChangePath = getChangePath(eObject); Serializable oldValue = getSerializable(eObject, eFeature); super.set(baseObjRef, typeOfThing, value); List<String> postChangePath = getChangePath(eObject); if (!preChangePath.equals(postChangePath)) { super.set(baseObjRef, typeOfThing, oldValue); ElementChange preElementChange = preChangePath.contains(null) ? null : createElementChange( vs.activeChangeSet, eObject); super.set(baseObjRef, typeOfThing, value); synchronizeElement(vs, eObject, preChangePath, preElementChange, postChangePath); } else if (!postChangePath.contains(null)) { if (eFeature instanceof EAttribute || eFeature instanceof EReference && !((EReference) eFeature).isContainment()) { synchronizeAttribute(vs, eObject, eFeature, resolveAttributeChanges(vs.workingChangeSets, eObject, eFeature.getName()), new SynchAttributeHelper()); } else if (value != null) { EObject childEObject = get((ObjRef) value); synchronizeElement(vs, childEObject, resolveElementChanges(vs.workingChangeSets, childEObject), new SynchElementHelper(true)); } else { synchronizeElement(vs, eObject, resolveElementChanges(vs.workingChangeSets, eObject), new SynchElementHelper(true)); } } } } finally { wLock.unlock(); } } @Override public void clear(ObjRef baseObjRef, String typeOfThing) { wLock.lock(); try { final VariabilityStatus vs = getVariabilityStatusCache(baseObjRef); if (vs == null || !vs.isChangeSetsEnabled || !super.isAttached(baseObjRef)) { super.clear(baseObjRef, typeOfThing); } else { final EObject eObject = get(baseObjRef); final EStructuralFeature eFeature = getEFeature(eObject, typeOfThing, false); List<String> preChangePath = getChangePath(eObject); ElementChange preElementChange = preChangePath.contains(null) ? null : createElementChange( vs.activeChangeSet, eObject); if (eFeature instanceof EAttribute || eFeature instanceof EReference && ((EReference) eFeature).isContainment()) { super.clear(baseObjRef, typeOfThing); List<String> postChangePath = getChangePath(eObject); if (!preChangePath.equals(postChangePath)) { synchronizeElement(vs, eObject, preChangePath, preElementChange, postChangePath); } else { synchronizeAttribute(vs, eObject, eFeature, resolveAttributeChanges(vs.workingChangeSets, eObject, eFeature.getName()), new SynchAttributeHelper()); } } else { EObject childEObject = (EObject) eObject.eGet(eFeature); setStatus(vs, childEObject, Status.DETACHED); List<String> postChangePath = getChangePath(eObject); if (!preChangePath.equals(postChangePath)) { synchronizeElement(vs, eObject, preChangePath, preElementChange, postChangePath); } else { synchronizeElement(vs, childEObject, resolveElementChanges(vs.workingChangeSets, childEObject), new SynchElementHelper(true)); } } } } finally { wLock.unlock(); } } @Override @SuppressWarnings("unchecked") public void add(ObjRef baseObjRef, String typeOfThing, Serializable thingToAdd) { wLock.lock(); try { final VariabilityStatus vs = getVariabilityStatusCache(baseObjRef); if (vs == null || !vs.isChangeSetsEnabled || !super.isAttached(baseObjRef)) { super.add(baseObjRef, typeOfThing, thingToAdd); } else { final EObject eObject = get(baseObjRef); final EStructuralFeature eFeature = getEFeature(eObject, typeOfThing, true); super.add(baseObjRef, typeOfThing, thingToAdd); if (!(thingToAdd instanceof ObjRef)) { List<String> changePath = getChangePath(eObject); if (!changePath.contains(null)) { synchronizeElement(vs, eObject, resolveElementChanges(vs.workingChangeSets, eObject), new SynchElementHelper(false)); } } else { final EObject eChildObject = get((ObjRef) thingToAdd); List<String> changePath = getChangePath(eChildObject); if (!changePath.contains(null)) { synchronizeElement(vs, eChildObject, resolveElementChanges(vs.workingChangeSets, eChildObject), new SynchElementHelper(false)); } else { EList<EObject> eList = (EList<EObject>) eObject.eGet(eFeature); eList.move(eList.size() - 1, eChildObject); setStatus(vs, eChildObject, Status.ATTACHED); } } } } finally { wLock.unlock(); } } @Override public void add(ObjRef baseObjRef, String typeOfThing, Collection<? extends Serializable> thingsToAdd) { wLock.lock(); try { for (Serializable thingToAdd : thingsToAdd) { add(baseObjRef, typeOfThing, thingToAdd); } } finally { wLock.unlock(); } } @Override public void remove(ObjRef baseObjRef, String typeOfThing, Serializable thingToRemove) { wLock.lock(); try { final VariabilityStatus vs = getVariabilityStatusCache(baseObjRef); if (vs == null || !vs.isChangeSetsEnabled || !super.isAttached(baseObjRef)) { super.remove(baseObjRef, typeOfThing, thingToRemove); } else { final EObject eObject = get(baseObjRef); if (!(thingToRemove instanceof ObjRef)) { super.remove(baseObjRef, typeOfThing, thingToRemove); List<String> changePath = getChangePath(eObject); if (!changePath.contains(null)) { synchronizeElement(vs, eObject, resolveElementChanges(vs.workingChangeSets, eObject), new SynchElementHelper(false)); } } else { final EObject eChildObject = get((ObjRef) thingToRemove); List<String> preChangePath = getChangePath(eChildObject); ElementChange preElementChange = preChangePath.contains(null) ? null : createElementChange( vs.activeChangeSet, eChildObject); if (preElementChange != null) { preElementChange.setType(null); preElementChange.getChange().clear(); List<Runnable> endRunnables = Lists.newArrayList(); vs.setup(vs.workingChangeSets, vs.workingChangeSetsBeginIndex, -1, new int[0]); synchronizeElement(vs, vs._synchronizeIndecies, eChildObject, resolveElementChanges(vs.workingChangeSets, eChildObject), new SynchElementHelper(false), endRunnables); run(endRunnables); } } } } finally { wLock.unlock(); } } @Override public void remove(ObjRef baseObjRef, String typeOfThing, Collection<? extends Serializable> thingsToRemove) { wLock.lock(); try { for (Serializable thingToRemove : thingsToRemove) { remove(baseObjRef, typeOfThing, thingToRemove); } } finally { wLock.unlock(); } } ////////////////////////////////////////////////////////////////////////// @Override public ObjRef load(URI uri) throws SAXException, IOException { wLock.lock(); try { ObjRef documentRootRef = super.load(uri); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (vs != null && vs.isChangeSetsEnabled) { vs.refreshFromXadl(); vs.setup(vs.appliedChangeSets, 0, -1, new int[0]); synchronizeDocumentRoot(vs, get(documentRootRef)); } return documentRootRef; } finally { wLock.unlock(); } }; @Override public ObjRef load(URI uri, byte[] content) throws SAXException, IOException { wLock.lock(); try { ObjRef documentRootRef = super.load(uri, content); VariabilityStatus vs = variabilityStatusCache.getUnchecked(documentRootRef); if (vs != null && vs.isChangeSetsEnabled) { vs.refreshFromXadl(); vs.setup(vs.appliedChangeSets, 0, -1, new int[0]); synchronizeDocumentRoot(vs, get(documentRootRef)); } return documentRootRef; } finally { wLock.unlock(); } }; @Override public @Nullable Serializable get(ObjRef baseObjRef, String typeOfThing) { rLock.lock(); try { return filterDetached(super.get(baseObjRef, typeOfThing)); } finally { rLock.unlock(); } } @Override public @Nullable Serializable get(ObjRef baseObjRef, String typeOfThing, boolean resolve) { rLock.lock(); try { return filterDetached(super.get(baseObjRef, typeOfThing, resolve)); } finally { rLock.unlock(); } } @Override public List<Serializable> getAll(ObjRef baseObjRef, String typeOfThing) { rLock.lock(); try { return filterDetached(super.getAll(baseObjRef, typeOfThing)); } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getByID(ObjRef documentRef, String id) { rLock.lock(); try { return filterDetachedByAncestors(super.getByID(documentRef, id)); } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getByID(String id) { rLock.lock(); try { return filterDetachedByAncestors(super.getByID(id)); } finally { rLock.unlock(); } } @Override @Nullable public String getContainingFeatureName(ObjRef ref) { rLock.lock(); try { return isAttachedObjRef(ref) ? super.getContainingFeatureName(ref) : null; } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getParent(ObjRef ref) { rLock.lock(); try { return isAttachedObjRef(ref) ? super.getParent(ref) : null; } finally { rLock.unlock(); } } @Override @Nullable public String getTagName(ObjRef ref) { rLock.lock(); try { return isAttachedObjRef(ref) ? super.getTagName(ref) : null; } finally { rLock.unlock(); } } @Override @Nullable public URI getURI(ObjRef ref) { rLock.lock(); try { return isAttachedObjRef(ref) ? super.getURI(ref) : null; } finally { rLock.unlock(); } } @Override public List<ObjRef> getAllAncestors(ObjRef ref) { rLock.lock(); try { EObject eObject = get(ref); List<ObjRef> ancestorObjRefs = Lists.newArrayList(); while (eObject != null) { ancestorObjRefs.add(put(eObject)); if (!isAttached(eObject)) { break; } eObject = eObject.eContainer(); } return ancestorObjRefs; } finally { rLock.unlock(); } } @Override public String getTagsOnlyPathString(ObjRef ref) { rLock.lock(); try { EObject eObject = get(ref); List<String> tags = Lists.newArrayList(); while (eObject != null) { if (!isAttached(eObject)) { break; } String tagName = getTagName(put(eObject)); if (tagName == null) { break; } tags.add(tagName); eObject = eObject.eContainer(); } return Joiner.on("/").join(Lists.reverse(tags)); } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getDocumentRootRef(ObjRef ref) { rLock.lock(); try { return isAttachedObjRef(ref) ? super.getDocumentRootRef(ref) : null; } finally { rLock.unlock(); } } }