| /******************************************************************************* |
| * Copyright (c) 2009, 2012 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| ******************************************************************************/ |
| |
| package org.eclipse.e4.ui.internal.workbench; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.e4.ui.model.application.MAddon; |
| import org.eclipse.e4.ui.model.application.MApplication; |
| import org.eclipse.e4.ui.model.application.MApplicationElement; |
| import org.eclipse.e4.ui.model.application.MContribution; |
| import org.eclipse.e4.ui.model.application.commands.MBindingTable; |
| import org.eclipse.e4.ui.model.application.commands.MBindingTableContainer; |
| import org.eclipse.e4.ui.model.application.commands.MCommand; |
| import org.eclipse.e4.ui.model.application.commands.MCommandParameter; |
| import org.eclipse.e4.ui.model.application.commands.MHandler; |
| import org.eclipse.e4.ui.model.application.commands.MHandlerContainer; |
| import org.eclipse.e4.ui.model.application.commands.MKeyBinding; |
| import org.eclipse.e4.ui.model.application.commands.MParameter; |
| import org.eclipse.e4.ui.model.application.descriptor.basic.MPartDescriptor; |
| import org.eclipse.e4.ui.model.application.descriptor.basic.MPartDescriptorContainer; |
| import org.eclipse.e4.ui.model.application.impl.ApplicationPackageImpl; |
| import org.eclipse.e4.ui.model.application.ui.MContext; |
| import org.eclipse.e4.ui.model.application.ui.MElementContainer; |
| import org.eclipse.e4.ui.model.application.ui.MUIElement; |
| import org.eclipse.e4.ui.model.application.ui.SideValue; |
| import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective; |
| import org.eclipse.e4.ui.model.application.ui.basic.MPart; |
| import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; |
| import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow; |
| import org.eclipse.e4.ui.model.application.ui.basic.MWindow; |
| import org.eclipse.e4.ui.model.application.ui.impl.UiPackageImpl; |
| import org.eclipse.e4.ui.model.application.ui.menu.ItemType; |
| import org.eclipse.e4.ui.model.application.ui.menu.MHandledItem; |
| import org.eclipse.e4.ui.model.application.ui.menu.MMenu; |
| import org.eclipse.e4.ui.model.application.ui.menu.MMenuContribution; |
| import org.eclipse.e4.ui.model.application.ui.menu.MMenuContributions; |
| import org.eclipse.e4.ui.model.application.ui.menu.MToolBar; |
| import org.eclipse.e4.ui.model.application.ui.menu.MToolBarContribution; |
| import org.eclipse.e4.ui.model.application.ui.menu.MToolBarContributions; |
| import org.eclipse.e4.ui.model.application.ui.menu.MTrimContribution; |
| import org.eclipse.e4.ui.model.application.ui.menu.MTrimContributions; |
| import org.eclipse.e4.ui.model.application.ui.menu.impl.MenuPackageImpl; |
| import org.eclipse.e4.ui.workbench.modeling.IDelta; |
| import org.eclipse.e4.ui.workbench.modeling.ModelDelta; |
| import org.eclipse.e4.ui.workbench.modeling.ModelReconciler; |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.EMap; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EFactory; |
| 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.change.ChangeDescription; |
| import org.eclipse.emf.ecore.change.FeatureChange; |
| import org.eclipse.emf.ecore.change.util.ChangeRecorder; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.xmi.XMLResource; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| public class XMLModelReconciler extends ModelReconciler { |
| |
| private static final String XMIID_ATTNAME = "xmiId"; //$NON-NLS-1$ |
| |
| private static final String REFERENCE_ELEMENT_NAME = "reference"; //$NON-NLS-1$ |
| private static final String ORIGINALREFERENCE_ELEMENT_NAME = "originalReference"; //$NON-NLS-1$ |
| |
| private static final String NAMESPACE_ATTNAME = "e4namespace"; //$NON-NLS-1$ |
| private static final String OLD_CONTRIBUTION_URI_PREFIX = "platform:/plugin/"; //$NON-NLS-1$ |
| private static final String NEW_CONTRIBUTION_URI_PREFIX = "bundleclass://"; //$NON-NLS-1$ |
| |
| /** |
| * The name of the root element that describes the model deltas in XML form (value is |
| * <code>changes</code>). |
| */ |
| private static final String CHANGES_ELEMENT_NAME = "changes"; //$NON-NLS-1$ |
| |
| /** |
| * The name of the attribute that describes the version of the model deltas (value is |
| * <code>version</code>). |
| */ |
| private static final String VERSION_ATTNAME = "version"; //$NON-NLS-1$ |
| |
| /** |
| * The version of the model deltas. |
| * |
| * <ul> |
| * <li>1.1 - introduced direct references to binding contexts instead of using string ids (see |
| * bug 320171 and bug 338444)</li> |
| * <li>1.0 (no change) - the model was updated with MArea, code was inserted to handle this case |
| * so the version number was not actually increased (see bug 328388)</li> |
| * <li>1.0 - first version of the model that went out for 4.0</li> |
| * </ul> |
| */ |
| // a new string is constructed because we do not know want the value to be inlined |
| private static final String VERSION_NUMBER = new String("1.1"); //$NON-NLS-1$ |
| |
| /** |
| * An attribute for describing the type of the object in question (value is <code>type</code>). |
| */ |
| private static final String TYPE_ATTNAME = "type"; //$NON-NLS-1$ |
| |
| private static final String UNSET_ATTNAME = "unset"; //$NON-NLS-1$ |
| private static final String UNSET_ATTVALUE_TRUE = "true"; //$NON-NLS-1$ |
| |
| private static final String ENTRY_ATTVALUE_KEY = "key"; //$NON-NLS-1$ |
| private static final String ENTRY_ATTVALUE_VALUE = "value"; //$NON-NLS-1$ |
| |
| private ChangeRecorder changeRecorder; |
| |
| private ChangeDescription changeDescription; |
| |
| private EObject rootObject; |
| |
| /** |
| * A map of all the objects that were originally defined in the model. |
| */ |
| private WeakHashMap<EObject, EObject> originalObjects = new WeakHashMap<EObject, EObject>(); |
| |
| /** |
| * Records all of the objects in the original model so that we can determine whether an element |
| * was originally a part of the defined model or not. |
| */ |
| private void record() { |
| originalObjects.clear(); |
| originalObjects.put(rootObject, null); |
| Iterator<EObject> it = rootObject.eAllContents(); |
| while (it.hasNext()) { |
| EObject object = it.next(); |
| originalObjects.put(object, null); |
| } |
| } |
| |
| public void recordChanges(Object object) { |
| Assert.isNotNull(object); |
| rootObject = (EObject) object; |
| changeRecorder = new ChangeRecorder(rootObject) { |
| protected boolean shouldRecord(EStructuralFeature feature, EObject eObject) { |
| return !feature.isTransient() && super.shouldRecord(feature, eObject); |
| } |
| |
| protected boolean shouldRecord(EStructuralFeature feature, EReference containment, |
| Notification notification, EObject eObject) { |
| return !feature.isTransient() |
| && super.shouldRecord(feature, containment, notification, eObject); |
| } |
| }; |
| changeDescription = null; |
| record(); |
| } |
| |
| static List<Object> getReferences(Object object) { |
| Iterator<EObject> it = ((EObject) object).eAllContents(); |
| List<Object> references = new LinkedList<Object>(); |
| while (it.hasNext()) { |
| Object reference = it.next(); |
| references.add(reference); |
| } |
| return references; |
| } |
| |
| public Collection<ModelDelta> constructDeltas(Object object, Object serializedState) { |
| rootObject = (EObject) object; |
| List<Object> references = getReferences(rootObject); |
| |
| Document document = (Document) serializedState; |
| |
| Collection<ModelDelta> deltas = new LinkedList<ModelDelta>(); |
| |
| Element rootElement = document.getDocumentElement(); |
| String version = rootElement.getAttribute(VERSION_ATTNAME); |
| try { |
| if (version == null || version.length() == 0 |
| || Double.parseDouble(version) < Double.parseDouble(VERSION_NUMBER)) { |
| return deltas; |
| } |
| } catch (NumberFormatException e) { |
| // some corrupt versioning, ignore this deltas file |
| return deltas; |
| } |
| |
| NodeList rootNodeList = (NodeList) rootElement; |
| for (int i = 0; i < rootNodeList.getLength(); i++) { |
| Node node = rootNodeList.item(i); |
| if (node instanceof Element) { |
| Element element = (Element) node; |
| constructDeltas(deltas, references, rootObject, element, |
| element.getAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME)); |
| } |
| } |
| |
| return deltas; |
| } |
| |
| private static EStructuralFeature getStructuralFeature(EObject object, String featureName) { |
| for (EStructuralFeature sf : object.eClass().getEAllStructuralFeatures()) { |
| if (sf.getName().equals(featureName)) { |
| return sf; |
| } |
| } |
| return null; |
| } |
| |
| private Object getValue(EStructuralFeature feature, String featureValue) { |
| Class<?> instanceClass = feature.getEType().getInstanceClass(); |
| if (instanceClass == String.class) { |
| return featureValue; |
| } else if (instanceClass == int.class) { |
| return Integer.valueOf(featureValue); |
| } else if (instanceClass == boolean.class) { |
| return Boolean.valueOf(featureValue); |
| } else if (feature == UiPackageImpl.eINSTANCE.getGenericTrimContainer_Side()) { |
| return SideValue.getByName(featureValue); |
| } else if (feature == MenuPackageImpl.eINSTANCE.getItem_Type()) { |
| return ItemType.getByName(featureValue); |
| } |
| return null; |
| } |
| |
| static Object findReference(List<Object> references, String id) { |
| for (Object reference : references) { |
| if (reference instanceof MApplicationElement && getLocalId(reference).equals(id)) { |
| return reference; |
| } |
| } |
| |
| return null; |
| } |
| |
| private boolean constructDeltas(Collection<ModelDelta> deltas, List<Object> references, |
| EObject object, Element element, String id) { |
| if (object instanceof MApplicationElement || object instanceof MKeyBinding) { |
| if (getLocalId(object).equals(id)) { |
| constructDeltas(deltas, references, object, element); |
| return true; |
| } |
| } |
| |
| if (object instanceof MElementContainer<?>) { |
| for (Object child : ((MElementContainer<?>) object).getChildren()) { |
| if (constructDeltas(deltas, references, (EObject) child, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MPerspective) { |
| for (MWindow window : ((MPerspective) object).getWindows()) { |
| if (constructDeltas(deltas, references, (EObject) window, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MBindingTableContainer) { |
| for (MBindingTable bindingTable : ((MBindingTableContainer) object).getBindingTables()) { |
| if (constructDeltas(deltas, references, (EObject) bindingTable, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MBindingTable) { |
| for (MKeyBinding keyBinding : ((MBindingTable) object).getBindings()) { |
| if (constructDeltas(deltas, references, (EObject) keyBinding, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MHandlerContainer) { |
| for (MHandler handler : ((MHandlerContainer) object).getHandlers()) { |
| if (constructDeltas(deltas, references, (EObject) handler, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MApplication) { |
| for (MCommand command : ((MApplication) object).getCommands()) { |
| if (constructDeltas(deltas, references, (EObject) command, element, id)) { |
| return true; |
| } |
| } |
| |
| for (MAddon addon : ((MApplication) object).getAddons()) { |
| if (constructDeltas(deltas, references, (EObject) addon, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MPartDescriptorContainer) { |
| for (MPartDescriptor descriptor : ((MPartDescriptorContainer) object).getDescriptors()) { |
| if (constructDeltas(deltas, references, (EObject) descriptor, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MPart) { |
| MPart part = (MPart) object; |
| |
| for (MMenu menu : part.getMenus()) { |
| if (constructDeltas(deltas, references, (EObject) menu, element, id)) { |
| return true; |
| } |
| } |
| |
| MToolBar toolBar = part.getToolbar(); |
| if (toolBar != null) { |
| if (constructDeltas(deltas, references, (EObject) toolBar, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MMenuContributions) { |
| for (MMenuContribution contribution : ((MMenuContributions) object) |
| .getMenuContributions()) { |
| if (constructDeltas(deltas, references, (EObject) contribution, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MToolBarContributions) { |
| for (MToolBarContribution contribution : ((MToolBarContributions) object) |
| .getToolBarContributions()) { |
| if (constructDeltas(deltas, references, (EObject) contribution, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MTrimContributions) { |
| for (MTrimContribution contribution : ((MTrimContributions) object) |
| .getTrimContributions()) { |
| if (constructDeltas(deltas, references, (EObject) contribution, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| if (object instanceof MWindow) { |
| MWindow window = (MWindow) object; |
| if (constructDeltas(deltas, references, (EObject) window.getMainMenu(), element, id)) { |
| return true; |
| } |
| |
| if (object instanceof MTrimmedWindow) { |
| MTrimmedWindow trimmedWindow = (MTrimmedWindow) object; |
| for (MTrimBar trimBar : trimmedWindow.getTrimBars()) { |
| if (constructDeltas(deltas, references, (EObject) trimBar, element, id)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| if (object instanceof MHandledItem) { |
| for (MParameter parameter : ((MHandledItem) object).getParameters()) { |
| if (constructDeltas(deltas, references, (EObject) parameter, element, id)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private void constructDeltas(Collection<ModelDelta> deltas, List<Object> references, |
| EObject object, Element element) { |
| String elementName = element.getNodeName(); |
| if (elementName.equals(CONTEXT_PROPERTIES_ATTNAME)) { |
| constructEntryDelta(deltas, UiPackageImpl.eINSTANCE.getContext_Properties(), object, |
| element); |
| } else if (elementName.equals(APPLICATIONELEMENT_PERSISTEDSTATE_ATTNAME)) { |
| constructEntryDelta(deltas, |
| ApplicationPackageImpl.eINSTANCE.getApplicationElement_PersistedState(), |
| object, element); |
| } else { |
| constructObjectDeltas(deltas, references, object, element); |
| } |
| } |
| |
| private void constructEntryDelta(Collection<ModelDelta> deltas, EStructuralFeature feature, |
| EObject object, Element element) { |
| if (element.getAttribute(UNSET_ATTNAME).equals(UNSET_ATTVALUE_TRUE)) { |
| EMFDeltaEntrySet delta = new EMFDeltaEntrySet(object, feature, |
| element.getAttribute(ENTRY_ATTVALUE_KEY), null); |
| deltas.add(delta); |
| } else { |
| EMFDeltaEntrySet delta = new EMFDeltaEntrySet(object, feature, |
| element.getAttribute(ENTRY_ATTVALUE_KEY), |
| element.getAttribute(ENTRY_ATTVALUE_VALUE)); |
| deltas.add(delta); |
| } |
| } |
| |
| private void constructObjectDeltas(Collection<ModelDelta> deltas, List<Object> references, |
| EObject object, Element element) { |
| NodeList nodeList = (NodeList) element; |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (!(node instanceof Element)) { |
| continue; |
| } |
| |
| Element innerElement = (Element) node; |
| String featureName = innerElement.getNodeName(); |
| EStructuralFeature feature = getStructuralFeature(object, featureName); |
| if (feature != null) { |
| if (isChainedReference(featureName)) { |
| ModelDelta delta = createMultiReferenceDelta(deltas, references, object, |
| feature, innerElement); |
| deltas.add(delta); |
| } else if (isUnset(innerElement)) { |
| ModelDelta delta = new EMFModelDeltaUnset(object, feature); |
| deltas.add(delta); |
| } else if (isDirectReference(featureName)) { |
| ModelDelta delta = createDirectReferenceDelta(deltas, references, object, |
| feature, innerElement); |
| deltas.add(delta); |
| } else if (isIndirectReference(featureName)) { |
| ModelDelta delta = createIndirectReferenceDelta(references, object, feature, |
| innerElement); |
| deltas.add(delta); |
| } else if (isUnorderedChainedAttribute(featureName)) { |
| ModelDelta delta = createUnorderedChainedAttributeDelta(object, feature, |
| innerElement, featureName); |
| deltas.add(delta); |
| } else if (isStringToStringMap(featureName)) { |
| ModelDelta delta = createMapDelta(object, innerElement, feature); |
| deltas.add(delta); |
| } else { |
| ModelDelta delta = createAttributeDelta(object, feature, innerElement, |
| featureName); |
| deltas.add(delta); |
| } |
| } |
| } |
| } |
| |
| private ModelDelta createDirectReferenceDelta(Collection<ModelDelta> deltas, |
| List<Object> references, EObject eObject, EStructuralFeature feature, Element node) { |
| NodeList referencedIds = (NodeList) node; |
| Element reference = getFirstElement(referencedIds); |
| String referenceId = reference.getAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME); |
| |
| Object match = findReference(references, referenceId); |
| if (match == null) { |
| // couldn't find a reference, must be a new object |
| match = createObject(deltas, reference, references); |
| } |
| |
| return new EMFModelDeltaSet(eObject, feature, match); |
| } |
| |
| private static Element getFirstElement(NodeList list) { |
| for (int i = 0; i < list.getLength(); i++) { |
| Node item = list.item(i); |
| if (item instanceof Element) { |
| return (Element) item; |
| } |
| } |
| return null; |
| } |
| |
| private ModelDelta createIndirectReferenceDelta(List<Object> references, EObject eObject, |
| EStructuralFeature feature, Element node) { |
| NodeList referencedIds = (NodeList) node; |
| |
| Element reference = getFirstElement(referencedIds); |
| String referenceId = reference.getAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME); |
| |
| Object match = findReference(references, referenceId); |
| if (match == null) { |
| return createDelayedDelta(eObject, feature, reference); |
| } |
| |
| return new EMFModelDeltaSet(eObject, feature, match); |
| } |
| |
| private ModelDelta createMapDelta(EObject object, Element innerElement, |
| EStructuralFeature feature) { |
| Map<String, String> deltaMap = new HashMap<String, String>(); |
| NodeList attributes = (NodeList) innerElement; |
| for (int j = 0; j < attributes.getLength(); j++) { |
| Node entry = attributes.item(j); |
| if (entry instanceof Element) { |
| Element keyValue = (Element) entry; |
| String key = keyValue.getAttribute(ENTRY_ATTVALUE_KEY); |
| String value = keyValue.getAttribute(ENTRY_ATTVALUE_VALUE); |
| deltaMap.put(key, value); |
| } |
| } |
| return new EMFDeltaMapSet(object, feature, deltaMap); |
| } |
| |
| private ModelDelta createDelayedDelta(EObject object, EStructuralFeature feature, |
| Element element) { |
| String referenceId = element.getAttribute(XMIID_ATTNAME); |
| return new EMFModelDeltaDelayedSet(object, feature, rootObject, referenceId); |
| } |
| |
| public static List<?> threeWayMerge(List<?> originalReferences, List<?> userReferences, |
| List<?> currentReferences) { |
| int userSize = userReferences.size(); |
| int originalSize = originalReferences.size(); |
| |
| if (userSize == 0) { |
| // the user removed all the original parts |
| List<Object> collectedReferences = new ArrayList<Object>(currentReferences); |
| collectedReferences.removeAll(originalReferences); |
| return collectedReferences; |
| } else if (originalSize == 0) { |
| List<Object> collectedReferences = new ArrayList<Object>(userReferences); |
| collectedReferences.addAll(currentReferences); |
| return collectedReferences; |
| } else if (currentReferences.isEmpty()) { |
| // currently not referencing anything, so just return what the user had exactly |
| return userReferences; |
| } else if (currentReferences.size() == originalSize |
| && currentReferences.containsAll(originalReferences)) { |
| // since both versions contain the same thing, just use whatever the user had |
| return userReferences; |
| } |
| |
| if (originalReferences.containsAll(userReferences) |
| && !userReferences.containsAll(originalReferences)) { |
| List<Object> collectedReferences2 = new ArrayList<Object>(originalReferences); |
| collectedReferences2.removeAll(userReferences); |
| |
| List<Object> collectedReferences = new ArrayList<Object>(currentReferences); |
| collectedReferences.removeAll(collectedReferences2); |
| |
| return collectedReferences; |
| } |
| |
| List<Position> positions = new ArrayList<Position>(); |
| |
| for (int i = 0; i < userReferences.size(); i++) { |
| Object user = userReferences.get(i); |
| |
| Position p = getPosition(originalReferences, userReferences, currentReferences, user, i); |
| if (p != null) { |
| positions.add(p); |
| } |
| } |
| |
| List<Object> collectedRefs = new ArrayList<Object>(currentReferences); |
| |
| for (Position position : positions) { |
| Object after = position.getAfter(); |
| if (after != null) { |
| int index = currentReferences.indexOf(after); |
| collectedRefs.add(index + 1, position.getObject()); |
| } |
| |
| Object before = position.getBefore(); |
| if (before != null) { |
| int index = currentReferences.indexOf(before); |
| collectedRefs.add(index, position.getObject()); |
| } |
| } |
| |
| return collectedRefs; |
| } |
| |
| private static Position getPosition(List<?> originalReferences, List<?> userReferences, |
| List<?> currentReferences, Object object, int originalIndex) { |
| int index = originalReferences.indexOf(object); |
| if (index == -1) { |
| Object after = null; |
| for (int i = originalIndex - 1; i > -1; i--) { |
| Object afterCandidate = userReferences.get(i); |
| int afterIndex = currentReferences.indexOf(afterCandidate); |
| if (afterIndex != -1) { |
| after = afterCandidate; |
| break; |
| } |
| } |
| |
| Object before = null; |
| for (int i = originalIndex + 1; i < userReferences.size(); i++) { |
| Object beforeCandidate = userReferences.get(i); |
| int beforeIndex = currentReferences.indexOf(beforeCandidate); |
| if (beforeIndex != -1) { |
| before = beforeCandidate; |
| break; |
| } |
| } |
| |
| return new Position(object, after, before); |
| } |
| return null; |
| } |
| |
| static class Position { |
| |
| private final Object object; |
| private final Object after; |
| private final Object before; |
| |
| Position(Object object, Object after, Object before) { |
| this.object = object; |
| this.after = after; |
| this.before = before; |
| } |
| |
| public Object getObject() { |
| return object; |
| } |
| |
| public Object getBefore() { |
| return before; |
| } |
| |
| public Object getAfter() { |
| return after; |
| } |
| |
| } |
| |
| private ModelDelta createMultiReferenceDelta(Collection<ModelDelta> deltas, |
| List<Object> references, EObject eObject, EStructuralFeature feature, Element node) { |
| NodeList referencedIds = (NodeList) node; |
| List<Object> originalReferences = new ArrayList<Object>(); |
| List<Object> userReferences = new ArrayList<Object>(); |
| List<?> currentReferences = (List<?>) eObject.eGet(feature); |
| |
| for (int i = 0; i < referencedIds.getLength(); i++) { |
| Node item = referencedIds.item(i); |
| if (item instanceof Element) { |
| Element reference = (Element) item; |
| if (isUnset(reference)) { |
| userReferences.add(createObject(deltas, reference, references)); |
| } else { |
| String referenceId = reference |
| .getAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME); |
| Object match = findReference(references, referenceId); |
| if (match != null) { |
| // determine if this was a reference set by the user or a reference that was |
| // originally defined by the application |
| if (reference.getNodeName().equals(REFERENCE_ELEMENT_NAME)) { |
| userReferences.add(match); |
| } else { |
| originalReferences.add(match); |
| } |
| } |
| } |
| } |
| } |
| |
| return new EMFModelDeltaThreeWayDelayedSet(eObject, feature, originalReferences, |
| userReferences, currentReferences); |
| } |
| |
| private static EObject createObject(String namespace, String type) { |
| EFactory factory = EPackage.Registry.INSTANCE.getEFactory(namespace); |
| for (EClassifier classifier : factory.getEPackage().getEClassifiers()) { |
| if (classifier instanceof EClass) { |
| EClass cls = (EClass) classifier; |
| if (cls.getInstanceClassName().equals(type)) { |
| return factory.create(cls); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private Object getReference(Collection<ModelDelta> deltas, Element element, |
| List<Object> references) { |
| String id = element.getAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME); |
| if (!id.equals("")) { //$NON-NLS-1$ |
| return findReference(references, id); |
| } |
| return createObject(deltas, element, references); |
| } |
| |
| private Object createObject(Collection<ModelDelta> deltas, Element element, |
| List<Object> references) { |
| String typeName = element.getAttribute(TYPE_ATTNAME); |
| String namespace = element.getAttribute(NAMESPACE_ATTNAME); |
| |
| EObject object = createObject(namespace, typeName); |
| CompositeDelta compositeDelta = new CompositeDelta(object); |
| |
| E4XMIResource resource = (E4XMIResource) rootObject.eResource(); |
| resource.setInternalId(object, element.getAttribute(XMIID_ATTNAME)); |
| |
| NodeList elementAttributes = (NodeList) element; |
| for (int i = 0; i < elementAttributes.getLength(); i++) { |
| Node node = elementAttributes.item(i); |
| if (node instanceof Element) { |
| Element item = (Element) node; |
| if (!isUnset(item)) { |
| String attributeName = item.getNodeName(); |
| EStructuralFeature attributeFeature = getStructuralFeature(object, |
| attributeName); |
| if (attributeFeature != null) { |
| if (isDirectReference(attributeName)) { |
| String id = item.getAttribute(attributeName); |
| Object objectReference = findReference(references, id); |
| if (objectReference == null) { |
| objectReference = createObject(deltas, |
| getFirstElement((NodeList) item), references); |
| } |
| |
| IDelta delta = new EMFModelDeltaSet(object, attributeFeature, |
| objectReference); |
| compositeDelta.add(delta); |
| } else if (isIndirectReference(attributeName)) { |
| String id = item.getAttribute(attributeName); |
| Object objectReference = findReference(references, id); |
| if (objectReference == null) { |
| NodeList list = (NodeList) item; |
| Element refElement = getFirstElement(list); |
| if (refElement != null) { |
| ModelDelta delta = createDelayedDelta(object, attributeFeature, |
| refElement); |
| deltas.add(delta); |
| } |
| } else { |
| IDelta delta = new EMFModelDeltaSet(object, attributeFeature, |
| objectReference); |
| compositeDelta.add(delta); |
| } |
| } else if (isChainedReference(attributeName)) { |
| List<Object> objectReferences = new ArrayList<Object>(); |
| NodeList objectReferenceNodes = (NodeList) item; |
| |
| for (int j = 0; j < objectReferenceNodes.getLength(); j++) { |
| Node refNode = objectReferenceNodes.item(j); |
| if (refNode instanceof Element) { |
| Object objectReference = getReference(deltas, |
| (Element) refNode, references); |
| if (objectReference != null) { |
| objectReferences.add(objectReference); |
| } |
| } |
| } |
| |
| IDelta delta = new EMFModelDeltaSet(object, attributeFeature, |
| objectReferences); |
| compositeDelta.add(delta); |
| } else if (isUnorderedChainedAttribute(attributeName)) { |
| ModelDelta delta = createUnorderedChainedAttributeDelta(object, |
| attributeFeature, item, attributeName); |
| deltas.add(delta); |
| } else if (isStringToStringMap(attributeName)) { |
| EStructuralFeature feature = getStructuralFeature(object, attributeName); |
| EMap map = (EMap) object.eGet(feature); |
| |
| NodeList attributes = (NodeList) item; |
| for (int j = 0; j < attributes.getLength(); j++) { |
| Node entry = attributes.item(j); |
| if (entry instanceof Element) { |
| Element keyValue = (Element) entry; |
| map.put(keyValue.getAttribute(ENTRY_ATTVALUE_KEY), |
| keyValue.getAttribute(ENTRY_ATTVALUE_VALUE)); |
| } |
| } |
| } else { |
| Object value = getValue(attributeFeature, |
| item.getAttribute(attributeName)); |
| if (CONTRIBUTION_URI_ATTNAME.equals(attributeFeature.getName())) |
| value = ((String) value).replaceFirst(OLD_CONTRIBUTION_URI_PREFIX, |
| NEW_CONTRIBUTION_URI_PREFIX); |
| object.eSet(attributeFeature, value); |
| } |
| } |
| } |
| } |
| } |
| |
| return compositeDelta; |
| } |
| |
| private ModelDelta createUnorderedChainedAttributeDelta(EObject object, |
| EStructuralFeature feature, Element node, String featureName) { |
| Set<Object> values = new HashSet<Object>(); |
| NodeList attributes = (NodeList) node; |
| for (int j = 0; j < attributes.getLength(); j++) { |
| Node item = attributes.item(j); |
| if (item instanceof Element) { |
| Element attribute = (Element) item; |
| Object value = getValue(feature, attribute.getAttribute(featureName)); |
| values.add(value); |
| } |
| } |
| |
| List<?> currentValues = (List<?>) object.eGet(feature); |
| values.addAll(currentValues); |
| |
| return new EMFModelDeltaSet(object, feature, new ArrayList<Object>(values)); |
| } |
| |
| private ModelDelta createAttributeDelta(EObject eObject, EStructuralFeature feature, |
| Element node, String featureName) { |
| Object value = getValue(feature, node.getAttribute(featureName)); |
| return new EMFModelDeltaSet(eObject, feature, value); |
| } |
| |
| private Document createDocument() { |
| try { |
| return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private ChangeDescription calculateDeltas() { |
| if (changeDescription == null) { |
| changeDescription = changeRecorder.endRecording(); |
| } |
| return changeDescription; |
| } |
| |
| public Object serialize() { |
| calculateDeltas(); |
| |
| // begin construction of the XML document |
| Document document = createDocument(); |
| Element root = document.createElement(CHANGES_ELEMENT_NAME); |
| root.setAttribute(VERSION_ATTNAME, VERSION_NUMBER); |
| document.appendChild(root); |
| |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription.getObjectChanges(); |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject object = entry.getKey(); |
| // persist every change |
| Element persistedElement = persist(document, entry, object); |
| if (persistedElement != null) { |
| // append the change to the document |
| root.appendChild(persistedElement); |
| } |
| } |
| |
| return document; |
| } |
| |
| /** |
| * Returns an XML representation of the changes that have occurred in the specified object. Or |
| * <code>null</code> if changes pertaining to this object should not be persisted |
| * |
| * @param document |
| * the root XML document |
| * @param entry |
| * a list of changes that have occurred to the object |
| * @param object |
| * the object to persist |
| * @return an XML representation of the changes of the object, or <code>null</code> if its |
| * changes should be ignored |
| */ |
| private Element persist(Document document, Entry<EObject, EList<FeatureChange>> entry, |
| EObject object) { |
| if (object instanceof Entry<?, ?>) { |
| return persistEntry(document, object); |
| } |
| return persistObject(document, entry, object); |
| } |
| |
| private Element persistObject(Document document, Entry<EObject, EList<FeatureChange>> entry, |
| EObject object) { |
| if (getOriginalId(object) == null) { |
| return null; |
| } |
| |
| Element modelChange = null; |
| |
| List<FeatureChange> featureChanges = entry.getValue(); |
| for (FeatureChange featureChange : featureChanges) { |
| // we only want to persist deltas of non-transient features |
| if (!featureChange.getFeature().isTransient()) { |
| String featureName = featureChange.getFeatureName(); |
| // check if we're interested in this particular feature |
| if (shouldPersist(featureName)) { |
| if (modelChange == null) { |
| // create an element to record this object's changes |
| modelChange = createElement(document, object); |
| } |
| |
| // create a delta for this particular change |
| Element deltaElement = createDeltaElement(document, object, featureChange, |
| featureName); |
| modelChange.appendChild(deltaElement); |
| } |
| } |
| } |
| |
| return modelChange; |
| } |
| |
| private String getEntryElementName(EObject object, Entry<?, ?> entry) { |
| if (object instanceof MContext) { |
| for (Entry<String, String> property : ((MContext) object).getProperties().entrySet()) { |
| if (property == entry) { |
| return CONTEXT_PROPERTIES_ATTNAME; |
| } |
| } |
| } |
| |
| if (object instanceof MContribution) { |
| for (Entry<String, String> state : ((MContribution) object).getPersistedState() |
| .entrySet()) { |
| if (state == entry) { |
| return APPLICATIONELEMENT_PERSISTEDSTATE_ATTNAME; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private Element persistEntry(Document document, EObject object) { |
| EObject container = object.eContainer(); |
| if (getOriginalId(container) == null) { |
| return null; |
| } |
| |
| Entry<?, ?> entry = (Entry<?, ?>) object; |
| String elementName = getEntryElementName(container, entry); |
| if (elementName == null) { |
| return null; |
| } |
| |
| Element element = createElement(document, elementName, container); |
| element.setAttribute(ENTRY_ATTVALUE_KEY, (String) entry.getKey()); |
| |
| String value = (String) entry.getValue(); |
| if (value == null) { |
| element.setAttribute(UNSET_ATTNAME, UNSET_ATTVALUE_TRUE); |
| } else { |
| element.setAttribute(ENTRY_ATTVALUE_VALUE, (String) entry.getValue()); |
| } |
| |
| return element; |
| } |
| |
| private Element createElement(Document document, EObject object) { |
| Class<?> rootInterface = object.getClass().getInterfaces()[0]; |
| // this technically doesn't have to be tagged with this name, it's not parsed, but makes the |
| // XML more readable |
| return createElement(document, rootInterface.getCanonicalName(), object); |
| } |
| |
| private Element createElement(Document document, String elementName, EObject object) { |
| Element modelChange = document.createElement(elementName); |
| modelChange.setAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME, getOriginalId(object)); |
| return modelChange; |
| } |
| |
| /** |
| * Retrieves the id of the object by querying for it from the resource. |
| * |
| * @param object |
| * the object to retrieve the id for |
| * @param container |
| * the parent container of the object |
| * @return the object's id as recorded by the containing resource |
| */ |
| private static String getResourceId(EObject object, EObject container) { |
| Resource resource = object.eResource(); |
| if (resource instanceof XMLResource) { |
| return ((XMLResource) resource).getID(object); |
| } |
| |
| resource = container.eResource(); |
| if (resource instanceof XMLResource) { |
| return ((XMLResource) resource).getID(object); |
| } |
| |
| throw new IllegalStateException(object + " could not be identified"); //$NON-NLS-1$ |
| } |
| |
| private String findResourceId(EObject object, EObject container) { |
| Resource resource = object.eResource(); |
| if (resource instanceof XMLResource) { |
| return ((XMLResource) resource).getID(object); |
| } |
| |
| resource = container.eResource(); |
| if (resource instanceof XMLResource) { |
| return ((XMLResource) resource).getID(object); |
| } |
| |
| if (originalObjects.containsKey(object)) { |
| resource = rootObject.eResource(); |
| if (resource instanceof XMLResource) { |
| return ((XMLResource) resource).getID(object); |
| } |
| } |
| |
| throw new IllegalStateException(object + " could not be identified"); //$NON-NLS-1$ |
| } |
| |
| private static String getLocalId(Object object) { |
| EObject reference = (EObject) object; |
| return getResourceId(reference, reference.eContainer()); |
| } |
| |
| /** |
| * Retrieves the original containing parent object of the specified reference. If |
| * <code>null</code> is returned, the object was not initially known by the change recorder. |
| * |
| * @param reference |
| * the object to find its original container for |
| * @return the original parent container of the object, or <code>null</code> if it did not |
| * initially exist |
| */ |
| private EObject getOriginalContainer(EObject reference) { |
| if (changeDescription == null) { |
| // no changes have been recorded, just ask the container through EMF directly |
| return reference.eContainer(); |
| } else if (!originalObjects.containsKey(reference)) { |
| return null; |
| } |
| |
| if (reference instanceof MCommandParameter) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean parametersChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MCommand) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(COMMAND_PARAMETERS_ATTNAME)) { |
| List<?> parameters = (List<?>) change.getValue(); |
| for (Object parameter : parameters) { |
| if (parameter == reference) { |
| return key; |
| } |
| } |
| |
| parametersChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| return parametersChanged ? null : reference.eContainer(); |
| } |
| |
| if (reference instanceof MParameter) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean parametersChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MHandledItem) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(HANDLEDITEM_PARAMETERS_ATTNAME)) { |
| List<?> parameters = (List<?>) change.getValue(); |
| for (Object parameter : parameters) { |
| if (parameter == reference) { |
| return key; |
| } |
| } |
| |
| parametersChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| return parametersChanged ? null : reference.eContainer(); |
| } |
| |
| if (reference instanceof MCommand) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean commandsChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == rootObject) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(APPLICATION_COMMANDS_ATTNAME)) { |
| List<?> commands = (List<?>) change.getValue(); |
| for (Object command : commands) { |
| if (command == reference) { |
| return key; |
| } |
| } |
| |
| commandsChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| if (!commandsChanged) { |
| return reference.eContainer(); |
| } |
| } |
| |
| if (reference instanceof MMenuContribution) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean menuContributionsChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == rootObject) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals( |
| MENUCONTRIBUTIONS_MENUCONTRIBUTIONS_ATTNAME)) { |
| List<?> commands = (List<?>) change.getValue(); |
| for (Object command : commands) { |
| if (command == reference) { |
| return key; |
| } |
| } |
| |
| menuContributionsChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| return menuContributionsChanged ? null : reference.eContainer(); |
| } |
| |
| if (reference instanceof MToolBarContribution) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean toolBarContributionsChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == rootObject) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals( |
| TOOLBARCONTRIBUTIONS_TOOLBARCONTRIBUTIONS_ATTNAME)) { |
| List<?> commands = (List<?>) change.getValue(); |
| for (Object command : commands) { |
| if (command == reference) { |
| return key; |
| } |
| } |
| |
| toolBarContributionsChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| return toolBarContributionsChanged ? null : reference.eContainer(); |
| } |
| |
| if (reference instanceof MTrimContribution) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean toolBarContributionsChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == rootObject) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals( |
| TRIMCONTRIBUTIONS_TRIMCONTRIBUTIONS_ATTNAME)) { |
| List<?> commands = (List<?>) change.getValue(); |
| for (Object command : commands) { |
| if (command == reference) { |
| return key; |
| } |
| } |
| |
| toolBarContributionsChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| return toolBarContributionsChanged ? null : reference.eContainer(); |
| } |
| |
| if (reference instanceof MBindingTable) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean bindingTablesChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == rootObject) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(BINDINGCONTAINER_BINDINGTABLES_ATTNAME)) { |
| List<?> commands = (List<?>) change.getValue(); |
| for (Object command : commands) { |
| if (command == reference) { |
| return key; |
| } |
| } |
| |
| bindingTablesChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| if (!bindingTablesChanged) { |
| return reference.eContainer(); |
| } |
| } |
| |
| if (reference instanceof MHandler) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean handlersChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MHandlerContainer) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(HANDLERCONTAINER_HANDLERS_ATTNAME)) { |
| List<?> commands = (List<?>) change.getValue(); |
| for (Object command : commands) { |
| if (command == reference) { |
| return key; |
| } |
| } |
| |
| handlersChanged = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (!handlersChanged) { |
| return reference.eContainer(); |
| } |
| } |
| |
| if (reference instanceof MKeyBinding) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| |
| boolean bindingsChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MBindingTable) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(BINDINGTABLE_BINDINGS_ATTNAME)) { |
| List<?> commands = (List<?>) change.getValue(); |
| for (Object command : commands) { |
| if (command == reference) { |
| return key; |
| } |
| } |
| |
| bindingsChanged = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (!bindingsChanged) { |
| return reference.eContainer(); |
| } |
| } |
| |
| if (reference instanceof MPartDescriptor) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| boolean descriptorsChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == rootObject) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals( |
| PARTDESCRIPTORCONTAINER_DESCRIPTORS_ATTNAME)) { |
| List<?> descriptors = (List<?>) change.getValue(); |
| for (Object descriptor : descriptors) { |
| if (descriptor == reference) { |
| return key; |
| } |
| } |
| descriptorsChanged = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| return descriptorsChanged ? null : reference.eContainer(); |
| } |
| |
| if (reference instanceof MTrimBar) { |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription |
| .getObjectChanges(); |
| boolean trimBarsChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MTrimmedWindow) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(TRIMMEDWINDOW_TRIMBARS_ATTNAME)) { |
| List<?> trimBars = (List<?>) change.getValue(); |
| for (Object trimBar : trimBars) { |
| if (trimBar == reference) { |
| return key; |
| } |
| } |
| trimBarsChanged = true; |
| break; |
| } |
| } |
| |
| if (trimBarsChanged) { |
| break; |
| } |
| } |
| } |
| |
| if (trimBarsChanged) { |
| return null; |
| } |
| |
| for (EObject rootChild : rootObject.eContents()) { |
| if (rootChild instanceof MTrimmedWindow) { |
| if (((MTrimmedWindow) rootChild).getTrimBars().contains(reference)) { |
| return rootChild; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription.getObjectChanges(); |
| if (reference instanceof MUIElement) { |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == reference) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(UIELEMENT_PARENT_ATTNAME)) { |
| return (EObject) change.getValue(); |
| } |
| } |
| break; |
| } |
| } |
| |
| if (reference instanceof MMenu) { |
| boolean appendedMenu = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MPart) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(PART_MENUS_ATTNAME)) { |
| List<?> originalMenus = (List<?>) change.getValue(); |
| if (originalMenus.contains(reference)) { |
| return key; |
| } |
| |
| if (((MPart) key).getMenus().contains(reference)) { |
| appendedMenu = true; |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| boolean menuSet = false; |
| boolean menuChanged = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MWindow) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(WINDOW_MAINMENU_ATTNAME)) { |
| Object oldMenu = change.getValue(); |
| if (oldMenu == reference) { |
| return key; |
| } else if (oldMenu == null |
| && ((MWindow) key).getMainMenu() == reference) { |
| menuSet = true; |
| } |
| menuChanged = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (menuChanged && menuSet) { |
| return null; |
| } else if (appendedMenu && !menuSet && !menuChanged) { |
| return null; |
| } |
| |
| EObject container = reference.eContainer(); |
| if (!(container instanceof MMenu)) { |
| return container; |
| } |
| } |
| |
| if (reference instanceof MToolBar) { |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MPart) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(PART_TOOLBAR_ATTNAME)) { |
| if (change.getValue() == reference) { |
| return key; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| boolean newElement = false; |
| |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key == reference.eContainer()) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(ELEMENTCONTAINER_CHILDREN_ATTNAME)) { |
| EList<?> value = (EList<?>) change.getValue(); |
| if (value.contains(reference)) { |
| return key; |
| } |
| newElement = true; |
| break; |
| } else if (change.getFeatureName().equals(PART_TOOLBAR_ATTNAME)) { |
| if (reference.equals(change.getValue())) { |
| return key; |
| } |
| newElement = true; |
| break; |
| } else if (change.getFeatureName().equals(WINDOW_SHAREDELEMENTS_ATTNAME)) { |
| EList<?> value = (EList<?>) change.getValue(); |
| if (value.contains(reference)) { |
| return key; |
| } |
| newElement = true; |
| break; |
| } else if (change.getFeatureName().equals(PERSPECTIVE_WINDOWS_ATTNAME)) { |
| EList<?> value = (EList<?>) change.getValue(); |
| if (value.contains(reference)) { |
| return key; |
| } |
| newElement = true; |
| break; |
| } |
| } |
| break; |
| } |
| |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(WINDOW_SHAREDELEMENTS_ATTNAME)) { |
| EList<?> value = (EList<?>) change.getValue(); |
| if (value.contains(reference)) { |
| return key; |
| } |
| } |
| } |
| } |
| |
| if (reference instanceof MWindow) { |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MPerspective) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(PERSPECTIVE_WINDOWS_ATTNAME)) { |
| EList<?> value = (EList<?>) change.getValue(); |
| if (value.contains(reference)) { |
| return key; |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| if (!newElement) { |
| return reference instanceof MApplication ? changeDescription : reference |
| .eContainer(); |
| } |
| } |
| |
| if (reference instanceof MAddon) { |
| for (Entry<EObject, EList<FeatureChange>> entry : objectChanges.entrySet()) { |
| EObject key = entry.getKey(); |
| if (key instanceof MApplication) { |
| for (FeatureChange change : entry.getValue()) { |
| if (change.getFeatureName().equals(APPLICATION_ADDONS_ATTNAME)) { |
| EList<?> value = (EList<?>) change.getValue(); |
| return value.contains(reference) ? key : null; |
| } |
| } |
| } |
| } |
| |
| return reference.eContainer(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Computes and returns the original id of the specified object. The meaning of the term |
| * "original" is defined as the state of the object prior to having its state monitored via |
| * {@link #recordChanges(Object)}. The identifier for the object will be queried from the |
| * resource. If the object did not exist was not known when changes began recording, |
| * <code>null</code> will be returned. |
| * |
| * @param object |
| * the object to query an id for |
| * @return an id suitable for looking up the object, or <code>null</code> if the object did not |
| * exist or was not known within the scope of the originally monitored object |
| */ |
| private String getOriginalId(Object object) { |
| EObject reference = (EObject) object; |
| EObject originalContainer = getOriginalContainer(reference); |
| |
| // did not exist in the model originally |
| if (originalContainer == null) { |
| return null; |
| } |
| |
| if (originalContainer != changeDescription) { |
| EObject container = originalContainer; |
| while (container != rootObject) { |
| container = getOriginalContainer(container); |
| if (container == null) { |
| return null; |
| } |
| } |
| } |
| |
| return findResourceId(reference, originalContainer); |
| } |
| |
| /** |
| * Creates an XML element that will capture the delta that occurred in the object. |
| * |
| * @param document |
| * the root XML document |
| * @param object |
| * the object that has changed |
| * @param featureChange |
| * the captured information about the change |
| * @param featureName |
| * the name of the feature that has changed |
| * @return the XML element about the change |
| */ |
| private Element createDeltaElement(Document document, EObject object, |
| FeatureChange featureChange, String featureName) { |
| EStructuralFeature feature = featureChange.getFeature(); |
| if (object.eIsSet(feature)) { |
| return createSetDeltaElement(document, object, featureChange, featureName, feature); |
| } |
| |
| return createUnsetDeltaElement(document, featureChange, featureName); |
| } |
| |
| private Element createSetDeltaElement(Document document, EObject object, |
| FeatureChange featureChange, String featureName, EStructuralFeature feature) { |
| Element featureElement = document.createElement(featureName); |
| Object value = object.eGet(feature); |
| if (isSingleReference(featureName)) { |
| // record what we're currently referencing, we create an element instead of simply |
| // recording the id because we need to describe the entire object if the object is new, |
| // simply recording an id would be insufficient for recording the attributes of the new |
| // object |
| Element referenceElement = createReferenceElement(document, (EObject) value, |
| featureName); |
| featureElement.appendChild(referenceElement); |
| } else if (isChainedReference(featureName)) { |
| // record what we're currently referencing |
| appendReferenceElements(document, featureElement, (List<?>) value); |
| // record what was originally referenced |
| appendOriginalReferenceElements(document, featureElement, |
| (List<?>) featureChange.getValue()); |
| } else if (isUnorderedChainedAttribute(featureName)) { |
| appendUnorderedChainedAttributeElements(document, (List<?>) value, featureName, |
| featureElement); |
| } else if (isStringToStringMap(featureName)) { |
| appendStringToStringMapElements(document, featureElement, object, feature, featureName); |
| } else { |
| featureElement.setAttribute(featureName, String.valueOf(value)); |
| } |
| return featureElement; |
| } |
| |
| private Element createUnsetDeltaElement(Document document, FeatureChange featureChange, |
| String featureName) { |
| Element featureElement = document.createElement(featureName); |
| if (isChainedReference(featureName)) { |
| appendOriginalReferenceElements(document, featureElement, |
| (List<?>) featureChange.getValue()); |
| } else { |
| featureElement.setAttribute(UNSET_ATTNAME, UNSET_ATTVALUE_TRUE); |
| } |
| return featureElement; |
| } |
| |
| /** |
| * Creates an element to describe that the specified object was a reference that was originally |
| * there when the application started prior to the applying of any deltas. It should be noted |
| * that an object that was originally referenced will not necessarily be dereferenced after any |
| * deltas have been applied. |
| * |
| * @param document |
| * the root XML document |
| * @param reference |
| * the referenced object |
| * @return an XML element to record that the specified object was originally referenced by |
| * another object when the application started |
| */ |
| private Element createOriginalReferenceElement(Document document, Object reference) { |
| Element referenceElement = document.createElement(ORIGINALREFERENCE_ELEMENT_NAME); |
| referenceElement.setAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME, |
| getOriginalId(reference)); |
| return referenceElement; |
| } |
| |
| private void appendOriginalReferenceElements(Document document, Element element, |
| List<?> references) { |
| for (Object reference : references) { |
| Element referenceElement = createOriginalReferenceElement(document, reference); |
| element.appendChild(referenceElement); |
| } |
| } |
| |
| /** |
| * Creates a new element that defines a reference to another object. |
| * |
| * @param document |
| * the root XML document |
| * @param eObject |
| * the object to reference |
| * @param featureName |
| * the name of the feature describing the reference relationship, if |
| * <code>null</code>, it is assumed to be a direct reference |
| * @return an XML element that describes the reference to the provided object |
| * @see #isDirectReference(String) |
| * @see #isIndirectReference(String) |
| */ |
| private Element createReferenceElement(Document document, EObject eObject, String featureName) { |
| String id = getOriginalId(eObject); |
| |
| if (id == null) { |
| if (featureName == null || isDirectReference(featureName)) { |
| // didn't exist originally, we need a completely new reference that describes the |
| // object and its attributes |
| return createNewReferenceElement(document, eObject); |
| } |
| return createUniqueReferenceElement(document, eObject); |
| } |
| |
| Element referenceElement = document.createElement(REFERENCE_ELEMENT_NAME); |
| referenceElement.setAttribute(APPLICATIONELEMENT_ELEMENTID_ATTNAME, id); |
| return referenceElement; |
| } |
| |
| private void appendReferenceElements(Document document, Element element, List<?> references) { |
| for (Object reference : references) { |
| Element ef = createReferenceElement(document, (EObject) reference, null); |
| element.appendChild(ef); |
| } |
| } |
| |
| private Element createNewReferenceElement(Document document, EObject eObject) { |
| Element referenceElement = createUniqueReferenceElement(document, eObject); |
| |
| // object did not exist, mark it as such so it can be created during the |
| // when the deltas are applied |
| referenceElement.setAttribute(UNSET_ATTNAME, UNSET_ATTVALUE_TRUE); |
| // note what we need to create by storing the type |
| referenceElement.setAttribute(TYPE_ATTNAME, |
| eObject.getClass().getInterfaces()[0].getCanonicalName()); |
| |
| for (EStructuralFeature collectedFeature : collectFeatures(eObject)) { |
| String featureName = collectedFeature.getName(); |
| // ignore transient features and features that we are not interested in |
| if (!collectedFeature.isTransient() && shouldPersist(featureName)) { |
| Element referenceAttributeElement = createAttributeElement(document, eObject, |
| collectedFeature, featureName); |
| referenceElement.appendChild(referenceAttributeElement); |
| } |
| } |
| |
| return referenceElement; |
| } |
| |
| private Element createUniqueReferenceElement(Document document, EObject eObject) { |
| EClass cls = eObject.eClass(); |
| EPackage pkg = (EPackage) cls.eContainer(); |
| |
| Element referenceElement = document.createElement(REFERENCE_ELEMENT_NAME); |
| |
| E4XMIResource resource = (E4XMIResource) rootObject.eResource(); |
| String internalId = resource.getInternalId(eObject); |
| referenceElement.setAttribute(XMIID_ATTNAME, internalId); |
| referenceElement.setAttribute(NAMESPACE_ATTNAME, pkg.getNsURI()); |
| |
| return referenceElement; |
| } |
| |
| /** |
| * Creates an element that describes the state of the feature in the specified object. |
| * |
| * @param document |
| * the root XML element document |
| * @param object |
| * the object to describe |
| * @param feature |
| * the feature of interest |
| * @param featureName |
| * the name of the feature |
| * @return an XML element that describes the feature's value in the provided object |
| */ |
| private Element createAttributeElement(Document document, EObject object, |
| EStructuralFeature feature, String featureName) { |
| Element referenceAttributeElement = document.createElement(featureName); |
| if (object.eIsSet(feature)) { |
| if (isSingleReference(featureName)) { |
| Object value = object.eGet(feature); |
| String id = getOriginalId(value); |
| if (id == null) { |
| Element referenceElement = createReferenceElement(document, (EObject) value, |
| featureName); |
| referenceAttributeElement.appendChild(referenceElement); |
| } else { |
| referenceAttributeElement.setAttribute(featureName, id); |
| } |
| } else if (isChainedReference(featureName)) { |
| List<?> references = (List<?>) object.eGet(feature); |
| appendReferenceElements(document, referenceAttributeElement, references); |
| } else if (isUnorderedChainedAttribute(featureName)) { |
| appendUnorderedChainedAttributeElements(document, (List<?>) object.eGet(feature), |
| featureName, referenceAttributeElement); |
| } else if (isStringToStringMap(featureName)) { |
| appendStringToStringMapElements(document, referenceAttributeElement, object, |
| feature, featureName); |
| } else { |
| referenceAttributeElement.setAttribute(featureName, |
| String.valueOf(object.eGet(feature))); |
| } |
| } else { |
| referenceAttributeElement.setAttribute(UNSET_ATTNAME, UNSET_ATTVALUE_TRUE); |
| } |
| return referenceAttributeElement; |
| } |
| |
| private void appendUnorderedChainedAttributeElements(Document document, List<?> attributes, |
| String featureName, Element referenceAttributeElement) { |
| // iterate over all the attributes |
| for (Object attribute : attributes) { |
| // create a new element for each attribute |
| Element attributeElement = document.createElement(featureName); |
| // set the attribute's value into the element |
| attributeElement.setAttribute(featureName, String.valueOf(attribute)); |
| // append the element to the parent |
| referenceAttributeElement.appendChild(attributeElement); |
| } |
| } |
| |
| private void appendStringToStringMapElements(Document document, Element parentElement, |
| EObject object, EStructuralFeature feature, String featureName) { |
| EMap<?, ?> map = (EMap<?, ?>) object.eGet(feature); |
| // iterate over the map |
| for (Entry<?, ?> entry : map.entrySet()) { |
| // create a new element for each entry in the map |
| Element entryElement = document.createElement(featureName); |
| // set the string keys and values into the element |
| entryElement.setAttribute(ENTRY_ATTVALUE_KEY, (String) entry.getKey()); |
| entryElement.setAttribute(ENTRY_ATTVALUE_VALUE, (String) entry.getValue()); |
| // append the element to the parent |
| parentElement.appendChild(entryElement); |
| } |
| } |
| |
| private static boolean isStringToStringMap(String featureName) { |
| return featureName.equals(APPLICATIONELEMENT_PERSISTEDSTATE_ATTNAME) |
| || featureName.equals(CONTEXT_PROPERTIES_ATTNAME); |
| } |
| |
| /** |
| * Determines whether this feature is a direct reference to a particular object. That is, the |
| * reference is a 1-1 relationship and the referenced object is not referred by other objects |
| * and is a hard containment feature. |
| * <p> |
| * An example of a direct reference would be a window's main menu. The menu is "owned" by the |
| * window and cannot be one of the menus in a part. |
| * </p> |
| * |
| * @param featureName |
| * the name of the interested feature |
| * @return <code>true</code> if this particular feature directly references and "owns" the |
| * target object, <code>false</code> otherwise |
| * @see #isIndirectReference(String) |
| */ |
| private static boolean isDirectReference(String featureName) { |
| // a Window has a single reference to a menu |
| return featureName.equals(WINDOW_MAINMENU_ATTNAME) || |
| // a Part has a single reference to a tool bar |
| featureName.equals(PART_TOOLBAR_ATTNAME) || |
| // a UIElement has a single reference to a visibleWhen |
| featureName.equals(UIELEMENT_VISIBLEWHEN_ATTNAME); |
| } |
| |
| /** |
| * Determines whether this feature is an indirect reference to a particular object. That is, the |
| * reference refers to an object and this reference does not necessarily describe containment. |
| * <p> |
| * An example of an indirect reference would be a handler's command. The handler points to a |
| * command but the command is actually "owned" by an application in its list of commands. In |
| * this case, there is no containment involved between the handler and the command. |
| * </p> |
| * <p> |
| * Another example of an indirect reference would be a element container's active child. In this |
| * case, the element container's contains the active child in its list of children and both this |
| * listing and the active child reference is a containment feature. |
| * </p> |
| * |
| * @param featureName |
| * the name of the interested feature |
| * @return <code>true</code> if this particular feature directly references and "owns" the |
| * target object, <code>false</code> otherwise |
| * @see #isDirectReference(String) |
| */ |
| private static boolean isIndirectReference(String featureName) { |
| // an ElementContainer has a single reference to its active child |
| return featureName.equals(ELEMENTCONTAINER_SELECTEDELEMENT_ATTNAME) || |
| // a Handler has a single reference to a command |
| featureName.equals(HANDLER_COMMAND_ATTNAME) || |
| // a KeyBinding has a single reference to a command |
| featureName.equals(KEYBINDING_COMMAND_ATTNAME) || |
| // a Placeholder has a single reference to a ui element |
| featureName.equals(PLACEHOLDER_REF_NAME) || |
| // a BindingTable has a single reference to a bindingContext |
| featureName.equals(BINDINGTABLE_BINDINGCONTEXT_ATTNAME); |
| } |
| |
| /** |
| * Returns whether the feature is a reference to another object. |
| * |
| * @param featureName |
| * the name of the feature |
| * @return <code>true</code> if this particular feature is a reference to another object, |
| * <code>false</code> otherwise |
| */ |
| private static boolean isSingleReference(String featureName) { |
| return isDirectReference(featureName) || isIndirectReference(featureName); |
| } |
| |
| /** |
| * Returns whether the feature is a list of object references. |
| * |
| * @param featureName |
| * the name of the feature |
| * @return <code>true</code> if this particular feature is referring to a list of object |
| * references, <code>false</code> otherwise |
| */ |
| private static boolean isChainedReference(String featureName) { |
| // an ElementContainer has multiple children |
| return featureName.equals(ELEMENTCONTAINER_CHILDREN_ATTNAME) || |
| // a BindingContainer has multiple bindings |
| featureName.equals(BINDINGTABLE_BINDINGS_ATTNAME) || |
| // a Part has multiple menus |
| featureName.equals(PART_MENUS_ATTNAME) || |
| // an Application has multiple commands |
| featureName.equals(APPLICATION_COMMANDS_ATTNAME) || |
| // a SnippetContainer has multiple snippets |
| featureName.equals(SNIPPETCONTAINER_SNIPPETS_ATTNAME) || |
| // a HandlerContainer has multiple handlers |
| featureName.equals(HANDLERCONTAINER_HANDLERS_ATTNAME) || |
| // a BindingContainer has multiple binding tables |
| featureName.equals(BINDINGCONTAINER_BINDINGTABLES_ATTNAME) || |
| // a TrimmedWindow has multiple trim bars |
| featureName.equals(TRIMMEDWINDOW_TRIMBARS_ATTNAME) || |
| // a Window has multiple shared elements |
| featureName.equals(WINDOW_SHAREDELEMENTS_ATTNAME) || |
| // a MenuContributions has multiple menu contributions |
| featureName.equals(MENUCONTRIBUTIONS_MENUCONTRIBUTIONS_ATTNAME) || |
| // a Command has multiple (command) parameters |
| featureName.equals(COMMAND_PARAMETERS_ATTNAME) || |
| // a HandledItem has multiple parameters |
| featureName.equals(HANDLEDITEM_PARAMETERS_ATTNAME) || |
| // a ToolBarContributions has multiple tool bar contributions |
| featureName.equals(TOOLBARCONTRIBUTIONS_TOOLBARCONTRIBUTIONS_ATTNAME) || |
| // a TrimContributions has multiple trim contributions |
| featureName.equals(TRIMCONTRIBUTIONS_TRIMCONTRIBUTIONS_ATTNAME) || |
| // a Perspective has multiple windows |
| featureName.equals(PERSPECTIVE_WINDOWS_ATTNAME) || |
| // a Binding has multiple binding contexts |
| featureName.equals(BINDINGS_BINDINGCONTEXTS_ATTNAME) || |
| // a BindingContainer has multiple root contexts |
| featureName.equals(BINDINGCONTAINER_ROOTCONTEXT_ATTNAME); |
| } |
| |
| private static boolean isUnorderedChainedAttribute(String featureName) { |
| return featureName.equals(APPLICATIONELEMENT_TAGS_ATTNAME); |
| } |
| |
| /** |
| * Returns whether this feature should be persisted. |
| * |
| * @param featureName |
| * the name of the feature |
| * @return <code>true</code> if this particular feature should be persisted, <code>false</code> |
| * otherwise |
| */ |
| private static boolean shouldPersist(String featureName) { |
| // parent changes are captured by children changes already |
| return !featureName.equals(UIELEMENT_PARENT_ATTNAME) |
| && !featureName.equals(PARTDESCRIPTORCONTAINER_DESCRIPTORS_ATTNAME); |
| } |
| |
| private static Collection<EStructuralFeature> collectFeatures( |
| Collection<EStructuralFeature> features, EClass eClass) { |
| features.addAll(eClass.getEStructuralFeatures()); |
| for (EClass superType : eClass.getESuperTypes()) { |
| collectFeatures(features, superType); |
| } |
| return features; |
| } |
| |
| private static Collection<EStructuralFeature> collectFeatures(EObject object) { |
| return collectFeatures(new HashSet<EStructuralFeature>(), object.eClass()); |
| } |
| |
| private static boolean isUnset(Element element) { |
| return UNSET_ATTVALUE_TRUE.equals(element.getAttribute(UNSET_ATTNAME)); |
| } |
| |
| } |