/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stefan Dirix (sdirix@eclipsesource.com) - Bug 473847: Minimum E4 Compatibility of Compare
 *******************************************************************************/
package org.eclipse.compare;

import java.lang.reflect.InvocationTargetException;
import java.util.ResourceBundle;

import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
import org.eclipse.compare.contentmergeviewer.IFlushable;
import org.eclipse.compare.internal.BinaryCompareViewer;
import org.eclipse.compare.internal.ChangePropertyAction;
import org.eclipse.compare.internal.CompareContentViewerSwitchingPane;
import org.eclipse.compare.internal.CompareEditorInputNavigator;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.CompareStructureViewerSwitchingPane;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.ICompareUIConstants;
import org.eclipse.compare.internal.IFlushable2;
import org.eclipse.compare.internal.OutlineViewerCreator;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.ViewerDescriptor;
import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.ui.texteditor.ITextEditorExtension3;

/**
 * A compare operation which can present its results in a special editor.
 * Running the compare operation and presenting the results in a compare editor
 * are combined in one class because it allows a client to keep the implementation
 * all in one place while separating it from the innards of a specific UI implementation of compare/merge.
 * <p>
 * A <code>CompareEditorInput</code> defines methods for the following sequence steps:
 * <UL>
 * <LI>running a lengthy compare operation under progress monitor control,
 * <LI>creating a UI for displaying the model and initializing the some widgets with the compare result,
 * <LI>tracking the dirty state of the model in case of merge,
 * <LI>saving the model.
 * </UL>
 * The Compare plug-in's <code>openCompareEditor</code> method takes an <code>CompareEditorInput</code>
 * and starts sequencing through the above steps. If the compare result is not empty a new compare editor
 * is opened and takes over the sequence until eventually closed.
 * <p>
 * The <code>prepareInput</code> method should contain the
 * code of the compare operation. It is executed under control of a progress monitor
 * and can be canceled. If the result of the compare is not empty, that is if there are differences
 * that needs to be presented, the <code>ICompareInput</code> should hold onto them and return them with
 * the <code>getCompareResult</code> method.
 * If the value returned from <code>getCompareResult</code> is not <code>null</code>
 * a compare editor is opened on the <code>ICompareInput</code> with title and title image initialized by the
 * corresponding methods of the <code>ICompareInput</code>.
 * <p>
 * Creation of the editor's SWT controls is delegated to the <code>createContents</code> method.
 * Here the SWT controls must be created and initialized  with the result of the compare operation.
 * <p>
 * If merging is allowed, the modification state of the compared constituents must be tracked and the dirty
 * state returned from method <code>isSaveNeeded</code>. The value <code>true</code> triggers a subsequent call
 * to <code>save</code> where the modified resources can be saved.
 * <p>
 * The most important part of this implementation is the setup of the compare/merge UI.
 * The UI uses a simple browser metaphor to present compare results.
 * The top half of the layout shows the structural compare results (e.g. added, deleted, and changed files),
 * the bottom half the content compare results (e.g. textual differences between two files).
 * A selection in the top pane is fed to the bottom pane. If a content viewer is registered
 * for the type of the selected object, this viewer is installed in the pane.
 * In addition if a structure viewer is registered for the selection type the top pane
 * is split vertically to make room for another pane and the structure viewer is installed
 * in it. When comparing Java files this second structure viewer would show the structural
 * differences within a Java file, e.g. added, deleted or changed methods and fields.
 * <p>
 * Subclasses provide custom setups, e.g. for a Catch-up/Release operation
 * by passing a subclass of <code>CompareConfiguration</code> and by implementing the <code>prepareInput</code> method.
 * If a subclass cannot use the <code>DiffTreeViewer</code> which is installed by default in the
 * top left pane, method <code>createDiffViewer</code> can be overridden.
 * <p>
 * If subclasses of this class implement {@link ISaveablesSource}, the compare editor will
 * pass these models through to the workbench. The editor will still show the dirty indicator
 * if one of these underlying models is dirty. It is the responsibility of subclasses that
 * implement this interface to call {@link #setDirty(boolean)} when the dirty state of
 * any of the models managed by the subclass change dirty state.
 *
 * @see CompareUI
 * @see CompareEditorInput
 */
public abstract class CompareEditorInput extends PlatformObject implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress, ICompareContainer {
	private static final boolean DEBUG= false;

	/**
	 * The name of the "dirty" property (value <code>"DIRTY_STATE"</code>).
	 */
	public static final String DIRTY_STATE= "DIRTY_STATE"; //$NON-NLS-1$

	/**
	 * The name of the "title" property. This property is fired when the title
	 * of the compare input changes. Clients should also re-obtain the tool tip
	 * when this property changes.
	 * @see #getTitle()
	 * @since 3.3
	 */
	public static final String PROP_TITLE= ICompareUIConstants.PROP_TITLE;

	/**
	 * The name of the "title image" property. This property is fired when the title
	 * image of the compare input changes.
	 * @see #getTitleImage()
	 * @since 3.3
	 */
	public static final String PROP_TITLE_IMAGE= ICompareUIConstants.PROP_TITLE_IMAGE;

	/**
	 * The name of the "selected edition" property. This property is fired when the selected
	 * edition of the compare input changes.
	 * @see #isEditionSelectionDialog()
	 * @see #getSelectedEdition()
	 * @since 3.3
	 */
	public static final String PROP_SELECTED_EDITION= ICompareUIConstants.PROP_SELECTED_EDITION;

	private static final String COMPARE_EDITOR_IMAGE_NAME= "eview16/compare_view.png"; //$NON-NLS-1$
	private static Image fgTitleImage;

	private Splitter fComposite;
	private final CompareConfiguration fCompareConfiguration;
	private CompareViewerPane fStructureInputPane;
	private CompareViewerSwitchingPane fStructurePane1;
	private CompareViewerSwitchingPane fStructurePane2;
	private CompareViewerSwitchingPane fContentInputPane;
	private CompareViewerPane fFocusPane;
	private String fMessage;
	private Object fInput;
	private String fTitle;
	private final ListenerList<IPropertyChangeListener> fListenerList= new ListenerList<>();
	private CompareNavigator fNavigator;
	private boolean fLeftDirty;
	private boolean fRightDirty;
	private final IPropertyChangeListener fDirtyStateListener;

	boolean fStructureCompareOnSingleClick= true;

	private ICompareContainer fContainer;
	private boolean fContainerProvided;
	private String fHelpContextId;
	private InternalOutlineViewerCreator fOutlineView;
	private ViewerDescriptor fContentViewerDescriptor;
	private ViewerDescriptor fStructureViewerDescriptor;

	private class InternalOutlineViewerCreator extends OutlineViewerCreator {
		private OutlineViewerCreator getWrappedCreator() {
			if (fContentInputPane != null) {
				Viewer v = fContentInputPane.getViewer();
				if (v != null) {
					return Adapters.adapt(v, OutlineViewerCreator.class);
				}
			}
			return null;
		}

		@Override
		public Viewer findStructureViewer(Viewer oldViewer,	ICompareInput input, Composite parent,
				CompareConfiguration configuration) {
			OutlineViewerCreator creator = getWrappedCreator();
			if (creator != null)
				return creator.findStructureViewer(oldViewer, input, parent, configuration);
			return null;
		}

		@Override
		public boolean hasViewerFor(Object input) {
			OutlineViewerCreator creator = getWrappedCreator();
			return creator != null;
		}

		@Override
		public Object getInput() {
			OutlineViewerCreator creator = getWrappedCreator();
			if (creator != null)
				return creator.getInput();
			return null;
		}
	}

	/**
	 * Creates a <code>CompareEditorInput</code> which is initialized with the given
	 * compare configuration.
	 * The compare configuration is passed to subsequently created viewers.
	 *
	 * @param configuration the compare configuration
	 */
	public CompareEditorInput(CompareConfiguration configuration) {
		fCompareConfiguration= configuration;
		Assert.isNotNull(configuration);

		fDirtyStateListener= e -> {
			String propertyName= e.getProperty();
			if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) {
				boolean changed= false;
				Object newValue= e.getNewValue();
				if (newValue instanceof Boolean)
					changed= ((Boolean) newValue).booleanValue();
				setDirty(e.getSource(), changed);
			}
		};

		IPreferenceStore ps= configuration.getPreferenceStore();
		if (ps != null)
			fStructureCompareOnSingleClick= ps.getBoolean(ComparePreferencePage.OPEN_STRUCTURE_COMPARE);

		fContainer = configuration.getContainer();
		configuration.setContainer(this);
	}

	private boolean structureCompareOnSingleClick() {
		return fStructureCompareOnSingleClick;
	}

	private boolean isShowStructureInOutlineView() {
		Object object= getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW);
		return object instanceof Boolean && ((Boolean)object).booleanValue();
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T getAdapter(Class<T> adapter) {
		if (ICompareNavigator.class.equals(adapter) || CompareNavigator.class.equals(adapter)) {
			return (T) getNavigator();
		}
		if (adapter == IShowInSource.class) {
			final IFile file = Adapters.adapt(this, IFile.class);
			if (file != null)
				return (T) (IShowInSource) () -> new ShowInContext(new FileEditorInput(file), StructuredSelection.EMPTY);
		}
		if (adapter == OutlineViewerCreator.class) {
			synchronized (this) {
				if (fOutlineView == null)
					fOutlineView = new InternalOutlineViewerCreator();
				return (T) fOutlineView;
			}
		}
		if (adapter == IFindReplaceTarget.class) {
			if (fContentInputPane != null) {
				Viewer v = fContentInputPane.getViewer();
				if (v != null) {
					return (T) Adapters.adapt(v, IFindReplaceTarget.class);
				}
			}
		}
		if (adapter == IEditorInput.class) {
			if (fContentInputPane != null) {
				Viewer v = fContentInputPane.getViewer();
				if (v != null) {
					return (T) Adapters.adapt(v, IEditorInput.class);
				}
			}
		}

		if (adapter == ITextEditorExtension3.class) {
			if (fContentInputPane != null) {
				Viewer v = fContentInputPane.getViewer();
				if (v != null) {
					return (T) Adapters.adapt(v, ITextEditorExtension3.class);
				}
			}
		}

		return super.getAdapter(adapter);
	}

	@Override
	public synchronized ICompareNavigator getNavigator() {
		if (fNavigator == null)
			fNavigator= new CompareEditorInputNavigator(
				new Object[] {
					fStructureInputPane,
					fStructurePane1,
					fStructurePane2,
					fContentInputPane
				}
			);
		return fNavigator;
	}

	@Override
	public ImageDescriptor getImageDescriptor() {
		return null;
	}

	@Override
	public String getToolTipText() {
		return getTitle();
	}

	@Override
	public String getName() {
		return getTitle();
	}

	/**
	 * Returns <code>null</code> since this editor cannot be persisted.
	 *
	 * @return <code>null</code> because this editor cannot be persisted
	 */
	@Override
	public IPersistableElement getPersistable() {
		return null;
	}

	/**
	 * Returns <code>false</code> to indicate that this input
	 * should not appear in the "File Most Recently Used" menu.
	 *
	 * @return <code>false</code>
	 */
	@Override
	public boolean exists() {
		return false;
	}

	/*
	 * FIXME!
	 */
	protected void setMessage(String message) {
		fMessage= message;
	}

	/*
	 * FIXME!
	 */
	public String getMessage() {
		return fMessage;
	}

	/**
	 * Returns the title which will be used in the compare editor's title bar.
	 * It can be set with <code>setTitle</code>.
	 *
	 * @return the title
	 */
	public String getTitle() {
		if (fTitle == null)
			return Utilities.getString("CompareEditorInput.defaultTitle"); //$NON-NLS-1$
		return fTitle;
	}

	/**
	 * Sets the title which will be used when presenting the compare result.
	 * This method must be called before the editor is opened.
	 *
	 * @param title the title to use for the CompareEditor
	 */
	public void setTitle(String title) {
		String oldTitle = fTitle;
		fTitle= title;
		Utilities.firePropertyChange(fListenerList, this, PROP_TITLE, oldTitle, title);
	}

	/**
	 * Returns the title image which will be used in the compare editor's title bar.
	 * Returns the title image which will be used when presenting the compare result.
	 * This implementation returns a generic compare icon.
	 * Subclasses can override.
	 *
	 * @return the title image, or <code>null</code> if none
	 */
	public Image getTitleImage() {
		if (fgTitleImage == null) {
			fgTitleImage= CompareUIPlugin.getImageDescriptor(COMPARE_EDITOR_IMAGE_NAME).createImage();
			CompareUI.disposeOnShutdown(fgTitleImage);
		}
		return fgTitleImage;
	}

	/**
	 * Returns the configuration object for the viewers within the compare editor.
	 * Returns the configuration which was passed to the constructor.
	 *
	 * @return the compare configuration
	 */
	public CompareConfiguration getCompareConfiguration() {
		return fCompareConfiguration;
	}

	/**
	 * Adds standard actions to the given <code>ToolBarManager</code>.
	 * <p>
	 * Subclasses may override to add their own actions.
	 * </p>
	 *
	 * @param toolBarManager the <code>ToolBarManager</code> to which to contribute
	 */
	public void contributeToToolBar(ToolBarManager toolBarManager) {
		ResourceBundle bundle= CompareUI.getResourceBundle();
		ChangePropertyAction ignoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(bundle, getCompareConfiguration());
		toolBarManager.getControl().addDisposeListener(ignoreWhitespace);
		ChangePropertyAction showPseudoConflicts= ChangePropertyAction.createShowPseudoConflictsAction(bundle, getCompareConfiguration());
		toolBarManager.getControl().addDisposeListener(showPseudoConflicts);
		toolBarManager.add(new Separator());
		toolBarManager.add(ignoreWhitespace);
		toolBarManager.add(showPseudoConflicts);
	}

	/**
	 * Runs the compare operation and stores the compare result.
	 *
	 * @param monitor the progress monitor to use to display progress and receive
	 *     requests for cancellation
	 * @throws InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
	 * 	   it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
	 *     wrapped in an <code>InvocationTargetException</code> by the calling context
	 * @throws InterruptedException if the operation detects a request to cancel,
	 *     using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
	 *     <code>InterruptedException</code>
	 */
	@Override
	public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
		fInput= prepareInput(monitor);
	}

	/**
	 * Runs the compare operation and returns the compare result.
	 * If <code>null</code> is returned no differences were found and no compare editor needs to be opened.
	 * Progress should be reported to the given progress monitor.
	 * A request to cancel the operation should be honored and acknowledged
	 * by throwing <code>InterruptedException</code>.
	 * <p>
	 * Note: this method is typically called in a modal context thread which doesn't have a Display assigned.
	 * Implementors of this method shouldn't therefore allocated any SWT resources in this method.
	 * </p>
	 *
	 * @param monitor the progress monitor to use to display progress and receive
	 *   requests for cancellation
	 * @return the result of the compare operation, or <code>null</code> if there are no differences
	 * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
	 * 	it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
	 *  wrapped in an <code>InvocationTargetException</code> by the calling context
	 * @exception InterruptedException if the operation detects a request to cancel,
	 *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
	 *  <code>InterruptedException</code>
	 */
	protected abstract Object prepareInput(IProgressMonitor monitor)
				throws InvocationTargetException, InterruptedException;

	/**
	 * Returns the compare result computed by the most recent call to the
	 * <code>run</code> method. Returns <code>null</code> if no
	 * differences were found.
	 *
	 * @return the compare result prepared in method <code>prepareInput</code>
	 *     or <code>null</code> if there were no differences
	 */
	public Object getCompareResult() {
		return fInput;
	}

	/**
	 * Creates the SWT controls that are used to display the result of the compare operation.
	 * Creates the SWT Controls and sets up the wiring between the individual panes.
	 * This implementation creates all four panes but makes only the necessary ones visible.
	 * Finally it feeds the compare result into the top left structure viewer
	 * and the content viewer.
	 * <p>
	 * Subclasses may override if they need to change the layout or wiring between panes.
	 *
	 * @param parent the parent control under which the control must be created
	 * @return the SWT control hierarchy for the compare editor
	 */
	public Control createContents(Composite parent) {
		fComposite= new Splitter(parent, SWT.VERTICAL);
		fComposite.setData(this);

		Control outline= createOutlineContents(fComposite, SWT.HORIZONTAL);

		fContentInputPane= createContentViewerSwitchingPane(fComposite, SWT.BORDER | SWT.FLAT, this);

		if (fFocusPane == null)
			fFocusPane= fContentInputPane;
		if (outline != null)
			fComposite.setVisible(outline, false);
		fComposite.setVisible(fContentInputPane, true);

		if (fStructureInputPane != null && fComposite.getChildren().length == 2)
			fComposite.setWeights(new int[] { 30, 70 });

		feedInput();
		parent.requestLayout();
		fComposite.addDisposeListener(e -> {
			/*
			 * When the UI associated with this compare editor input is
			 * disposed each composite being part of the UI releases its
			 * children first. A dispose listener is added to the last
			 * widget found in that structure. Therefore, compare editor
			 * input is disposed at the end making it possible to refer
			 * during widgets disposal.
			 */
			Composite composite = (Composite) e.widget;
			Control control = composite;
			while (composite.getChildren().length > 0) {
				control = composite.getChildren()[composite.getChildren().length - 1];
				if (control instanceof Composite) {
					composite = (Composite) control;
				} else {
					break;
				}
			}
			control.addDisposeListener(ev -> handleDispose());
		});
		if (fHelpContextId != null && PlatformUI.isWorkbenchRunning())
			PlatformUI.getWorkbench().getHelpSystem().setHelp(fComposite, fHelpContextId);
		contentsCreated();
		return fComposite;
	}

	/**
	 * @param parent the parent control under which the control must be created
	 * @param style  the style of widget to construct
	 * @param cei the compare editor input for the viewer
	 * @return the pane displaying content changes
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	protected CompareViewerSwitchingPane createContentViewerSwitchingPane(Splitter parent, int style, CompareEditorInput cei) {
		return new CompareContentViewerSwitchingPane(parent, style, cei);
	}

	/**
	 * Callback that occurs when the UI associated with this compare editor
	 * input is disposed. This method will only be invoked if the UI has been
	 * created (i.e. after the call to {@link #createContents(Composite)}.
	 * Subclasses can extend this method but ensure that the overridden method
	 * is invoked.
	 *
	 * @since 3.3
	 */
	protected void handleDispose() {
		fContainerProvided = false;
		fContainer = null;
		fComposite = null;
		fStructureInputPane = null;
		fStructurePane1 = null;
		fStructurePane2 = null;
		fContentInputPane = null;
		fFocusPane = null;
		fNavigator = null;
		fCompareConfiguration.dispose();
	}

	/**
	 * Callback that occurs after the control for the input has
	 * been created. If this method gets invoked then {@link #handleDispose()}
	 * will be invoked when the control is disposed. Subclasses may extend this
	 * method to register any listeners that need to be de-registered when the
	 * input is disposed.
	 * @since 3.3
	 */
	protected void contentsCreated() {
		// Default is to do nothing
	}

	/**
	 * @param parent    the parent control under which the control must be created
	 * @param direction the layout direction of the contents, either
	 *                  <code>SWT.HORIZONTAL</code> or <code>SWT.VERTICAL</code>
	 * @return the SWT control hierarchy for the outline part of the compare editor
	 * @since 3.0
	 */
	public Control createOutlineContents(Composite parent, int direction) {
		final Splitter h= new Splitter(parent, direction);

		fStructureInputPane= createStructureInputPane(h);
		if (hasChildren(getCompareResult()))
			fFocusPane= fStructureInputPane;

		fStructurePane1= new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
		h.setVisible(fStructurePane1, false);

		fStructurePane2= new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
		h.setVisible(fStructurePane2, false);

		// setup the wiring for top left pane
		fStructureInputPane.addOpenListener(
			oe -> feed1(oe.getSelection())
		);
		fStructureInputPane.addSelectionChangedListener(
			new ISelectionChangedListener() {
				@Override
				public void selectionChanged(SelectionChangedEvent e) {
					ISelection s= e.getSelection();
					if (s == null || s.isEmpty())
						feed1(s);
					if (isEditionSelectionDialog())
						firePropertyChange(new PropertyChangeEvent(this, PROP_SELECTED_EDITION, null, getSelectedEdition()));
				}
			}
		);
		fStructureInputPane.addDoubleClickListener(
			event -> feedDefault1(event.getSelection())
		);

		fStructurePane1.addSelectionChangedListener(
			e -> feed2(e.getSelection())
		);

		fStructurePane2.addSelectionChangedListener(
			e -> feed3(e.getSelection())
		);

		return h;
	}

	/**
	 * Create the pane that will contain the structure input pane (upper left).
	 * By default, a {@link CompareViewerSwitchingPane} is returned. Subclasses
	 * may override to provide an alternate pane.
	 * @param parent the parent composite
	 * @return the structure input pane
	 * @since 3.3
	 */
	protected CompareViewerPane createStructureInputPane(final Composite parent) {
		return new CompareStructureViewerSwitchingPane(parent, SWT.BORDER | SWT.FLAT, true, this) {
			@Override
			protected Viewer getViewer(Viewer oldViewer, Object input) {
				if (CompareEditorInput.this.hasChildren(input)) {
					return createDiffViewer(this);
				}
				return super.getViewer(oldViewer, input);
			}
		};
	}

	boolean hasChildren(Object input) {
		if (input instanceof IDiffContainer) {
			IDiffContainer dn= (IDiffContainer) input;
			return dn.hasChildren();
		}
		return false;
	}

	private void feedInput() {
		if (fStructureInputPane != null
				&& (fInput instanceof ICompareInput
						|| isCustomStructureInputPane())) {
			if (hasChildren(fInput) || isCustomStructureInputPane()) {
				// The input has multiple entries so set the input of the structure input pane
				fStructureInputPane.setInput(fInput);
			} else if (!structureCompareOnSingleClick() || isShowStructureInOutlineView()) {
				// We want to avoid showing the structure in the editor if we can so first
				// we'll set the content pane to see if we need to provide a structure
				internalSetContentPaneInput(fInput);
				// If the content viewer is unusable
				if (hasUnusableContentViewer()
						|| (structureCompareOnSingleClick()
								&& isShowStructureInOutlineView()
								&& !hasOutlineViewer(fInput))) {
					fStructureInputPane.setInput(fInput);
				}
			} else {
				fStructureInputPane.setInput(fInput);
			}
			ISelection sel= fStructureInputPane.getSelection();
			if (sel == null || sel.isEmpty())
				feed1(sel);	// we only feed downstream viewers if the top left pane is empty
		}
	}

	private boolean hasOutlineViewer(Object input) {
		if (!isShowStructureInOutlineView())
			return false;
		OutlineViewerCreator creator = getAdapter(OutlineViewerCreator.class);
		if (creator != null)
			return creator.hasViewerFor(input);
		return false;
	}

	private boolean hasUnusableContentViewer() {
		return fContentInputPane.isEmpty() || fContentInputPane.getViewer() instanceof BinaryCompareViewer;
	}

	private boolean isCustomStructureInputPane() {
		return !(fStructureInputPane instanceof CompareViewerSwitchingPane);
	}

	private void feed1(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			() -> {
				if (selection == null || selection.isEmpty()) {
					Object input1= fStructureInputPane.getInput();
					if (input1 != null)
						internalSetContentPaneInput(input1);
					if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2))
						return;
					fStructurePane2.setInput(null); // clear downstream pane
					fStructurePane1.setInput(null);
				} else {
					Object input2= getElement(selection);
					internalSetContentPaneInput(input2);
					if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2))
						return;
					if (structureCompareOnSingleClick() || hasUnusableContentViewer())
						fStructurePane1.setInput(input2);
					fStructurePane2.setInput(null); // clear downstream pane
					if (fStructurePane1.getInput() != input2)
						fStructurePane1.setInput(null);
				}
			}
		);
	}

	private void feedDefault1(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			() -> {
				if (!selection.isEmpty())
					fStructurePane1.setInput(getElement(selection));
			}
		);
	}

	private void feed2(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			() -> {
				if (selection.isEmpty()) {
					Object input1= fStructurePane1.getInput();
					internalSetContentPaneInput(input1);
					fStructurePane2.setInput(null);
				} else {
					Object input2= getElement(selection);
					internalSetContentPaneInput(input2);
					fStructurePane2.setInput(input2);
				}
			}
		);
	}

	private void feed3(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			() -> {
				if (selection.isEmpty())
					internalSetContentPaneInput(fStructurePane2.getInput());
				else
					internalSetContentPaneInput(getElement(selection));
			}
		);

	}

	private void internalSetContentPaneInput(Object input) {
		Object oldInput = fContentInputPane.getInput();
		fContentInputPane.setInput(input);
		if (fOutlineView != null)
			fOutlineView.fireInputChange(oldInput, input);
	}

	/**
	 * Returns the first element of the given selection if the selection
	 * is a <code>IStructuredSelection</code> with exactly one element. Returns
	 * <code>null</code> otherwise.
	 *
	 * @param selection the selection
	 * @return the first element of the selection, or <code>null</code>
	 */
	private static Object getElement(ISelection selection) {
		if (selection instanceof IStructuredSelection) {
			IStructuredSelection ss= (IStructuredSelection) selection;
			if (ss.size() == 1)
				return ss.getFirstElement();
		}
		return null;
	}

	/**
	 * Asks this input to take focus within its container (editor).
	 *
	 * @noreference Clients should not call this method but they may override if
	 *              they implement a different layout with different visual
	 *              components. Clients are free to call the inherited method.
	 *
	 * @deprecated Please use {@link #setFocus2()} instead.
	 */
	@Deprecated
	public void setFocus() {
		setFocus2();
	}

	/**
	 * Asks this input to take focus within its container (editor).
	 *
	 * @noreference Clients should not call this method but they may override if
	 *              they implement a different layout with different visual
	 *              components. Clients are free to call the inherited method.
	 *
	 * @return <code>true</code> if the input got focus, and <code>false</code>
	 *         if it was unable to.
	 * @since 3.5
	 */
	public boolean setFocus2() {
		if (fFocusPane != null) {
			return fFocusPane.setFocus();
		} else if (fComposite != null)
			return fComposite.setFocus();
		return false;
	}

	/**
	 * Factory method for creating a differences viewer for the top left pane.
	 * It is called from <code>createContents</code> and returns a <code>DiffTreeViewer</code>.
	 * <p>
	 * Subclasses may override if they need a different viewer.
	 * </p>
	 *
	 * @param parent the SWT parent control under which to create the viewer's SWT controls
	 * @return a compare viewer for the top left pane
	 */
	public Viewer createDiffViewer(Composite parent) {
		return new DiffTreeViewer(parent, fCompareConfiguration);
	}

	/**
	 * Implements the dynamic viewer switching for structure viewers.
	 * The method must return a compare viewer based on the old (or current) viewer
	 * and a new input object. If the old viewer is suitable for showing the new input the old viewer
	 * can be returned. Otherwise a new viewer must be created under the given parent composite or
	 * <code>null</code> can be returned to indicate that no viewer could be found.
	 * <p>
	 * This implementation forwards the request to <code>CompareUI.findStructureViewer</code>.
	 * <p>
	 * Subclasses may override to implement a different strategy.
	 * </p>
	 * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
	 * @param input the input object for which to find a structure viewer
	 * @param parent the SWT parent composite under which the new viewer is created
	 * @return a compare viewer which is suitable for the given input object or <code>null</code>
	 */
	public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) {
		return fStructureViewerDescriptor != null ? fStructureViewerDescriptor.createViewer(oldViewer, parent,
				fCompareConfiguration) : CompareUI.findStructureViewer(oldViewer,
				input, parent, fCompareConfiguration);
	}

	/**
	 * Implements the dynamic viewer switching for content viewers.
	 * The method must return a compare viewer based on the old (or current) viewer
	 * and a new input object. If the old viewer is suitable for showing the new input the old viewer
	 * can be returned. Otherwise a new viewer must be created under the given parent composite or
	 * <code>null</code> can be returned to indicate that no viewer could be found.
	 * <p>
	 * This implementation forwards the request to <code>CompareUI.findContentViewer</code>.
	 * <p>
	 * Subclasses may override to implement a different strategy.
	 * </p>
	 * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
	 * @param input the input object for which to find a structure viewer
	 * @param parent the SWT parent composite under which the new viewer is created
	 * @return a compare viewer which is suitable for the given input object or <code>null</code>
	 */
	public Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent) {

		Viewer newViewer = fContentViewerDescriptor != null ? fContentViewerDescriptor.createViewer(oldViewer, parent,
				fCompareConfiguration) : CompareUI.findContentViewer(oldViewer,
				input, parent, fCompareConfiguration);

		boolean isNewViewer= newViewer != oldViewer;
		if (DEBUG) System.out.println("CompareEditorInput.findContentViewer: " + isNewViewer); //$NON-NLS-1$

		if (isNewViewer && newViewer instanceof IPropertyChangeNotifier) {
			final IPropertyChangeNotifier dsp= (IPropertyChangeNotifier) newViewer;
			dsp.addPropertyChangeListener(fDirtyStateListener);

			Control c= newViewer.getControl();
			c.addDisposeListener(
				e -> dsp.removePropertyChangeListener(fDirtyStateListener)
			);
		}

		return newViewer;
	}

	/**
	 * @param vd
	 *            the content viewer descriptor
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended
	 *             by clients.
	 */
	public void setContentViewerDescriptor(ViewerDescriptor vd) {
		this.fContentViewerDescriptor = vd;
	}

	/**
	 * @return the content viewer descriptor set for the input
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended
	 *             by clients.
	 */
	public ViewerDescriptor getContentViewerDescriptor() {
		return this.fContentViewerDescriptor;
	}

	/**
	 * @param vd
	 *            the structure viewer descriptor
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended
	 *             by clients.
	 */
	public void setStructureViewerDescriptor(ViewerDescriptor vd) {
		this.fStructureViewerDescriptor = vd;
	}

	/**
	 * @return the structure viewer descriptor set for the input
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended
	 *             by clients.
	 */
	public ViewerDescriptor getStructureViewerDescriptor() {
		return this.fStructureViewerDescriptor;
	}

	/**
	 * Returns <code>true</code> if there are unsaved changes in either left or
	 * right side. The value returned is the value of the
	 * <code>DIRTY_STATE</code> property of this input object.
	 *
	 * Returns <code>true</code> if left or right side has unsaved changes
	 * Subclasses don't have to override if the functionality provided by
	 * <code>setDirty</code> is sufficient.
	 *
	 * @return <code>true</code> if there are changes that need to be saved
	 */
	public boolean isSaveNeeded() {
		return isLeftSaveNeeded() || isRightSaveNeeded();
	}

	/**
	 * Returns <code>true</code> if there are unsaved changes for left side.
	 *
	 * @return <code>true</code> if there are changes that need to be saved
	 * @noreference This method is not intended to be referenced by clients.
	 */
	protected boolean isLeftSaveNeeded() {
		return fLeftDirty;
	}

	/**
	 * Returns <code>true</code> if there are unsaved changes for right side.
	 *
	 * @return <code>true</code> if there are changes that need to be saved
	 * @noreference This method is not intended to be referenced by clients.
	 */
	protected boolean isRightSaveNeeded() {
		return fRightDirty;
	}

	/**
	 * Returns <code>true</code> if there are unsaved changes.
	 * The method should be called by any parts or dialogs
	 * that contain the input.
	 * By default, this method calls {@link #isSaveNeeded()}
	 * but subclasses may extend.
	 * @return <code>true</code> if there are unsaved changes
	 * @since 3.3
	 */
	public boolean isDirty() {
		return isSaveNeeded();
	}

	/**
	 * Sets the dirty state of this input to the given value and sends out a
	 * <code>PropertyChangeEvent</code> if the new value differs from the old
	 * value. Direct calling this method with parameter dirty equal to
	 * <code>false</code> when there are unsaved changes in viewers, results in
	 * inconsistent state. The dirty state of compare input should be based only
	 * on the information if there are changes in viewers for left or right
	 * side.
	 *
	 * @param dirty
	 *            the dirty state for this compare input
	 */
	public void setDirty(boolean dirty) {
		boolean oldDirty = isSaveNeeded();
		fLeftDirty = dirty;
		fRightDirty = dirty;

		if (oldDirty != isSaveNeeded()) {
			Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty), Boolean.valueOf(isSaveNeeded()));
		}
	}

	/**
	 * Sets the dirty state of left site of this input to the given value and
	 * sends out a <code>PropertyChangeEvent</code> if the new value for whole
	 * input differs from the old value. Direct calling this method with
	 * parameter dirty equal to <code>false</code> when there are unsaved
	 * changes in left viewer, results in inconsistent state. The dirty state of
	 * compare input should be based only on the information if there are
	 * changes in viewers for left side.
	 *
	 * @param dirty
	 *            the dirty state for this compare input
	 * @since 3.7
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended
	 *             by clients.
	 */
	protected void setLeftDirty(boolean dirty) {
		boolean oldDirty = isSaveNeeded();
		fLeftDirty = dirty;

		if (oldDirty != isSaveNeeded()) {
			Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE,
					Boolean.valueOf(oldDirty), Boolean.valueOf(isSaveNeeded()));
		}
	}

	/**
	 * Sets the dirty state of right site of this input to the given value and
	 * sends out a <code>PropertyChangeEvent</code> if the new value for whole
	 * input differs from the old value. Direct calling this method with
	 * parameter dirty equal to <code>false</code> when there are unsaved
	 * changes in right viewer, results in inconsistent state. The dirty state
	 * of compare input should be based only on the information if there are
	 * changes in viewers for right side.
	 *
	 * @param dirty
	 *            the dirty state for this compare input
	 * @since 3.7
	 * @noreference This method is not intended to be referenced by clients.
	 * @nooverride This method is not intended to be re-implemented or extended
	 *             by clients.
	 */
	protected void setRightDirty(boolean dirty) {
		boolean oldDirty = isSaveNeeded();
		fRightDirty = dirty;

		if (oldDirty != isSaveNeeded()) {
			Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE,
					Boolean.valueOf(oldDirty), Boolean.valueOf(isSaveNeeded()));
		}
	}

	/**
	 * Method adds or removes viewers that changed left or right side of this
	 * compare input. Any modification of any of the list of viewers may result
	 * in dirty state change.
	 *
	 * @param source
	 *            the object that fired <code>PropertyChangeEvent</code>
	 *            modifying the dirty state
	 * @param dirty
	 *            value that describes if the changes were added or removed
	 */
	private void setDirty(Object source, boolean dirty) {
		Assert.isNotNull(source);
		boolean oldDirty = isSaveNeeded();

		if (source instanceof ContentMergeViewer) {
			ContentMergeViewer cmv = (ContentMergeViewer) source;

			if (dirty == cmv.internalIsLeftDirty()) {
				fLeftDirty = cmv.internalIsLeftDirty();
			}

			if (dirty == cmv.internalIsRightDirty()) {
				fRightDirty = cmv.internalIsRightDirty();
			}
		} else {
			fLeftDirty = dirty;
		}

		boolean newDirty = isSaveNeeded();
		if (DEBUG) {
			System.out.println("setDirty(" + source + ", " + dirty + "): " + newDirty); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		if (oldDirty != newDirty) {
			Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty), Boolean.valueOf(newDirty));
		}
	}

	@Override
	public void addPropertyChangeListener(IPropertyChangeListener listener) {
		if (listener != null)
			fListenerList.add(listener);
	}

	@Override
	public void removePropertyChangeListener(IPropertyChangeListener listener) {
		if (fListenerList != null) {
			fListenerList.remove(listener);
		}
	}

	/**
	 * Saves any unsaved changes.
	 * Empty implementation.
	 * Subclasses must override to save any changes.
	 *
	 * @param pm an <code>IProgressMonitor</code> that the implementation of save may use to show progress
	 * @deprecated Override method saveChanges instead.
	 */
	@Deprecated
	public void save(IProgressMonitor pm) {
		// empty default implementation
	}

	/**
	 * Saves any unsaved changes. Subclasses must override to save any changes. This
	 * implementation tries to flush changes in all viewers by calling
	 * <code>ISavable.save</code> on them.
	 *
	 * @param monitor an <code>IProgressMonitor</code> that the implementation of
	 *                save may use to show progress
	 * @throws CoreException If save fails.
	 * @since 2.0
	 */
	public void saveChanges(IProgressMonitor monitor) throws CoreException {
		flushViewers(monitor);

		save(monitor);
	}

	/**
	 * Flushes the viewer contents into the input.
	 * @param monitor a progress monitor
	 * @since 3.3
	 */
	protected void flushViewers(IProgressMonitor monitor) {
		// flush changes in any dirty viewer
		flushViewer(fStructureInputPane, monitor);
		flushViewer(fStructurePane1, monitor);
		flushViewer(fStructurePane2, monitor);
		flushViewer(fContentInputPane, monitor);
	}

	/**
	 * @param monitor a progress monitor
	 * @noreference This method is not intended to be referenced by clients.
	 */
	protected void flushLeftViewers(IProgressMonitor monitor) {
		// flush changes in left dirty viewer
		flushViewer(fStructureInputPane, monitor);
		flushViewer(fStructurePane1, monitor);
		flushViewer(fStructurePane2, monitor);
		flushLeftViewer(fContentInputPane, monitor);
	}

	/**
	 * @param monitor a progress monitor
	 * @noreference This method is not intended to be referenced by clients.
	 */
	protected void flushRightViewers(IProgressMonitor monitor) {
		// flush changes in right dirty viewer
		flushViewer(fStructureInputPane, monitor);
		flushViewer(fStructurePane1, monitor);
		flushViewer(fStructurePane2, monitor);
		flushRightViewer(fContentInputPane, monitor);
	}

	private static void flushViewer(CompareViewerPane pane, IProgressMonitor pm) {
		if (pane != null) {
			IFlushable flushable = Adapters.adapt(pane, IFlushable.class);
			if (flushable != null)
				flushable.flush(pm);
		}
	}

	private static void flushLeftViewer(CompareViewerPane pane, IProgressMonitor pm) {
		if (pane != null) {
			IFlushable2 flushable = Adapters.adapt(pane, IFlushable2.class);
			if (flushable != null)
				flushable.flushLeft(pm);
		}
	}

	private static void flushRightViewer(CompareViewerPane pane, IProgressMonitor pm) {
		if (pane != null) {
			IFlushable2 flushable = Adapters.adapt(pane, IFlushable2.class);
			if (flushable != null)
				flushable.flushRight(pm);
		}
	}

	@Override
	public void addCompareInputChangeListener(ICompareInput input,
			ICompareInputChangeListener listener) {
		if (fContainer == null) {
			input.addCompareInputChangeListener(listener);
		} else {
			fContainer.addCompareInputChangeListener(input, listener);
		}
	}

	@Override
	public void removeCompareInputChangeListener(ICompareInput input,
			ICompareInputChangeListener listener) {
		if (fContainer == null) {
			input.removeCompareInputChangeListener(listener);
		} else {
			fContainer.removeCompareInputChangeListener(input, listener);
		}
	}

	@Override
	public void registerContextMenu(MenuManager menu, ISelectionProvider selectionProvider) {
		if (fContainer != null)
			fContainer.registerContextMenu(menu, selectionProvider);
	}

	@Override
	public void setStatusMessage(String message) {
		if (!fContainerProvided) {
			// Try the action bars directly
			IActionBars actionBars= getActionBars();
			if (actionBars != null) {
				IStatusLineManager slm= actionBars.getStatusLineManager();
				if (slm != null) {
					slm.setMessage(message);
				}
			}
		} else if (fContainer != null) {
			fContainer.setStatusMessage(message);
		}
	}

	@Override
	public IActionBars getActionBars() {
		if (fContainer != null) {
			IActionBars actionBars = fContainer.getActionBars();
			if (actionBars == null && !fContainerProvided) {
				// The old way to find the action bars
				return Utilities.findActionBars(fComposite);
			}
			return actionBars;
		}
		return null;
	}

	@Override
	public IServiceLocator getServiceLocator() {
		IServiceLocator serviceLocator = fContainer.getServiceLocator();
		if (serviceLocator == null && !fContainerProvided) {
			// The old way to find the service locator
			return Utilities.findSite(fComposite);
		}
		return serviceLocator;
	}

	@Override
	public IWorkbenchPart getWorkbenchPart() {
		if (fContainer != null)
			return fContainer.getWorkbenchPart();
		return null;
	}

	@Override
	public void run(boolean fork, boolean cancelable,
			IRunnableWithProgress runnable) throws InvocationTargetException,
			InterruptedException {
		if (fContainer != null)
			fContainer.run(fork, cancelable, runnable);
	}

	@Override
	public void runAsynchronously(IRunnableWithProgress runnable) {
		if (fContainer != null)
			fContainer.runAsynchronously(runnable);
	}

	/**
	 * Sets the container of this input to the given container
	 * @param container the container
	 * @since 3.3
	 */
	public void setContainer(ICompareContainer container) {
		Assert.isNotNull(container);
		this.fContainer = container;
		fContainerProvided = true;
	}

	/**
	 * Returns the container of this input or <code>null</code> if there is no container
	 * set.
	 * @return the container of this input or <code>null</code>
	 * @since 3.3
	 */
	public final ICompareContainer getContainer() {
		return fContainer;
	}

	/**
	 * Fires the given property change event to all listeners
	 * registered with this compare editor input.
	 * @param event the property change event
	 * @since 3.3
	 */
	protected void firePropertyChange(PropertyChangeEvent event) {
		Utilities.firePropertyChange(fListenerList, event);
	}

	/**
	 * Returns whether this compare editor input can be run as a job.
	 * By default, <code>false</code> is returned since traditionally inputs
	 * were prepared in the foreground (i.e the UI was blocked when the
	 * {@link #run(IProgressMonitor)} method (and indirectly the
	 * {@link #prepareInput(IProgressMonitor)} method) was invoked. Subclasses
	 * may override.
	 * @return whether this compare editor input can be run in the background
	 * @since 3.3
	 */
	public boolean canRunAsJob() {
		return false;
	}

	/**
	 * Returns whether this input belongs to the given family
	 * when it is run as a job.
	 * @see #canRunAsJob()
	 * @see Job#belongsTo(Object)
	 * @param family the job family
	 * @return whether this input belongs to the given family
	 * @since 3.3
	 */
	public boolean belongsTo(Object family) {
		return family == this;
	}

	/**
	 * Returns whether this input is intended to be used to select
	 * a particular edition of an element in a dialog. The result
	 * of this method is only consider if neither sides of the
	 * input are editable. By default, <code>false</code> is returned.
	 * @return whether this input is intended to be used to select
	 * a particular edition of an element in a dialog
	 * @see #getOKButtonLabel()
	 * @see #okPressed()
	 * @see #getSelectedEdition()
	 * @since 3.3
	 */
	public boolean isEditionSelectionDialog() {
		return false;
	}

	/**
	 * Returns the label to be used for the <code>OK</code>
	 * button when this input is displayed in a dialog.
	 * By default, different labels are used depending on
	 * whether the input is editable or is for edition selection
	 * (see {@link #isEditionSelectionDialog()}.
	 * @return the label to be used for the <code>OK</code>
	 * button when this input is displayed in a dialog
	 * @since 3.3
	 */
	public String getOKButtonLabel() {
		if (isEditable())
			return CompareMessages.CompareDialog_commit_button;
		if (isEditionSelectionDialog())
			return CompareMessages.CompareEditorInput_0;
		return IDialogConstants.OK_LABEL;
	}

	/**
	 * Returns the label used for the <code>CANCEL</code>
	 * button when this input is shown in a compare dialog
	 * using {@link CompareUI#openCompareDialog(CompareEditorInput)}.
	 * @return the label used for the <code>CANCEL</code> button
	 * @since 3.3
	 */
	public String getCancelButtonLabel() {
		return IDialogConstants.CANCEL_LABEL;
	}

	private boolean isEditable() {
		return getCompareConfiguration().isLeftEditable()
			|| getCompareConfiguration().isRightEditable();
	}

	/**
	 * The <code>OK</code> button was pressed in a dialog. If one or both of
	 * the sides of the input is editable then any changes will be saved. If the
	 * input is for edition selection (see {@link #isEditionSelectionDialog()}),
	 * it is up to subclasses to override this method in order to perform the
	 * appropriate operation on the selected edition.
	 *
	 * @return whether the dialog should be closed or not.
	 * @since 3.3
	 */
	public boolean okPressed() {
		if (isEditable()) {
			if (!saveChanges())
				return false;
		}
		return true;
	}

	/**
	 * The <code>CANCEL</code> button was pressed in a dialog.
	 * By default, nothing is done. Subclasses may override.
	 * @since 3.3
	 */
	public void cancelPressed() {
		// Do nothing
	}

	private boolean saveChanges() {
		try {
			Utilities.executeRunnable(monitor -> {
				try {
					saveChanges(monitor);
				} catch (CoreException e) {
					throw new InvocationTargetException(e);
				}
			});
			return true;
		} catch (InterruptedException | OperationCanceledException x) {
			// Ignore
		} catch (InvocationTargetException x) {
			ErrorDialog.openError(fComposite.getShell(), CompareMessages.CompareDialog_error_title, null,
				new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0,
					NLS.bind(CompareMessages.CompareDialog_error_message, x.getTargetException().getMessage()), x.getTargetException()));
		}
		return false;
	}

	/**
	 * Returns the selected edition or <code>null</code> if no edition is selected.
	 * The result of this method should only be considered if {@link #isEditionSelectionDialog()}
	 * returns <code>true</code>.
	 * @return the selected edition or <code>null</code>
	 * @since 3.3
	 */
	public Object getSelectedEdition() {
		if (fStructureInputPane != null) {
			ISelection selection = fStructureInputPane.getSelection();
			if (selection instanceof IStructuredSelection) {
				IStructuredSelection ss = (IStructuredSelection) selection;
				if (!ss.isEmpty())
					return ss.getFirstElement();

			}
		}
		return null;
	}

	/**
	 * Sets the help context id for this input.
	 * @param helpContextId the help context id.
	 * @since 3.3
	 */
	public void setHelpContextId(String helpContextId) {
		this.fHelpContextId = helpContextId;
	}
}
