org.xtuml.bp.model.compare.contentmergeviewer.ModelContentMergeViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.xtuml.bp.model.compare.contentmergeviewer.ModelContentMergeViewer.java

Source

//=====================================================================
//
//File:      $RCSfile: ModelContentMergeViewer.java,v $
//Version:   $Revision: 1.11.12.3 $
//Modified:  $Date: 2013/07/24 19:20:29 $
//
//(c) Copyright 2004-2014 by Mentor Graphics Corp. All rights reserved.
//
//========================================================================
// Licensed under the Apache License, Version 2.0 (the "License"); you may not 
// use this file except in compliance with the License.  You may obtain a copy 
// of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   See the 
// License for the specific language governing permissions and limitations under
// the License.
//========================================================================
package org.xtuml.bp.model.compare.contentmergeviewer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.UUID;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareNavigator;
import org.eclipse.compare.ICompareNavigator;
import org.eclipse.compare.IEditableContent;
import org.eclipse.compare.ISharedDocumentAdapter;
import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.SharedDocumentAdapter;
import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider;
import org.eclipse.compare.internal.BufferedCanvas;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.ICompareUIConstants;
import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.compare.internal.NavigationEndDialog;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourceAttributes;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.team.internal.ui.synchronize.LocalResourceTypedElement;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.texteditor.IDocumentProvider;

import org.xtuml.bp.core.ClassStateMachine_c;
import org.xtuml.bp.core.CorePlugin;
import org.xtuml.bp.core.InstanceStateMachine_c;
import org.xtuml.bp.core.Ooaofooa;
import org.xtuml.bp.core.StateMachine_c;
import org.xtuml.bp.core.common.ModelRoot;
import org.xtuml.bp.core.common.NonRootModelElement;
import org.xtuml.bp.core.common.Transaction;
import org.xtuml.bp.core.common.TransactionManager;
import org.xtuml.bp.core.ui.AbstractModelExportFactory;
import org.xtuml.bp.model.compare.ComparableTreeObject;
import org.xtuml.bp.model.compare.ComparePlugin;
import org.xtuml.bp.model.compare.CompareTransactionManager;
import org.xtuml.bp.model.compare.EmptyElement;
import org.xtuml.bp.model.compare.ITreeDifferencerProvider;
import org.xtuml.bp.model.compare.ModelCacheManager;
import org.xtuml.bp.model.compare.ModelCacheManager.ModelLoadException;
import org.xtuml.bp.model.compare.ModelMergeProcessor;
import org.xtuml.bp.model.compare.TreeDifference;
import org.xtuml.bp.model.compare.TreeDifferencer;
import org.xtuml.bp.model.compare.actions.CopyDiffAction;
import org.xtuml.bp.model.compare.actions.NavigateDownAction;
import org.xtuml.bp.model.compare.actions.NavigateUpAction;
import org.xtuml.bp.model.compare.providers.ComparableProvider;
import org.xtuml.bp.model.compare.providers.ModelCompareContentProvider;
import org.xtuml.bp.model.compare.providers.ModelCompareLabelProvider;
import org.xtuml.bp.model.compare.providers.NonRootModelElementComparable;
import org.xtuml.bp.model.compare.structuremergeviewer.ModelStructureDiffViewer;
import org.xtuml.bp.ui.canvas.CanvasPlugin;
import org.xtuml.bp.ui.canvas.Connector_c;
import org.xtuml.bp.ui.canvas.ElementSpecification_c;
import org.xtuml.bp.ui.canvas.Graphconnector_c;
import org.xtuml.bp.ui.canvas.Graphedge_c;
import org.xtuml.bp.ui.canvas.GraphicalElement_c;

public class ModelContentMergeViewer extends ContentMergeViewer
        implements IModelContentMergeViewer, IResourceChangeListener {

    private static final String BUNDLE_NAME = "org.xtuml.bp.model.compare.ComparePluginMessages"; //$NON-NLS-1$;
    // width for center area
    public static final int CENTER_WIDTH = 42;
    private static HashMap<Object, ModelContentMergeViewer> instanceMap = new HashMap<Object, ModelContentMergeViewer>();
    // model cache manager
    ModelCacheManager modelManager = ComparePlugin.getDefault().getModelCacheManager();
    private SynchronizedTreeViewer leftTreeViewer;
    private SynchronizedTreeViewer rightTreeViewer;
    private Canvas canvas;
    private TreeDifferencer differencer;
    private SynchronizedTreeViewer ancestorTreeViewer;
    private double[] basicCenterCurve;
    private CompareConfiguration configuration;
    private CompareTransactionManager compareTransactionManager;
    private ActionContributionItem nextDifference;
    private ActionContributionItem previousDifference;

    // the following symbolic constants must match the IDs in Compare's
    // plugin.xml
    private static final String INCOMING_COLOR = "INCOMING_COLOR"; //$NON-NLS-1$
    private static final String OUTGOING_COLOR = "OUTGOING_COLOR"; //$NON-NLS-1$
    private static final String CONFLICTING_COLOR = "CONFLICTING_COLOR"; //$NON-NLS-1$
    private static final String RESOLVED_COLOR = "RESOLVED_COLOR"; //$NON-NLS-1$
    private static final String CONTAINED_COLOR = "CONTAINED_COLOR"; //$NON-NLS-1$
    private RGB INCOMING_BASE;
    private RGB INCOMING;
    private RGB CONFLICT_BASE;
    private RGB CONFLICT;
    private RGB OUTGOING_BASE;
    private RGB OUTGOING;
    private RGB RESOLVED;
    private RGB CONTAINED;
    private HashMap<RGB, Color> colors;
    private Action undo;
    private Action redo;
    protected boolean copySelection;
    public boolean mergeSupportEnabled = true;
    protected boolean ignoreNextEvent = false;
    private Object oldInput;
    private CTabFolder leftFolder;
    private CTabFolder rightFolder;
    private List<ModelMergeViewer> leftExtensions = new ArrayList<ModelMergeViewer>();
    private List<ModelMergeViewer> rightExtensions = new ArrayList<ModelMergeViewer>();
    private Map<NonRootModelElement, ICompareInput> savedModels = new HashMap<NonRootModelElement, ICompareInput>();
    private Map<NonRootModelElement, ICompareInput> visitedModels = new HashMap<NonRootModelElement, ICompareInput>();
    // This field will enable graphical data, and in the future maybe other
    // data, when set to true.  This is helpful currently when looking into
    // graphical related changes as they are used during merge but not shown
    // in the tree
    public boolean debug = false;

    public ModelContentMergeViewer(Composite parent, CompareConfiguration configuration) {
        super(SWT.NONE, ResourceBundle.getBundle(BUNDLE_NAME), configuration);
        this.configuration = configuration;
        buildControl(parent);
        // undo and redo
        undo = getCompareTransactionManager().getUndoAction();
        redo = getCompareTransactionManager().getRedoAction();
        if (configuration.getContainer().getActionBars() != null) {
            configuration.getContainer().getActionBars().setGlobalActionHandler(ActionFactory.UNDO.getId(), undo);
            configuration.getContainer().getActionBars().setGlobalActionHandler(ActionFactory.REDO.getId(), redo);
        }
        setContentProvider(new MergeViewerContentProvider(configuration) {

            @Override
            public void saveLeftContent(Object element, byte[] bytes) {
                if (element instanceof ICompareInput) {
                    ICompareInput node = (ICompareInput) element;
                    updateSMIds(node, true);
                    writeData(node, true);
                    NonRootModelElement[] rootElements = new NonRootModelElement[0];
                    try {
                        rootElements = modelManager.getRootElements(element, this, false, getLeftCompareRoot(),
                                ModelCacheManager.getLeftKey(element));
                        if (rootElements.length != 0) {
                            ICompareInput savedInput = savedModels.get(rootElements[0]);
                            if (savedInput == null) {
                                savedModels.put(rootElements[0], node);
                            }
                        }
                    } catch (ModelLoadException e) {
                        CorePlugin.logError("Unable to get root model elements from compare input.", e);
                    } finally {
                        modelManager.releaseModel(element, this, ModelCacheManager.getLeftKey(element));
                    }
                }
            }

            @Override
            public void saveRightContent(Object element, byte[] bytes) {
                if (element instanceof ICompareInput) {
                    ICompareInput node = (ICompareInput) element;
                    updateSMIds(node, false);
                    writeData(node, false);
                    NonRootModelElement[] rootElements = new NonRootModelElement[0];
                    try {
                        rootElements = modelManager.getRootElements(element, this, false, getRightCompareRoot(),
                                ModelCacheManager.getRightKey(element));
                        if (rootElements.length != 0) {
                            ICompareInput savedInput = savedModels.get(rootElements[0]);
                            if (savedInput == null) {
                                savedModels.put(rootElements[0], node);
                            }
                        }
                    } catch (ModelLoadException e) {
                        CorePlugin.logError("Unable to get root model elements from compare input.", e);
                    } finally {
                        modelManager.releaseModel(element, this, ModelCacheManager.getRightKey(element));
                    }
                }

            }

        });
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
    }

    protected void updateSMIds(ICompareInput node, boolean left) {
        // reset any SM ids that were upgrade for compare/merge
        Object key = ModelCacheManager.getLeftKey(node);
        if (!left) {
            key = ModelCacheManager.getRightKey(node);
        }
        Ooaofooa compareRoot = getLeftCompareRoot();
        if (!left) {
            compareRoot = getRightCompareRoot();
        }
        UUID originalSMId = modelManager.getOriginalSMIdFromEntry(key);
        if (originalSMId != null) {
            NonRootModelElement[] rootElements;
            try {
                rootElements = modelManager.getRootElements(node, this, false, compareRoot, key);
                // only support case where there is only one root
                StateMachine_c sm = null;
                if (rootElements[0] instanceof InstanceStateMachine_c) {
                    sm = StateMachine_c.getOneSM_SMOnR517((InstanceStateMachine_c) rootElements[0]);
                }
                if (rootElements[0] instanceof ClassStateMachine_c) {
                    sm = StateMachine_c.getOneSM_SMOnR517((ClassStateMachine_c) rootElements[0]);
                }
                if (sm != null) {
                    ModelCacheManager.updateIdForStateMachine(originalSMId, sm);
                }
            } catch (ModelLoadException e) {
                CorePlugin.logError("Unable to load compare data.", e);
            }
        }
    }

    protected void writeData(ICompareInput input, boolean toLeft) {
        try {
            ITypedElement destination = input.getLeft();
            Ooaofooa root = Ooaofooa.getInstance(ModelRoot.getLeftCompareRootPrefix() + input.hashCode());
            if (!toLeft) {
                destination = input.getRight();
                root = Ooaofooa.getInstance(ModelRoot.getRightCompareRootPrefix() + input.hashCode());
            }
            final NonRootModelElement rootElement = modelManager.getRootElements(destination, null, false, root,
                    ModelCacheManager.getLeftKey(input))[0];
            if (destination instanceof IEditableContent) {
                // before saving copy all graphical changes that are
                // non-conflicting
                List<TreeDifference> incomingGraphicalDifferences = getIncomingGraphicalDifferences(toLeft);
                if (incomingGraphicalDifferences.size() > 0) {
                    mergeIncomingGraphicalChanges(incomingGraphicalDifferences, toLeft, input);
                }
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                AbstractModelExportFactory modelExportFactory = CorePlugin.getModelExportFactory();
                IRunnableWithProgress runnable = modelExportFactory.create(root, baos, rootElement);
                runnable.run(new NullProgressMonitor());
                ((IEditableContent) destination).setContent(baos.toByteArray());
                if (destination instanceof LocalResourceTypedElement) {
                    ((IFile) ((LocalResourceTypedElement) destination).getResource()).setContents(
                            new ByteArrayInputStream(baos.toByteArray()), IFile.FORCE | IFile.KEEP_HISTORY,
                            new NullProgressMonitor());
                }
                WorkspaceJob job = new WorkspaceJob("Refresh workspace content") {

                    @Override
                    public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
                        NonRootModelElement elementGlobally = (NonRootModelElement) Ooaofooa.getDefaultInstance()
                                .getInstanceList(rootElement.getClass()).getGlobal(rootElement.getInstanceKey());
                        if (elementGlobally != null) {
                            if (elementGlobally.getFile() != null) {
                                elementGlobally.getFile().refreshLocal(IFile.DEPTH_INFINITE, monitor);
                            }
                        }
                        return Status.OK_STATUS;
                    }
                };
                job.schedule(1500);
            }

        } catch (FileNotFoundException e) {
            ComparePlugin.writeToLog("Unable to save merge data.", e, ModelContentMergeViewer.class);
        } catch (ModelLoadException e) {
            ComparePlugin.writeToLog("Unable to save merge data.", e, ModelContentMergeViewer.class);
        } catch (InvocationTargetException e) {
            ComparePlugin.writeToLog("Unable to save merge data.", e, ModelContentMergeViewer.class);
        } catch (InterruptedException e) {
            ComparePlugin.writeToLog("Unable to save merge data.", e, ModelContentMergeViewer.class);
        } catch (CoreException e) {
            ComparePlugin.writeToLog("Unable to save merge data.", e, ModelContentMergeViewer.class);
        }
    }

    protected void mergeIncomingGraphicalChanges(List<TreeDifference> incomingGraphicalDifferences, boolean left,
            ICompareInput input) {
        ITreeDifferencerProvider provider = (ITreeDifferencerProvider) leftTreeViewer.getContentProvider();
        ITableLabelProvider labelProvider = (ITableLabelProvider) leftTreeViewer.getLabelProvider();
        Ooaofooa destinationRoot = Ooaofooa.getInstance(ModelRoot.getLeftCompareRootPrefix() + input.hashCode());
        if (!left) {
            provider = (ITreeDifferencerProvider) rightTreeViewer.getContentProvider();
            labelProvider = (ITableLabelProvider) rightTreeViewer.getLabelProvider();
            destinationRoot = Ooaofooa.getInstance(ModelRoot.getRightCompareRootPrefix() + input.hashCode());
        }
        for (TreeDifference difference : incomingGraphicalDifferences) {
            try {
                ModelMergeProcessor.merge(differencer, difference, left, provider, labelProvider, destinationRoot);
            } catch (IOException e) {
                CorePlugin.logError("Unable to automatically merge graphical changes", e);
            }
        }
    }

    @Override
    protected void createToolItems(ToolBarManager toolBarManager) {
        CompareConfiguration cc = getCompareConfiguration();

        if (cc.isRightEditable()) {
            final CopyDiffAction a = new CopyDiffAction(this, true);
            leftTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {

                @Override
                public void selectionChanged(SelectionChangedEvent event) {
                    boolean result = a.isEnabled();
                    a.setEnabled(!result);
                    a.setEnabled(result);
                }
            });
            rightTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {

                @Override
                public void selectionChanged(SelectionChangedEvent event) {
                    boolean result = a.isEnabled();
                    a.setEnabled(!result);
                    a.setEnabled(result);
                }
            });
            Utilities.initAction(a, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$
            ActionContributionItem item = new ActionContributionItem(a);
            item.setVisible(true);
            toolBarManager.appendToGroup("merge", item); //$NON-NLS-1$
        }

        if (cc.isLeftEditable()) {
            final CopyDiffAction a = new CopyDiffAction(this, false);
            leftTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {

                @Override
                public void selectionChanged(SelectionChangedEvent event) {
                    boolean result = a.isEnabled();
                    a.setEnabled(!result);
                    a.setEnabled(result);
                }
            });
            rightTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {

                @Override
                public void selectionChanged(SelectionChangedEvent event) {
                    boolean result = a.isEnabled();
                    a.setEnabled(!result);
                    a.setEnabled(result);
                }
            });
            Utilities.initAction(a, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$
            ActionContributionItem item = new ActionContributionItem(a);
            item.setVisible(true);
            toolBarManager.appendToGroup("merge", item); //$NON-NLS-1$
        }
        Action navigateDown = new NavigateDownAction(this);
        Utilities.initAction(navigateDown, getResourceBundle(), "action.NextDiff.");
        nextDifference = new ActionContributionItem(navigateDown);
        toolBarManager.appendToGroup("navigation", nextDifference); //$NON-NLS-1$
        Action navigateUp = new NavigateUpAction(this);
        Utilities.initAction(navigateUp, getResourceBundle(), "action.PrevDiff.");
        previousDifference = new ActionContributionItem(navigateUp);
        toolBarManager.appendToGroup("navigation", previousDifference); //$NON-NLS-1$
    }

    public TreeDifference getNextDifference(boolean down) {
        TreeDifference nextDifference = null;
        if (down) {
            nextDifference = locateNextDifferenceBelow();
        } else {
            nextDifference = locateNextDifferenceAbove();
        }
        return nextDifference;
    }

    private TreeDifference locateNextDifferenceBelow() {
        TreeItem[] selection = leftTreeViewer.getTree().getSelection();
        // if the selection is empty check the right side as we
        // may have an item selected which matches a missing
        // item on the left
        TreeItem selected = null;
        if (selection.length == 0) {
            selection = rightTreeViewer.getTree().getSelection();
            if (selection.length != 0) {
                selected = selection[selection.length - 1];
            }
        } else {
            // otherwise we use the last selected item
            selected = selection[selection.length - 1];
        }
        // if the selection is empty return the first
        // difference
        if (selected == null) {
            return differencer.getNextDifference(null);
        } else {
            // walk the tree down
            return walkDown(selected.getData());
        }
    }

    private TreeDifference walkDown(Object selected) {
        return scanChildrenForDifference(selected, null, true, true);
    }

    private Object[] getChildrenIncludingMissing(Object parent) {
        if (parent instanceof NonRootModelElementComparable) {
            NonRootModelElementComparable nrmec = (NonRootModelElementComparable) parent;
            NonRootModelElement nrme = (NonRootModelElement) nrmec.getRealElement();
            if (nrme.getModelRoot().getId().startsWith(ModelRoot.getRightCompareRootPrefix())) {
                TreeItem matchingItem = SynchronizedTreeViewer.getMatchingItem(nrmec, leftTreeViewer);
                if (matchingItem != null) {
                    parent = matchingItem.getData();
                }
            }
        }
        ITreeContentProvider modelContentProvider = (ITreeContentProvider) leftTreeViewer.getContentProvider();
        Object[] children = modelContentProvider.getChildren(parent);
        List<Object> orderedChildren = new ArrayList<Object>();
        orderedChildren.addAll(Arrays.asList(children));
        // add missing children in
        List<TreeDifference> missingDiffs = differencer.getDifferences(parent, true);
        int processed = 0;
        int add = 1;
        for (TreeDifference difference : missingDiffs) {
            if (difference.getElement() == null) {
                if (processed == difference.getLocation()) {
                    add++;
                } else {
                    add = 1;
                }
                orderedChildren.add(difference.getLocation() + add,
                        difference.getMatchingDifference().getElement());
                processed = difference.getLocation();
            }
        }
        return orderedChildren.toArray();
    }

    private TreeDifference scanChildrenForDifference(Object parent, Object selected, boolean down,
            boolean scanParent) {
        Object[] children = getChildrenIncludingMissing(parent);
        List<Object> reversable = Arrays.asList(children);
        if (!down) {
            // reverse the list
            Collections.reverse(reversable);
        }
        boolean checkChild = selected == null;
        for (Object child : reversable) {
            if (checkChild) {
                List<TreeDifference> differences = differencer.getDifferences(child, true);
                if (differences.isEmpty()) {
                    // try the right map
                    differences = differencer.getDifferences(child, false);
                }
                for (TreeDifference difference : differences) {
                    if (difference.getElement() != null) {
                        return difference;
                    }
                }
                TreeDifference difference = scanChildrenForDifference(child, null, down, false);
                if (difference != null) {
                    return difference;
                }
            }
            if (child.equals(selected)) {
                checkChild = true;
            }
        }
        if (scanParent) {
            Object nextParent = ((ITreeContentProvider) leftTreeViewer.getContentProvider()).getParent(parent);
            if (nextParent == null) {
                return null;
            }
            return scanChildrenForDifference(nextParent, parent, down, true);
        } else {
            return null;
        }
    }

    private TreeDifference locateNextDifferenceAbove() {
        TreeItem[] selection = leftTreeViewer.getTree().getSelection();
        // if the selection is empty check the right side as we
        // may have an item selected which matches a missing
        // item on the left
        TreeItem selected = null;
        if (selection.length == 0) {
            selection = rightTreeViewer.getTree().getSelection();
            if (selection.length != 0) {
                selected = selection[0];
            }
        } else {
            // otherwise we use the first selected item
            selected = selection[0];
        }
        // if the selection is empty return null
        if (selected == null) {
            return null;
        } else {
            // walk the tree up
            return walkUp(selected.getData());
        }
    }

    private TreeDifference walkUp(Object selected) {
        return scanChildrenForDifference(selected, null, false, true);
    }

    public void endOfDocumentReached(boolean down) {
        Control c = getControl();
        if (Utilities.okToUse(c)) {
            handleEndOfDocumentReached(c.getShell(), down);
        }
    }

    private void handleEndOfDocumentReached(Shell shell, boolean next) {
        IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore();
        String value = store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION);
        if (!value.equals(ICompareUIConstants.PREF_VALUE_PROMPT)) {
            performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION, next);
        } else {
            shell.getDisplay().beep();
            String loopMessage;
            String nextMessage;
            String message;
            String title;
            if (next) {
                title = CompareMessages.TextMergeViewer_0;
                message = CompareMessages.TextMergeViewer_1;
                loopMessage = CompareMessages.TextMergeViewer_2;
                nextMessage = CompareMessages.TextMergeViewer_3;
            } else {
                title = CompareMessages.TextMergeViewer_4;
                message = CompareMessages.TextMergeViewer_5;
                loopMessage = CompareMessages.TextMergeViewer_6;
                nextMessage = CompareMessages.TextMergeViewer_7;
            }
            String[] localLoopOption = new String[] { loopMessage, ICompareUIConstants.PREF_VALUE_LOOP };
            String[] nextElementOption = new String[] { nextMessage, ICompareUIConstants.PREF_VALUE_NEXT };
            String[] doNothingOption = new String[] { CompareMessages.TextMergeViewer_17,
                    ICompareUIConstants.PREF_VALUE_DO_NOTHING };
            NavigationEndDialog dialog = new NavigationEndDialog(shell, title, null, message,
                    new String[][] { localLoopOption, nextElementOption, doNothingOption });
            int result = dialog.open();
            if (result == Window.OK) {
                performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL,
                        next);
                if (dialog.getToggleState()) {
                    String oldValue = store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION);
                    store.putValue(ICompareUIConstants.PREF_NAVIGATION_END_ACTION,
                            store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL));
                    store.firePropertyChangeEvent(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, oldValue,
                            store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL));
                }
            }
        }
    }

    private void performEndOfDocumentAction(Shell shell, IPreferenceStore store, String key, boolean next) {
        String value = store.getString(key);
        if (value.equals(ICompareUIConstants.PREF_VALUE_DO_NOTHING)) {
            return;
        }
        if (value.equals(ICompareUIConstants.PREF_VALUE_NEXT)) {
            ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
            if (hasNextElement(next)) {
                navigator.selectChange(next);
            }
        } else {
            if (next) {
                revealAndSelectDifference(differencer.getNextDifference(null));
            } else {
                revealAndSelectDifference(differencer.getLastDifference());
            }
        }
    }

    private boolean hasNextElement(boolean down) {
        ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
        if (navigator instanceof CompareNavigator) {
            CompareNavigator n = (CompareNavigator) navigator;
            return n.hasChange(down);
        }
        return false;
    }

    public void revealAndSelectDifference(TreeDifference next) {
        if (next == null) {
            // no differences, just return
            return;
        }
        leftTreeViewer.getTree().deselectAll();
        rightTreeViewer.getTree().deselectAll();
        if (!differencer.getLeftDifferences().contains(next)) {
            next = next.getMatchingDifference();
        }
        if (next.getElement() != null) {
            leftTreeViewer.setSelection(new StructuredSelection(next.getElement()), true);
            TreeItem matching = SynchronizedTreeViewer.getMatchingItem(next.getElement(), leftTreeViewer);
            if (matching != null) {
                leftTreeViewer.getTree().setTopItem(matching);
            }
            Object right = next.getMatchingDifference().getElement();
            if (right != null) {
                rightTreeViewer.setSelection(new StructuredSelection(right), true);
                TreeItem rightItem = SynchronizedTreeViewer.getMatchingItem(right, rightTreeViewer);
                if (rightItem != null) {
                    rightTreeViewer.getTree().setTopItem(rightItem);
                }
            } else {
                TreeItem rightParentItem = SynchronizedTreeViewer
                        .getMatchingItem(next.getMatchingDifference().getParent(), rightTreeViewer);
                if (rightParentItem != null) {
                    rightTreeViewer.reveal(rightParentItem.getData());
                    rightTreeViewer.setExpandedState(rightParentItem.getData(), true);
                }
            }
        } else {
            if (next.getParent() != null) {
                TreeItem leftParentItem = SynchronizedTreeViewer.getMatchingItem(next.getParent(), leftTreeViewer);
                if (leftParentItem != null) {
                    //leftTreeViewer.reveal(leftParentItem.getData());
                    leftTreeViewer.setExpandedState(leftParentItem.getData(), true);
                }
            }
            rightTreeViewer.setSelection(new StructuredSelection(next.getMatchingDifference().getElement()), true);
            if (next.getMatchingDifference().getElement() != null) {
                TreeItem matchingItem = SynchronizedTreeViewer
                        .getMatchingItem(next.getMatchingDifference().getElement(), rightTreeViewer);
                if (matchingItem != null) {
                    rightTreeViewer.getTree().setTopItem(matchingItem);
                }
            }
        }
        refreshCenter();
    }

    @Override
    protected void handleCompareInputChange() {
        setLeftDirty(false);
        setRightDirty(false);
        super.handleCompareInputChange();
    }

    public CompareConfiguration getConfiguration() {
        return configuration;
    }

    @Override
    public void copy(boolean leftToRight) {
        if (!mergeSupportEnabled) {
            return;
        }
        Ooaofooa modelRoot = getLeftCompareRoot();
        if (leftToRight) {
            modelRoot = getRightCompareRoot();
        }
        List<TreeDifference> leftDifferences = getSelectedDifferences(true, !copySelection);
        List<TreeDifference> rightDifferences = getSelectedDifferences(false, !copySelection);
        List<TreeDifference> differences = new ArrayList<TreeDifference>();
        // must copy over any removals as well, but there is nothing to select
        // in that case so we must get the difference from the other side
        if (leftToRight) {
            if (copySelection) {
                differences.addAll(leftDifferences);
                for (TreeDifference difference : rightDifferences) {
                    if (difference.getMatchingDifference().getElement() == null) {
                        differences.add(difference.getMatchingDifference());
                    }
                }
            }
        } else {
            if (copySelection) {
                differences.addAll(rightDifferences);
                for (TreeDifference difference : leftDifferences) {
                    if (difference.getMatchingDifference().getElement() == null) {
                        differences.add(difference.getMatchingDifference());
                    }
                }
            }
        }
        if (!copySelection) {
            List<TreeDifference> allDiffs = new ArrayList<TreeDifference>();
            if (leftToRight)
                allDiffs.addAll(leftDifferences);
            else
                allDiffs.addAll(rightDifferences);
            // add only the correct change
            boolean onlyOutgoingChanges = true;
            for (TreeDifference difference : rightDifferences) {
                if ((difference.getKind() & Differencer.DIRECTION_MASK) == Differencer.RIGHT
                        || (difference.getKind() & Differencer.DIRECTION_MASK) == Differencer.CONFLICTING) {
                    onlyOutgoingChanges = false;
                    break;
                }
            }
            for (TreeDifference diff : allDiffs) {
                if (onlyOutgoingChanges) {
                    // allow copy non-conflicting changes to overwrite
                    // left changes
                    differences.add(diff);
                } else {
                    if ((diff.getKind() & Differencer.DIRECTION_MASK) == Differencer.LEFT && leftToRight) {
                        differences.add(diff);
                    }
                    if ((diff.getKind() & Differencer.DIRECTION_MASK) == Differencer.RIGHT && !leftToRight) {
                        differences.add(diff);
                    }
                }
            }
        }
        List<TreeDifference> mergeDifferences = new ArrayList<TreeDifference>();
        // remove any contained differences and if the
        // container difference is not selected add it
        removeContainerDifferences(differences, mergeDifferences);
        Transaction transaction = compareTransactionManager.startCompareTransaction();
        try {
            SynchronizedTreeViewer viewer = leftTreeViewer;
            if (leftToRight) {
                viewer = rightTreeViewer;
            }
            // sort the differences, making sure that all additions are first
            // otherwise ordering differences may not be handled appropriately
            // when using the copy all action
            List<TreeDifference> additionsOrRemovals = new ArrayList<TreeDifference>();
            List<TreeDifference> remainder = new ArrayList<TreeDifference>();
            for (TreeDifference difference : mergeDifferences) {
                if (difference.getElement() != null && difference.getMatchingDifference().getElement() != null) {
                    remainder.add(difference);
                } else {
                    additionsOrRemovals.add(difference);
                }
            }
            mergeDifferences.clear();
            mergeDifferences.addAll(additionsOrRemovals);
            mergeDifferences.addAll(remainder);
            for (TreeDifference difference : mergeDifferences) {
                // skip graphical data at this point
                if (SynchronizedTreeViewer.differenceIsGraphical(difference) && !debug) {
                    continue;
                }
                if ((difference.getKind() & Differencer.DIRECTION_MASK) == Differencer.CONFLICTING
                        && !copySelection) {
                    continue;
                }
                boolean changed = ModelMergeProcessor.merge(differencer, difference, !leftToRight,
                        (ITreeDifferencerProvider) viewer.getContentProvider(),
                        (ITableLabelProvider) viewer.getLabelProvider(), modelRoot);
                if (changed) {
                    if (leftToRight) {
                        markRightDirty(true);
                    } else {
                        markLeftDirty(true);
                    }
                }
            }
            compareTransactionManager.endTransaction(transaction);
        } catch (Exception e) {
            // need to cancel transaction
            if (compareTransactionManager.getActiveTransaction() != null && transaction != null) {
                compareTransactionManager.cancelTransaction(transaction);
            }
            ComparePlugin.writeToLog("Unable to complete merge transaction", e, getClass());
        }
    }

    public List<TreeDifference> getSelectedDifferences(boolean left, boolean ignoreSelection) {
        if (ignoreSelection && left) {
            return differencer.getLeftDifferences();
        }
        if (ignoreSelection && !left) {
            return differencer.getRightDifferences();
        }
        SynchronizedTreeViewer viewer = leftTreeViewer;
        List<TreeDifference> differences = new ArrayList<TreeDifference>();
        if (!left) {
            viewer = rightTreeViewer;
        }
        IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
        for (Iterator<?> iterator = selection.iterator(); iterator.hasNext();) {
            Object next = iterator.next();
            List<TreeDifference> located = differencer.getDifferences(next, left);
            if (located.isEmpty()) {
                // walk the hierarchy up to see if we find a difference that
                // includes children
                Object parent = ((ITreeContentProvider) viewer.getContentProvider()).getParent(next);
                while (parent != null) {
                    located = differencer.getDifferences(parent, left);
                    if (!located.isEmpty()) {
                        if (located.size() == 1) {
                            if (located.get(0).getIncludeChildren() && located.get(0).getElement() != null) {
                                differences.addAll(located);
                                break;
                            } else {
                                parent = ((ITreeContentProvider) viewer.getContentProvider()).getParent(parent);
                            }
                        } else {
                            // do not continue
                            break;
                        }
                    } else {
                        parent = ((ITreeContentProvider) viewer.getContentProvider()).getParent(parent);
                    }
                }
                // next see if this is a non-expanded parent that contains
                // differences and include all of them
                differences.addAll(SynchronizedTreeViewer.scanChildrenForDifferences(next, differencer,
                        (ITreeContentProvider) viewer.getContentProvider(), viewer == getLeftViewer()));
            } else {
                differences.addAll(located);
            }
        }
        return differences;
    }

    protected void createControls(Composite composite) {
        leftExtensions.addAll(getExtensions());
        leftFolder = new CTabFolder(composite, SWT.BOTTOM);
        leftFolder.setBorderVisible(false);
        if (leftExtensions.size() == 0) {
            leftFolder.setSingle(true);
            leftFolder.setTabHeight(0);
            leftFolder.marginHeight = -2;
            leftFolder.marginWidth = -2;
        }
        CTabItem leftItem = new CTabItem(leftFolder, SWT.NONE);
        Composite leftPanel = new Composite(leftFolder, SWT.NONE);
        leftPanel.setLayout(new FillLayout());
        leftPanel.setFont(composite.getFont());
        leftTreeViewer = new SynchronizedTreeViewer(leftPanel, SWT.H_SCROLL | SWT.MULTI | SWT.FULL_SELECTION
                | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.BORDER, this, configuration.isLeftEditable(),
                false);
        ModelCompareContentProvider leftContentProvider = new ModelCompareContentProvider();
        leftContentProvider.setIncludeNonTreeData(debug);
        leftTreeViewer.setContentProvider(leftContentProvider);
        leftTreeViewer.setUseHashlookup(true);
        leftTreeViewer.setLabelProvider(new ModelCompareLabelProvider());
        leftItem.setControl(leftPanel);
        leftItem.setText("Tree");
        for (ModelMergeViewer viewerExtension : leftExtensions) {
            CTabItem item = new CTabItem(leftFolder, SWT.NONE);
            Composite panel = new Composite(leftFolder, SWT.NONE);
            panel.setLayout(new FillLayout());
            panel.setFont(composite.getFont());
            viewerExtension.createControl(panel);
            viewerExtension.setType(ModelMergeViewer.LEFT);
            item.setControl(panel);
            item.setText(viewerExtension.getTitle());
        }
        leftFolder.setSelection(leftItem);
        rightExtensions.addAll(getExtensions());
        rightFolder = new CTabFolder(composite, SWT.BOTTOM);
        rightFolder.setBorderVisible(false);
        rightFolder.setSimple(true);
        if (rightExtensions.size() == 0) {
            rightFolder.setSingle(true);
            rightFolder.setTabHeight(0);
            rightFolder.marginHeight = -2;
            rightFolder.marginWidth = -2;
        }
        CTabItem rightItem = new CTabItem(rightFolder, SWT.NONE);
        Composite rightPanel = new Composite(rightFolder, SWT.NONE);
        rightPanel.setLayout(new FillLayout());
        rightPanel.setFont(composite.getFont());
        rightTreeViewer = new SynchronizedTreeViewer(rightPanel, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI
                | SWT.FULL_SELECTION | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.BORDER, this,
                configuration.isRightEditable(), false);
        rightTreeViewer.addSynchronizationViewer(leftTreeViewer);
        rightTreeViewer.setUseHashlookup(true);
        leftTreeViewer.addSynchronizationViewer(rightTreeViewer);
        ModelCompareContentProvider rightProvider = new ModelCompareContentProvider();
        rightProvider.setIncludeNonTreeData(debug);
        rightTreeViewer.setContentProvider(rightProvider);
        rightTreeViewer.setLabelProvider(new ModelCompareLabelProvider());
        rightItem.setControl(rightPanel);
        rightItem.setText("Tree");
        for (ModelMergeViewer viewerExtension : rightExtensions) {
            CTabItem item = new CTabItem(rightFolder, SWT.NONE);
            Composite panel = new Composite(rightFolder, SWT.NONE);
            panel.setLayout(new FillLayout());
            panel.setFont(composite.getFont());
            viewerExtension.createControl(panel);
            viewerExtension.setType(ModelMergeViewer.RIGHT);
            item.setControl(panel);
            item.setText(viewerExtension.getTitle());
        }
        rightFolder.setSelection(rightItem);
        ancestorTreeViewer = new SynchronizedTreeViewer(composite, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI
                | SWT.FULL_SELECTION | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.BORDER, this, false, true);
        ancestorTreeViewer.setUseHashlookup(true);
        ModelCompareContentProvider ancestorProvider = new ModelCompareContentProvider();
        ancestorProvider.setIncludeNonTreeData(debug);
        ancestorTreeViewer.setContentProvider(ancestorProvider);
        ancestorTreeViewer.setLabelProvider(new ModelCompareLabelProvider());
        leftTreeViewer.addSynchronizationViewer(ancestorTreeViewer);
        rightTreeViewer.addSynchronizationViewer(ancestorTreeViewer);
        ancestorTreeViewer.addSynchronizationViewer(leftTreeViewer);
        ancestorTreeViewer.addSynchronizationViewer(rightTreeViewer);
    }

    private Collection<? extends ModelMergeViewer> getExtensions() {
        List<ModelMergeViewer> viewers = new ArrayList<ModelMergeViewer>();
        IExtensionRegistry reg = Platform.getExtensionRegistry();
        IExtensionPoint extPt = reg.getExtensionPoint("org.xtuml.bp.model.compare.compareDifferencers"); //$NON-NLS-1$
        IExtension[] extensions = extPt.getExtensions();
        for (int i = 0; i < extensions.length; i++) {
            IConfigurationElement[] configurationElements = extensions[i].getConfigurationElements();
            for (int j = 0; j < configurationElements.length; j++) {
                if (configurationElements[j].getName().equals("differenceEngine")) { //$NON-NLS-1$
                    try {
                        Object viewer = configurationElements[j].createExecutableExtension("differenceViewer"); //$NON-NLS-1$
                        if (viewer != null) {
                            viewers.add((ModelMergeViewer) viewer);
                        }
                    } catch (CoreException e) {
                        CanvasPlugin.logError("Unable to create executable extension from point.", e); //$NON-NLS-1$
                    }
                }
            }
        }
        return viewers;
    }

    @Override
    protected byte[] getContents(boolean left) {
        return null;
    }

    @Override
    protected void handleResizeAncestor(int x, int y, int width, int height) {
        ancestorTreeViewer.getControl().setBounds(x, y, width, height);
    }

    @Override
    protected void handleResizeLeftRight(int x, int y, int leftWidth, int centerWidth, int rightWidth, int height) {
        // always split left and right width
        int totalWidth = leftWidth + rightWidth;
        leftWidth = totalWidth / 2;
        rightWidth = totalWidth - leftWidth;

        if (getCenterPart() != null) {
            getCenterPart().setBounds(leftWidth - CENTER_WIDTH / 2, y, CENTER_WIDTH, height);
        }
        leftFolder.setBounds(x, y, leftWidth - CENTER_WIDTH / 2, height);
        rightFolder.setBounds(x + leftWidth + CENTER_WIDTH / 2, y, rightWidth - CENTER_WIDTH / 2, height);
        leftTreeViewer.getControl().redraw();
        rightTreeViewer.getControl().redraw();
        getCenterPart().redraw();
    }

    public void refreshCenter() {
        canvas.redraw();
        canvas.update();
    }

    private Canvas getCenterPart() {
        return canvas;
    }

    @Override
    public Composite createCenterControl(Composite parent) {
        canvas = new BufferedCanvas((Composite) getControl(), SWT.TRANSPARENT) {
            public void doPaint(GC gc) {
                try {
                    drawDiffLines(gc);
                } catch (Exception e) {
                    ComparePlugin.writeToLog("Exception during difference line drawing", e, getClass());
                }
            }
        };
        return canvas;
    }

    protected void drawDiffLines(GC gc) {
        // if the differencer is null then we are currently
        // in the middle of a content update, skip this paint
        // request
        if (differencer == null) {
            return;
        }
        gc.setAdvanced(true);
        gc.setAntialias(SWT.ON);
        List<TreeDifference> differences = differencer.getLeftDifferences();
        for (TreeDifference difference : differences) {
            if (SynchronizedTreeViewer.differenceIsGraphical(difference) && !debug) {
                // currently do not include graphical data
                continue;
            }
            gc.setForeground(getColor(PlatformUI.getWorkbench().getDisplay(), getStrokeColor(difference)));
            TreeItem leftItem = leftTreeViewer.getItemForDifference(difference);
            if (leftItem == null || leftItem.isDisposed()) {
                continue;
            }
            TreeItem matchingItem = SynchronizedTreeViewer.getMatchingItem(leftItem.getData(), rightTreeViewer);
            if (matchingItem == null) {
                continue;
            }
            Rectangle leftBounds = leftTreeViewer.buildHighlightRectangle(leftItem, false, gc, true, true);
            Rectangle rightBounds = rightTreeViewer.buildHighlightRectangle(matchingItem, false, gc, true, true);
            // draw a line from the left edge to the right edge
            int[] points = getCenterCurvePoints(0, leftBounds.y + (leftBounds.height / 2), canvas.getBounds().width,
                    rightBounds.y + (rightBounds.height / 2));
            for (int i = 1; i < points.length; i++) {
                gc.drawLine(i - 1, points[i - 1], i, points[i]);
            }
        }
    }

    private int[] getCenterCurvePoints(int startx, int starty, int endx, int endy) {
        if (basicCenterCurve == null)
            buildBaseCenterCurve(endx - startx);
        double height = endy - starty;
        endy = endy + 2;
        starty = starty + 2;
        height = height / 2;
        int width = endx - startx;
        int[] points = new int[width];
        for (int i = 0; i < width; i++) {
            points[i] = (int) (-height * basicCenterCurve[i] + height + starty);
        }
        return points;
    }

    private void buildBaseCenterCurve(int w) {
        double width = w;
        basicCenterCurve = new double[CENTER_WIDTH];
        for (int i = 0; i < CENTER_WIDTH; i++) {
            double r = i / width;
            basicCenterCurve[i] = Math.cos(Math.PI * r);
        }
    }

    @Override
    public void setInput(Object input) {
        oldInput = getInput();
        super.setInput(input);
    }

    @Override
    protected void updateContent(Object ancestor, Object left, Object right) {
        // make left and right non-edittable as either we were saved
        // or there were changes outside of the compare editor
        setLeftDirty(false);
        setRightDirty(false);

        boolean leftEditable = getCompareConfiguration().isLeftEditable();
        boolean rightEditable = getCompareConfiguration().isRightEditable();
        if (left instanceof IEditableContent) {
            leftEditable = ((IEditableContent) left).isEditable();
        } else {
            leftEditable = false;
        }
        if (right instanceof IEditableContent) {
            rightEditable = ((IEditableContent) right).isEditable();
        } else {
            rightEditable = false;
        }
        getLeftViewer().setEditable(leftEditable);
        getRightViewer().setEditable(rightEditable);

        reconnectLocalDocuments(ancestor, left, right);

        if (left == null && right == null) {
            return;
        }
        if (((left instanceof IStreamContentAccessor || right instanceof IStreamContentAccessor)
                || ((left instanceof IStreamContentAccessor) && right == null))) {
            try {
                instanceMap.remove(oldInput);
                Object leftKey = ModelCacheManager.getLeftKey(getInput());
                Object rightKey = ModelCacheManager.getRightKey(getInput());
                Object ancestorKey = ModelCacheManager.getAncestorKey(getInput());
                ModelCacheManager modelCacheManager = ComparePlugin.getDefault().getModelCacheManager();
                NonRootModelElement[] leftElements = null;
                NonRootModelElement[] rightElements = null;
                NonRootModelElement[] ancestorElements = null;
                if (left instanceof IStreamContentAccessor) {
                    leftElements = modelCacheManager.getRootElements(left, this, false, getLeftCompareRoot(),
                            leftKey);
                }
                if (right instanceof IStreamContentAccessor) {
                    rightElements = modelCacheManager.getRootElements(right, this, false, getRightCompareRoot(),
                            rightKey);
                }
                if (ancestor instanceof IStreamContentAccessor) {
                    ancestorElements = modelCacheManager.getRootElements(ancestor, this, false,
                            getAncestorCompareRoot(), ancestorKey);
                }
                if (leftElements.length != 0) {
                    ICompareInput visited = visitedModels.get(leftElements[0]);
                    if (visited == null) {
                        visitedModels.put(leftElements[0], (ICompareInput) getInput());
                    }
                } else if (rightElements.length != 0) {
                    ICompareInput visited = visitedModels.get(rightElements[0]);
                    if (visited == null) {
                        visitedModels.put(rightElements[0], (ICompareInput) getInput());
                    }
                }
                // configure cache key for left, right and ancestor label providers
                ((ModelCompareContentProvider) leftTreeViewer.getContentProvider()).setCacheKey(leftKey);
                ((ModelCompareContentProvider) rightTreeViewer.getContentProvider()).setCacheKey(rightKey);
                if (ancestorTreeViewer != null) {
                    ((ModelCompareContentProvider) ancestorTreeViewer.getContentProvider())
                            .setCacheKey(ancestorKey);
                }
                ((ModelCompareContentProvider) leftTreeViewer.getContentProvider())
                        .setModelRoot(getLeftCompareRoot());
                ((ModelCompareContentProvider) rightTreeViewer.getContentProvider())
                        .setModelRoot(getRightCompareRoot());
                if (ancestorTreeViewer != null) {
                    ((ModelCompareContentProvider) ancestorTreeViewer.getContentProvider())
                            .setModelRoot(getAncestorCompareRoot());
                }
                ModelCompareContentProvider differencerProvider = new ModelCompareContentProvider();
                differencerProvider.setRootElements(leftElements, rightElements);
                if (differencer != null) {
                    differencer.dipose();
                }
                differencer = new TreeDifferencer(differencerProvider, leftElements, rightElements,
                        ancestorElements, isThreeWay(), getInput());
                TreeDifferencer.instances.put(getInput(), differencer);
                updateRootElementsForTreeViewers(leftElements, rightElements);
                nextDifference.getAction().setEnabled(false);
                nextDifference.getAction().setEnabled(nextDifference.getAction().isEnabled());
                previousDifference.getAction().setEnabled(false);
                previousDifference.getAction().setEnabled(previousDifference.getAction().isEnabled());
                instanceMap.put(getInput(), this);
                // set input on any extending viewers
                for (ModelMergeViewer viewer : leftExtensions) {
                    viewer.setCompareRoot(getLeftCompareRoot());
                    viewer.setKey(leftKey);
                    viewer.setInput(getInput());
                }
                for (ModelMergeViewer viewer : rightExtensions) {
                    viewer.setCompareRoot(getRightCompareRoot());
                    viewer.setKey(rightKey);
                    viewer.setInput(getInput());
                }
                if (left instanceof IStreamContentAccessor && leftIsLocal()) {
                    // here we will automatically merge any incoming/conflicting
                    // graphics this will cover the case where only graphics
                    // are changed
                    List<TreeDifference> graphicalDifferences = getIncomingGraphicalDifferences(leftIsLocal());
                    if (!graphicalDifferences.isEmpty()) {
                        mergeIncomingGraphicalChanges(graphicalDifferences, leftIsLocal(),
                                (ICompareInput) getInput());
                        markLeftDirty(true);
                        differencer.refresh();
                    }
                }
                if (right instanceof IStreamContentAccessor && !leftIsLocal()) {
                    // here we will automatically merge any incoming/conflicting
                    // graphics this will cover the case where only graphics
                    // are changed
                    List<TreeDifference> graphicalDifferences = getIncomingGraphicalDifferences(!leftIsLocal());
                    if (!graphicalDifferences.isEmpty()) {
                        mergeIncomingGraphicalChanges(graphicalDifferences, !leftIsLocal(),
                                (ICompareInput) getInput());
                        markRightDirty(true);
                        differencer.refresh();
                    }
                }
            } catch (ModelLoadException e) {
                CorePlugin.logError("Unable to load data for comparison.", e);
            }
        }
        if (ancestor != null) {
            ancestorTreeViewer.setInput(ancestor);
        }
        leftTreeViewer.setInput(left);
        rightTreeViewer.setInput(right);
        refreshCenter();
        PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {

            @Override
            public void run() {
                if (differencer != null) {
                    revealAndSelectDifference(differencer.getNextDifference(null));
                }
            }
        });
    }

    private void updateRootElementsForTreeViewers(NonRootModelElement[] leftElements,
            NonRootModelElement[] rightElements) {
        // update the root elements for each tree viewer, this is
        // to support empty elements
        ((ModelCompareContentProvider) leftTreeViewer.getContentProvider()).setRootElements(leftElements,
                rightElements);
        ((ModelCompareContentProvider) rightTreeViewer.getContentProvider()).setRootElements(leftElements,
                rightElements);
        if (ancestorTreeViewer != null) {
            ((ModelCompareContentProvider) ancestorTreeViewer.getContentProvider()).setRootElements(leftElements,
                    rightElements);
        }
    }

    private List<TreeDifference> getIncomingGraphicalDifferences(boolean left) {
        List<TreeDifference> incomingGraphicalDifferences = new ArrayList<TreeDifference>();
        List<TreeDifference> differences = differencer.getRightDifferences();
        if (!left) {
            differences = differencer.getLeftDifferences();
        }
        for (TreeDifference difference : differences) {
            // only copy those that are incoming or conflicting
            // also exclude any additions or removals where the
            // semantical model does not match
            int diffKind = difference.getKind() & Differencer.DIRECTION_MASK;
            if (((left && diffKind == Differencer.RIGHT) || (!left && diffKind == Differencer.LEFT)
                    || diffKind == Differencer.CONFLICTING && differencer.isThreeWay())
                    || ((difference.getKind() == Differencer.ADDITION)
                            || (difference.getKind() == Differencer.DELETION))) {
                if (SynchronizedTreeViewer.differenceIsGraphical(difference)) {
                    boolean add = true;
                    if ((difference.getElement() == null || difference.getElement() instanceof EmptyElement)
                            && difference.getMatchingDifference()
                                    .getElement() instanceof NonRootModelElementComparable) {
                        // this is a removal, only remove if the
                        // semantic element represented is also
                        // removed
                        Object elementToBeRemoved = ((NonRootModelElementComparable) difference
                                .getMatchingDifference().getElement()).getRealElement();
                        if (elementToBeRemoved instanceof GraphicalElement_c) {
                            GraphicalElement_c graphEleToRemove = (GraphicalElement_c) elementToBeRemoved;
                            // if the graphical element represents value is null
                            // we need to
                            // look at the local file, in some cases the
                            // configuration
                            // management system may have pre-merged the
                            // semantical element
                            // in a different file
                            if (graphEleToRemove.getRepresents() == null) {
                                ElementSpecification_c spec = ElementSpecification_c
                                        .getOneGD_ESOnR10(graphEleToRemove);
                                if (spec != null) {
                                    NonRootModelElement elementGlobally = (NonRootModelElement) Ooaofooa
                                            .getDefaultInstance().getInstanceList(spec.getRepresents())
                                            .getGlobal(graphEleToRemove.getOoa_id());
                                    if (elementGlobally != null) {
                                        add = false;
                                    }
                                }
                            }
                            if (add) {
                                if (graphEleToRemove.getRepresents() != null
                                        && graphEleToRemove.getRepresents() instanceof NonRootModelElement) {
                                    NonRootModelElement semanticElement = (NonRootModelElement) graphEleToRemove
                                            .getRepresents();
                                    if (!semanticElement.isOrphaned()) {
                                        add = false;
                                    } else {
                                        addConnectionPoints(graphEleToRemove, left, incomingGraphicalDifferences);
                                    }
                                }
                            }
                        } else {
                            add = false;
                        }
                    }
                    if ((difference.getMatchingDifference().getElement() == null
                            || difference.getMatchingDifference().getElement() instanceof EmptyElement)
                            && difference.getElement() instanceof NonRootModelElementComparable) {
                        // this is an addition, only add the graphic if the
                        // semantic element was also copied
                        Object elementToBeAdded = ((NonRootModelElementComparable) difference.getElement())
                                .getRealElement();
                        if (elementToBeAdded instanceof GraphicalElement_c) {
                            GraphicalElement_c graphEleToAdd = (GraphicalElement_c) elementToBeAdded;
                            // if the graphical element represents value is null we need to
                            // look at the local file, in some cases the configuration
                            // management system may have pre-merged the semantical element
                            // in a different file
                            if (graphEleToAdd.getRepresents() == null) {
                                ElementSpecification_c spec = ElementSpecification_c
                                        .getOneGD_ESOnR10(graphEleToAdd);
                                if (spec != null) {
                                    NonRootModelElement elementGlobally = (NonRootModelElement) Ooaofooa
                                            .getDefaultInstance().getInstanceList(spec.getRepresents())
                                            .getGlobal(graphEleToAdd.getOoa_id());
                                    if (elementGlobally == null) {
                                        add = false;
                                    } else {
                                        addConnectionPoints(graphEleToAdd, left, incomingGraphicalDifferences);
                                    }
                                }
                            }
                            if (graphEleToAdd.getRepresents() != null
                                    && graphEleToAdd.getRepresents() instanceof NonRootModelElement) {
                                NonRootModelElement semanticElement = (NonRootModelElement) graphEleToAdd
                                        .getRepresents();
                                NonRootModelElement parentElement = (NonRootModelElement) ((EmptyElement) difference
                                        .getMatchingDifference().getElement()).getParent();
                                Object existingSemanticElement = Ooaofooa
                                        .getInstance(parentElement.getModelRoot().getId())
                                        .getInstanceList(semanticElement.getClass())
                                        .get(semanticElement.getInstanceKey());
                                if (existingSemanticElement == null) {
                                    add = false;
                                } else {
                                    addConnectionPoints(graphEleToAdd, left, incomingGraphicalDifferences);
                                }
                            }
                        } else {
                            add = false;
                        }
                    }
                    if (add) {
                        incomingGraphicalDifferences.add(difference);
                    }
                }
            }
        }
        return incomingGraphicalDifferences;
    }

    private void addConnectionPoints(GraphicalElement_c graphEleToAdd, boolean left,
            List<TreeDifference> incomingGraphicalDifferences) {
        // we need to include any DIM_CON additions (connections to existing shapes)
        Connector_c connector = Connector_c.getOneGD_CONOnR2(graphEleToAdd);
        if (connector != null) {
            Graphconnector_c startCon = Graphconnector_c
                    .getOneDIM_CONOnR320(Graphedge_c.getOneDIM_EDOnR20(connector));
            Graphconnector_c endCon = Graphconnector_c
                    .getOneDIM_CONOnR321(Graphedge_c.getManyDIM_EDsOnR20(connector));
            if (startCon != null) {
                ComparableTreeObject comparableTreeObject = ComparableProvider.getComparableTreeObject(startCon);
                List<TreeDifference> startConDiffs = differencer.getDifferences(comparableTreeObject, !left);
                incomingGraphicalDifferences.addAll(startConDiffs);
            }
            if (endCon != null) {
                ComparableTreeObject comparableTreeObject = ComparableProvider.getComparableTreeObject(endCon);
                List<TreeDifference> endConDiffs = differencer.getDifferences(comparableTreeObject, !left);
                incomingGraphicalDifferences.addAll(endConDiffs);
            }
        }
    }

    /**
     * Reconnects the document providers to reset the modification state. Left
     * and Right modifications are not always done on a document, but the tree
     * states are kept in sync. Therefore we reset the connection to allow
     * resetting of the current modification state.
     * 
     * @param ancestor
     * @param left
     * @param right
     */
    private void reconnectLocalDocuments(Object ancestor, Object left, Object right) {
        if (left instanceof IAdaptable) {
            SharedDocumentAdapter sharedDocumentAdapter = (SharedDocumentAdapter) ((IAdaptable) left)
                    .getAdapter(ISharedDocumentAdapter.class);
            if (sharedDocumentAdapter != null) {
                IEditorInput documentKey = sharedDocumentAdapter.getDocumentKey(left);
                if (documentKey != null) {
                    IDocumentProvider documentProvider = SharedDocumentAdapter.getDocumentProvider(documentKey);
                    sharedDocumentAdapter.disconnect(left);
                    try {
                        sharedDocumentAdapter.connect(documentProvider, sharedDocumentAdapter.getDocumentKey(left));
                    } catch (CoreException e) {
                        ComparePlugin.writeToLog("Unable to reconnect to left changed document.", e, getClass());
                    }
                }
            }
        }
        if (right instanceof IAdaptable) {
            SharedDocumentAdapter sharedDocumentAdapter = (SharedDocumentAdapter) ((IAdaptable) right)
                    .getAdapter(ISharedDocumentAdapter.class);
            if (sharedDocumentAdapter != null) {
                IEditorInput key = sharedDocumentAdapter.getDocumentKey(right);
                if (key != null) {
                    IDocumentProvider documentProvider = SharedDocumentAdapter.getDocumentProvider(key);
                    sharedDocumentAdapter.disconnect(right);
                    try {
                        sharedDocumentAdapter.connect(documentProvider,
                                sharedDocumentAdapter.getDocumentKey(right));
                    } catch (CoreException e) {
                        ComparePlugin.writeToLog("Unable to reconnect to right changed document.", e, getClass());
                    }
                }
            }
        }
    }

    public TreeDifferencer getDifferencer() {
        return differencer;
    }

    public SynchronizedTreeViewer getLeftViewer() {
        return leftTreeViewer;
    }

    public SynchronizedTreeViewer getAncestorTree() {
        return ancestorTreeViewer;
    }

    @Override
    protected void handleDispose(DisposeEvent event) {
        // persist any non-saved left inputs
        Set<NonRootModelElement> elements = visitedModels.keySet();
        for (NonRootModelElement element : elements) {
            ICompareInput saved = savedModels.get(element);
            if (saved == null) {
                // only do this for any that have the
                // git annotations
                NonRootModelElement elementGlobally = (NonRootModelElement) Ooaofooa.getDefaultInstance()
                        .getInstanceList(element.getClass()).getGlobal(element.getInstanceKey());
                if (elementGlobally != null) {
                    // refresh the file to make sure its
                    // synced with what git wrote
                    InputStream actualContents = null;
                    try {
                        elementGlobally.getFile().refreshLocal(IFile.DEPTH_ONE, new NullProgressMonitor());
                        actualContents = elementGlobally.getFile().getContents();
                    } catch (CoreException e) {
                        CorePlugin.logError("Unable to load local file for conflict annotation removal.", e);
                    }
                    if (actualContents != null) {
                        String data = readData(actualContents);
                        if (data.contains(">>>>>>>") && data.contains("<<<<<<<") && data.contains("=======")) {
                            // we need to reload the data from the input, incase
                            // a user has modified and the closed without saving
                            ICompareInput input = (ICompareInput) visitedModels.get(element);
                            Ooaofooa compareRoot = Ooaofooa
                                    .getInstance(ModelRoot.getLeftCompareRootPrefix() + input.hashCode());
                            try {
                                ITypedElement left = input.getLeft();
                                if (left instanceof IStreamContentAccessor) {
                                    IStreamContentAccessor accesor = (IStreamContentAccessor) left;
                                    InputStream contents = accesor.getContents();
                                    String compareData = readData(contents);
                                    ByteArrayInputStream bais = new ByteArrayInputStream(compareData.getBytes());
                                    modelManager.getRootElements(left, this, true, compareRoot,
                                            ModelCacheManager.getLeftKey(input));
                                    elementGlobally.getFile().setContents(bais, IResource.FORCE,
                                            new NullProgressMonitor());
                                }
                            } catch (CoreException e) {
                                CorePlugin.logError(
                                        "Unable to update local contents to remove conflict annotations.", e);
                            } catch (ModelLoadException e) {
                                CorePlugin.logError(
                                        "Unable to load compare contents to remove conflict annotations.", e);
                            }
                        }
                    }
                }
            }
        }
        for (NonRootModelElement element : elements) {
            ICompareInput previous = visitedModels.get(element);
            ITypedElement oldLeft = previous.getLeft();
            ITypedElement oldRight = previous.getRight();
            ITypedElement oldAncestor = previous.getAncestor();
            ModelCacheManager modelCacheManager = ComparePlugin.getDefault().getModelCacheManager();
            modelCacheManager.releaseModel(oldLeft, this, ModelCacheManager.getLeftKey(previous));
            modelCacheManager.releaseModel(oldRight, this, ModelCacheManager.getRightKey(previous));
            modelCacheManager.releaseModel(oldAncestor, this, ModelCacheManager.getAncestorKey(previous));
        }
        visitedModels.clear();
        savedModels.clear();
        differencer.dipose();
        instanceMap.remove(getInput());
        super.handleDispose(event);
        if (canvas != null) {
            canvas.dispose();
            canvas = null;
        }
        differencer = null;
        leftTreeViewer = null;
        rightTreeViewer = null;
        ancestorTreeViewer = null;
        if (compareTransactionManager != null) {
            // unregister transaction listener
            TransactionManager.getSingleton().removeTransactionListener(compareTransactionManager);
        }
        compareTransactionManager = null;
        if (colors != null) {
            Set<RGB> keySet = colors.keySet();
            for (RGB rgb : keySet) {
                Color color = colors.get(rgb);
                if (color != null) {
                    color.dispose();
                }
            }
            colors.clear();
        }
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

    private String readData(InputStream contents) {
        try {
            byte[] resultBuff = new byte[0];
            byte[] buff = new byte[1024];
            int i = -1;
            while ((i = contents.read(buff, 0, buff.length)) > -1) {
                byte[] tbuff = new byte[resultBuff.length + i];
                System.arraycopy(resultBuff, 0, tbuff, 0, resultBuff.length);
                System.arraycopy(buff, 0, tbuff, resultBuff.length, i);
                resultBuff = tbuff;
            }
            String result = new String(resultBuff);
            contents.close();
            return result;
        } catch (IOException e) {
            CorePlugin.logError("Unable to read compare contents.", e);
        }
        return "";
    }

    public CompareTransactionManager getCompareTransactionManager() {
        if (compareTransactionManager == null) {
            compareTransactionManager = new CompareTransactionManager();
        }
        return compareTransactionManager;
    }

    public void markLeftDirty(boolean value) {
        setLeftDirty(value);
    }

    public void markRightDirty(boolean value) {
        setRightDirty(value);
    }

    public void updateColors() {
        ColorRegistry registry = JFaceResources.getColorRegistry();

        RGB bg = getBackground(PlatformUI.getWorkbench().getDisplay());

        INCOMING_BASE = registry.getRGB(INCOMING_COLOR);
        if (INCOMING_BASE == null) {
            INCOMING_BASE = new RGB(0, 0, 255);
            registry.put(INCOMING_COLOR, INCOMING_BASE);
        }
        INCOMING = interpolate(INCOMING_BASE, bg, 0.6);
        OUTGOING_BASE = registry.getRGB(OUTGOING_COLOR);
        if (OUTGOING_BASE == null) {
            OUTGOING_BASE = new RGB(0, 0, 0); // BLACK
            registry.put(OUTGOING_COLOR, OUTGOING_BASE);
        }
        OUTGOING = interpolate(OUTGOING_BASE, bg, 0.6);

        CONFLICT_BASE = registry.getRGB(CONFLICTING_COLOR);
        if (CONFLICT_BASE == null) {
            CONFLICT_BASE = new RGB(255, 0, 0);
            registry.put(CONFLICTING_COLOR, CONFLICT_BASE); // RED
        }
        CONFLICT = interpolate(CONFLICT_BASE, bg, 0.6);

        RESOLVED = registry.getRGB(RESOLVED_COLOR);
        if (RESOLVED == null) {
            RESOLVED = new RGB(0, 255, 0); // GREEN
        }

        CONTAINED = registry.getRGB(CONTAINED_COLOR);
        if (CONTAINED == null) {
            CONTAINED = new RGB(50, 255, 25);
        }
    }

    private RGB getBackground(Display display) {
        return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB();
    }

    static RGB interpolate(RGB fg, RGB bg, double scale) {
        if (fg != null && bg != null)
            return new RGB((int) ((1.0 - scale) * fg.red + scale * bg.red),
                    (int) ((1.0 - scale) * fg.green + scale * bg.green),
                    (int) ((1.0 - scale) * fg.blue + scale * bg.blue));
        if (fg != null)
            return fg;
        if (bg != null)
            return bg;
        return new RGB(128, 128, 128); // a gray
    }

    RGB getStrokeColor(TreeDifference difference) {
        updateColors();
        boolean ignoreAncestor = Utilities.getBoolean(getCompareConfiguration(),
                ICompareUIConstants.PROP_IGNORE_ANCESTOR, false);
        if (difference.isContainedDifference()) {
            return CONTAINED;
        }
        if (isThreeWay() && !ignoreAncestor) {
            switch (difference.getKind() & Differencer.DIRECTION_MASK) {
            case Differencer.RIGHT:
                if (leftIsLocal())
                    return INCOMING;
                return OUTGOING;
            case Differencer.LEFT:
                if (leftIsLocal())
                    return OUTGOING;
                return INCOMING;
            case Differencer.CONFLICTING:
                return CONFLICT;
            }
            return OUTGOING;
        }
        return OUTGOING;
    }

    Color getColor(Display display, RGB rgb) {
        if (rgb == null)
            return null;
        if (colors == null)
            colors = new HashMap<RGB, Color>(20);
        Color c = (Color) colors.get(rgb);
        if (c == null) {
            c = new Color(display, rgb);
            colors.put(rgb, c);
        }
        return c;
    }

    private boolean leftIsLocal() {
        return Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$
    }

    public SynchronizedTreeViewer getRightViewer() {
        return rightTreeViewer;
    }

    public IAction getUndoAction() {
        return undo;
    }

    public IAction getRedoAction() {
        return redo;
    }

    public Ooaofooa getLeftCompareRoot() {
        if (getInput() != null) {
            return Ooaofooa.getInstance(ModelRoot.getLeftCompareRootPrefix() + getInput().hashCode());
        }
        if (oldInput != null) {
            return Ooaofooa.getInstance(ModelRoot.getLeftCompareRootPrefix() + oldInput.hashCode());
        }
        return null;
    }

    public Ooaofooa getRightCompareRoot() {
        if (getInput() != null) {
            return Ooaofooa.getInstance(ModelRoot.getRightCompareRootPrefix() + getInput().hashCode());
        }
        if (oldInput != null) {
            return Ooaofooa.getInstance(ModelRoot.getRightCompareRootPrefix() + oldInput.hashCode());
        }
        return null;
    }

    public Ooaofooa getAncestorCompareRoot() {
        if (getInput() != null) {
            return Ooaofooa.getInstance(ModelRoot.getAncestorCompareRootPrefix() + getInput().hashCode());
        }
        if (oldInput != null) {
            return Ooaofooa.getInstance(ModelRoot.getAncestorCompareRootPrefix() + oldInput.hashCode());
        }
        return null;
    }

    public static ModelContentMergeViewer getInstance(Object input) {
        if (input == null) {
            Set<Object> keySet = instanceMap.keySet();
            if (keySet.iterator().hasNext()) {
                return instanceMap.get(keySet.iterator().next());
            } else {
                return null;
            }
        }
        return instanceMap.get(input);
    }

    public void revealAndSelectItem(Object element) {
        getLeftViewer().setSelection(new StructuredSelection(element), true);
        TreeItem leftItem = SynchronizedTreeViewer.getMatchingItem(element, getLeftViewer());
        if (leftItem != null) {
            getLeftViewer().getTree().setTopItem(leftItem);
        } else {

        }
        getRightViewer().setSelection(new StructuredSelection(element), true);
        TreeItem rightItem = SynchronizedTreeViewer.getMatchingItem(element, getRightViewer());
        if (rightItem != null) {
            getRightViewer().getTree().setTopItem(rightItem);
        } else {

        }
        if (getAncestorTree() != null) {
            TreeItem ancestorItem = SynchronizedTreeViewer.getMatchingItem(element, getAncestorTree());
            if (ancestorItem != null) {
                getAncestorTree().getTree().setTopItem(ancestorItem);
            }
            getAncestorTree().setSelection(new StructuredSelection(element), true);
        }
        refreshCenter();
    }

    @Override
    public void setCopySelection(boolean value) {
        copySelection = value;
    }

    @Override
    public CompareConfiguration internalGetCompareConfiguration() {
        return getCompareConfiguration();
    }

    @Override
    public boolean isLeftEditable() {
        IMergeViewerContentProvider cp = (IMergeViewerContentProvider) getContentProvider();
        if (cp != null) {
            if (!isPatchHunk()) {
                boolean result = cp.isLeftEditable(getInput());
                if (result) {
                    // also check if the file is writable, for some reason
                    // eclipse does not
                    if (getInput() instanceof ICompareInput) {
                        Object left = ((ICompareInput) getInput()).getLeft();
                        if (left instanceof LocalResourceTypedElement) {
                            ResourceAttributes resourceAttributes = ((LocalResourceTypedElement) left).getResource()
                                    .getResourceAttributes();
                            return !resourceAttributes.isReadOnly();
                        }
                    }
                    return result;
                }
            }
        }
        return false;
    }

    private boolean isPatchHunk() {
        return Utilities.isHunk(getInput());
    }

    @Override
    public boolean isRightEditable() {
        IMergeViewerContentProvider cp = (IMergeViewerContentProvider) getContentProvider();
        if (cp != null) {
            if (!isPatchHunk()) {
                boolean result = cp.isRightEditable(getInput());
                if (result) {
                    // also check if the file is writable, for some reason
                    // eclipse does not
                    if (getInput() instanceof ICompareInput) {
                        Object right = ((ICompareInput) getInput()).getRight();
                        if (right instanceof LocalResourceTypedElement) {
                            ResourceAttributes resourceAttributes = ((LocalResourceTypedElement) right)
                                    .getResource().getResourceAttributes();
                            return !resourceAttributes.isReadOnly();
                        }
                    }
                    return result;
                }
            }
        }
        return false;
    }

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        boolean refresh = false;
        IResourceDelta originalDelta = event.getDelta();
        IResourceDelta delta = null;
        if (originalDelta != null) {
            IResource[] resources = getResourcesFromInput();
            for (IResource resource : resources) {
                delta = originalDelta.findMember(resource.getFullPath());
                if (delta != null) {
                    break;
                }
            }
        }
        if (delta != null) {
            final int flags = delta.getFlags();
            switch (delta.getKind()) {
            case IResourceDelta.CHANGED:
                if ((IResourceDelta.CONTENT & flags) != 0) {
                    refresh = true;
                }
                break;
            }

        }
        if (refresh) {
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {

                @Override
                public void run() {
                    if (getInput() != null) {
                        updateContent(((ICompareInput) getInput()).getAncestor(),
                                ((ICompareInput) getInput()).getLeft(), ((ICompareInput) getInput()).getRight());
                        ModelStructureDiffViewer modelStructureDiffViewer = ModelStructureDiffViewer.inputMap
                                .get(getInput());
                        if (modelStructureDiffViewer != null) {
                            modelStructureDiffViewer.refreshModel();
                        }
                    }
                }
            });
        }
    }

    private IResource[] getResourcesFromInput() {
        List<IResource> resources = new ArrayList<IResource>();
        Object compareInput = getInput();
        if (compareInput instanceof ICompareInput) {
            ICompareInput cinput = (ICompareInput) compareInput;
            ITypedElement left = cinput.getLeft();
            ITypedElement right = cinput.getRight();
            ITypedElement anc = cinput.getAncestor();
            if (left instanceof LocalResourceTypedElement) {
                IResource leftResource = ((LocalResourceTypedElement) left).getResource();
                resources.add(leftResource);
            }
            if (right instanceof LocalResourceTypedElement) {
                IResource rightResource = ((LocalResourceTypedElement) right).getResource();
                resources.add(rightResource);
            }
            if (anc != null && anc instanceof LocalResourceTypedElement) {
                IResource ancResource = ((LocalResourceTypedElement) anc).getResource();
                resources.add(ancResource);
            }
        }
        return resources.toArray(new IResource[resources.size()]);
    }

    public static void removeContainerDifferences(List<TreeDifference> differences,
            List<TreeDifference> mergeDifferences) {
        for (TreeDifference difference : differences) {
            if (difference.isContainedDifference()) {
                TreeDifference container = difference.getContainerDifference();
                if (container != null && !mergeDifferences.contains(container)) {
                    mergeDifferences.add(container);
                } // if for some reason there is no container leave the
                  // difference out of the merge
            } else {
                mergeDifferences.add(difference);
            }
        }
    }
}