blob: 88b0b31c12d4909d8ef8c4fd53fab1444bba942a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2010 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.workbench.ui.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.e4.ui.model.application.ItemType;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.MApplicationElement;
import org.eclipse.e4.ui.model.application.MApplicationFactory;
import org.eclipse.e4.ui.model.application.MApplicationPackage;
import org.eclipse.e4.ui.model.application.MBindingContainer;
import org.eclipse.e4.ui.model.application.MCommand;
import org.eclipse.e4.ui.model.application.MDirectMenuItem;
import org.eclipse.e4.ui.model.application.MDirectToolItem;
import org.eclipse.e4.ui.model.application.MElementContainer;
import org.eclipse.e4.ui.model.application.MHandledMenuItem;
import org.eclipse.e4.ui.model.application.MHandledToolItem;
import org.eclipse.e4.ui.model.application.MHandler;
import org.eclipse.e4.ui.model.application.MHandlerContainer;
import org.eclipse.e4.ui.model.application.MKeyBinding;
import org.eclipse.e4.ui.model.application.MMenu;
import org.eclipse.e4.ui.model.application.MMenuItem;
import org.eclipse.e4.ui.model.application.MPart;
import org.eclipse.e4.ui.model.application.MPartDescriptor;
import org.eclipse.e4.ui.model.application.MPartDescriptorContainer;
import org.eclipse.e4.ui.model.application.MPartSashContainer;
import org.eclipse.e4.ui.model.application.MPartStack;
import org.eclipse.e4.ui.model.application.MPerspective;
import org.eclipse.e4.ui.model.application.MPerspectiveStack;
import org.eclipse.e4.ui.model.application.MToolBar;
import org.eclipse.e4.ui.model.application.MToolItem;
import org.eclipse.e4.ui.model.application.MUIElement;
import org.eclipse.e4.ui.model.application.MWindow;
import org.eclipse.e4.ui.model.application.MWindowTrim;
import org.eclipse.e4.ui.model.application.SideValue;
import org.eclipse.e4.workbench.modeling.IDelta;
import org.eclipse.e4.workbench.modeling.ModelDelta;
import org.eclipse.e4.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.EObject;
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$
/**
* The name of the root element that describes the model deltas in XML form (value is
* <code>changes</code>).
*/
private static final String CHANGES_ATTNAME = "changes"; //$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 ChangeRecorder changeRecorder;
private ChangeDescription changeDescription;
private EObject rootObject;
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;
}
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>();
NodeList rootNodeList = (NodeList) document.getDocumentElement();
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_ID_ATTNAME));
}
}
return deltas;
}
private static EStructuralFeature getStructuralFeature(EObject object, String featureName) {
if (featureName.equals(APPLICATIONELEMENT_ID_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getApplicationElement_Id();
} else if (featureName.equals(APPLICATIONELEMENT_TAGS_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getApplicationElement_Tags();
} else if (featureName.equals(APPLICATION_COMMANDS_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getApplication_Commands();
} else if (featureName.equals(UILABEL_LABEL_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getUILabel_Label();
} else if (featureName.equals(UILABEL_TOOLTIP_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getUILabel_Tooltip();
} else if (featureName.equals(UILABEL_ICONURI_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getUILabel_IconURI();
} else if (featureName.equals(UIELEMENT_TOBERENDERED_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getUIElement_ToBeRendered();
} else if (featureName.equals(UIELEMENT_VISIBLE_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getUIElement_Visible();
} else if (featureName.equals(ELEMENTCONTAINER_CHILDREN_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getElementContainer_Children();
} else if (featureName.equals(UIELEMENT_PARENT_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getUIElement_Parent();
} else if (featureName.equals(UIELEMENT_CONTAINERDATA_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getUIElement_ContainerData();
} else if (featureName.equals(ELEMENTCONTAINER_SELECTEDELEMENT_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getElementContainer_SelectedElement();
} else if (featureName.equals(COMMAND_COMMANDNAME_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getCommand_CommandName();
} else if (featureName.equals(COMMAND_DESCRIPTION_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getCommand_Description();
} else if (featureName.equals(KEYSEQUENCE_KEYSEQUENCE_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getKeySequence_KeySequence();
} else if (featureName.equals(BINDINGCONTAINER_BINDINGS_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getBindingContainer_Bindings();
} else if (featureName.equals(HANDLER_COMMAND_ATTNAME)
|| featureName.equals(KEYBINDING_COMMAND_ATTNAME)
|| featureName.equals(HANDLEDITEM_COMMAND_ATTNAME)) {
// technically all three names are the same
if (object instanceof MKeyBinding) {
return MApplicationPackage.eINSTANCE.getKeyBinding_Command();
} else if (object instanceof MHandler) {
return MApplicationPackage.eINSTANCE.getHandler_Command();
}
return MApplicationPackage.eINSTANCE.getHandledItem_Command();
} else if (featureName.equals(COMMAND_PARAMETERS_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getKeyBinding_Parameters();
} else if (featureName.equals(ITEM_ENABLED_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getItem_Enabled();
} else if (featureName.equals(ITEM_SELECTED_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getItem_Selected();
} else if (featureName.equals(ITEM_TYPE_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getItem_Type();
} else if (featureName.equals(PART_MENUS_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getPart_Menus();
} else if (featureName.equals(PART_TOOLBAR_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getPart_Toolbar();
} else if (featureName.equals(GENERICTILE_HORIZONTAL_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getGenericTile_Horizontal();
} else if (featureName.equals(TRIMCONTAINER_SIDE_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getTrimContainer_Side();
} else if (featureName.equals(HANDLERCONTAINER_HANDLERS_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getHandlerContainer_Handlers();
} else if (featureName.equals(CONTRIBUTION_PERSISTEDSTATE_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getContribution_PersistedState();
} else if (featureName.equals(CONTRIBUTION_URI_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getContribution_URI();
} else if (featureName.equals(WINDOW_MAINMENU_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getWindow_MainMenu();
} else if (featureName.equals(WINDOW_X_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getWindow_X();
} else if (featureName.equals(WINDOW_Y_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getWindow_Y();
} else if (featureName.equals(WINDOW_WIDTH_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getWindow_Width();
} else if (featureName.equals(WINDOW_HEIGHT_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getWindow_Height();
} else if (featureName.equals(PARTDESCRIPTOR_ALLOWMULTIPLE_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getPartDescriptor_AllowMultiple();
} else if (featureName.equals(PARTDESCRIPTOR_CATEGORY_ATTNAME)) {
return MApplicationPackage.eINSTANCE.getPartDescriptor_Category();
}
Activator.log(IStatus.WARNING, "Unknown feature found, reconciliation may fail: " //$NON-NLS-1$
+ object.eClass().getName() + '#' + 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 == MApplicationPackage.eINSTANCE.getTrimContainer_Side()) {
return SideValue.getByName(featureValue);
} else if (feature == MApplicationPackage.eINSTANCE.getItem_Type()) {
return ItemType.getByName(featureValue);
}
return null;
}
static Object findReference(List<Object> references, String id) {
for (Object reference : references) {
if (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)) {
constructObjectDeltas(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 MBindingContainer) {
for (MKeyBinding keyBinding : ((MBindingContainer) 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;
}
}
}
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 MWindow) {
MWindow window = (MWindow) object;
if (constructDeltas(deltas, references, (EObject) window.getMainMenu(), element, id)) {
return true;
}
}
return false;
}
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 {
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_ID_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_ID_ATTNAME);
Object match = findReference(references, referenceId);
if (match == null) {
return createDelayedDelta(eObject, feature, reference);
}
return new EMFModelDeltaSet(eObject, feature, match);
}
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 (originalReferences.containsAll(currentReferences)
&& 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_ID_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 type) {
if (type.equals(MPart.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createPart();
} else if (type.equals(MCommand.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createCommand();
} else if (type.equals(MHandler.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createHandler();
} else if (type.equals(MKeyBinding.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createKeyBinding();
} else if (type.equals(MMenu.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createMenu();
} else if (type.equals(MWindow.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createWindow();
} else if (type.equals(MToolBar.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createToolBar();
} else if (type.equals(MToolItem.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createToolItem();
} else if (type.equals(MDirectToolItem.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createDirectToolItem();
} else if (type.equals(MHandledToolItem.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createHandledToolItem();
} else if (type.equals(MMenuItem.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createMenuItem();
} else if (type.equals(MDirectMenuItem.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createDirectMenuItem();
} else if (type.equals(MHandledMenuItem.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createHandledMenuItem();
} else if (type.equals(MPartStack.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createPartStack();
} else if (type.equals(MPartSashContainer.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createPartSashContainer();
} else if (type.equals(MWindowTrim.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createWindowTrim();
} else if (type.equals(MPerspective.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createPerspective();
} else if (type.equals(MPerspectiveStack.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createPerspectiveStack();
} else if (type.equals(MPartDescriptor.class.getSimpleName())) {
return (EObject) MApplicationFactory.eINSTANCE.createPartDescriptor();
}
return null;
}
private Object getReference(Collection<ModelDelta> deltas, Element element,
List<Object> references) {
String id = element.getAttribute(APPLICATIONELEMENT_ID_ATTNAME);
if (!id.equals("")) { //$NON-NLS-1$
return findReference(references, id);
}
return createObject(deltas, element, references);
}
private Object createObject(Collection<ModelDelta> deltas, String typeName, Element element,
List<Object> references) {
EObject object = createObject(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 {
object.eSet(attributeFeature, getValue(attributeFeature, item
.getAttribute(attributeName)));
}
}
}
}
}
return compositeDelta;
}
private Object createObject(Collection<ModelDelta> deltas, Element reference,
List<Object> references) {
return createObject(deltas, reference.getAttribute(TYPE_ATTNAME), reference, references);
}
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++) {
Element attribute = (Element) attributes.item(j);
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_ATTNAME);
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 (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 Element createElement(Document document, EObject object) {
String id = getOriginalId(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
Element modelChange = document.createElement(rootInterface.getSimpleName());
modelChange.setAttribute(APPLICATIONELEMENT_ID_ATTNAME, id);
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 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();
}
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 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 MBindingContainer) {
for (FeatureChange change : entry.getValue()) {
if (change.getFeatureName().equals(BINDINGCONTAINER_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 MUIElement) {
EMap<EObject, EList<FeatureChange>> objectChanges = changeDescription
.getObjectChanges();
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;
}
}
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;
}
}
break;
}
}
if (!newElement) {
return reference instanceof MApplication ? changeDescription : 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 getResourceId(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)) {
List<?> attributes = (List<?>) value;
for (Object attribute : attributes) {
Element attributeElement = document.createElement(featureName);
attributeElement.setAttribute(featureName, String.valueOf(attribute));
featureElement.appendChild(attributeElement);
}
} 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_ID_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_ID_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
// delta application phase
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]
.getSimpleName());
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) {
Element referenceElement = document.createElement(REFERENCE_ELEMENT_NAME);
E4XMIResource resource = (E4XMIResource) rootObject.eResource();
String internalId = resource.getInternalId(eObject);
referenceElement.setAttribute(XMIID_ATTNAME, internalId);
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 {
referenceAttributeElement.setAttribute(featureName, String.valueOf(object
.eGet(feature)));
}
} else {
referenceAttributeElement.setAttribute(UNSET_ATTNAME, UNSET_ATTVALUE_TRUE);
}
return referenceAttributeElement;
}
/**
* 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);
}
/**
* 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);
}
/**
* 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(BINDINGCONTAINER_BINDINGS_ATTNAME) ||
// a Part has multiple menus
featureName.equals(PART_MENUS_ATTNAME) ||
// an Application has multiple commands
featureName.equals(APPLICATION_COMMANDS_ATTNAME) ||
// a HandlerContainer has multiple handlers
featureName.equals(HANDLERCONTAINER_HANDLERS_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));
}
}