/**
* <copyright>
*
* Copyright (c) 2010-2016 Thales Global Services S.A.S.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Thales Global Services S.A.S. - initial API and implementation
*
* </copyright>
*/
package org.eclipse.emf.diffmerge.ui.setup;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.compare.ITypedElement;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin;
import org.eclipse.emf.diffmerge.ui.Messages;
import org.eclipse.emf.diffmerge.ui.specification.IComparisonMethod;
import org.eclipse.emf.diffmerge.ui.specification.IComparisonMethodFactory;
import org.eclipse.emf.diffmerge.ui.specification.IModelScopeDefinition;
import org.eclipse.emf.diffmerge.ui.specification.IModelScopeDefinitionFactory;
import org.eclipse.emf.diffmerge.ui.specification.IOverridableFactory;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Shell;
/**
* A manager for model comparison setups that are contributed through the dedicated
* extension point.
* @author Olivier Constant
*/
public class ComparisonSetupManager {
/** The ModelComparisonContext extension point */
private static final String MODEL_COMPARISON_CONTEXT_EXTENSION_POINT =
"org.eclipse.emf.diffmerge.ui.modelComparisonContext"; //$NON-NLS-1$
/** The ModelComparisonContext extension point node for configurations */
private static final String EXTENSION_POINT_METHOD = "comparisonMethod"; //$NON-NLS-1$
/** The ModelComparisonContext extension point property for configurations */
private static final String EXTENSION_POINT_PROPERTY_METHOD = "factory"; //$NON-NLS-1$
/** The ModelComparisonContext extension point node for scopes */
private static final String EXTENSION_POINT_SCOPE = "scopeDefinition"; //$NON-NLS-1$
/** The ModelComparisonContext extension point property for scopes */
private static final String EXTENSION_POINT_PROPERTY_SCOPE = "factory"; //$NON-NLS-1$
/** The registered comparison method factories (initially null) */
private Map<Class<?>, IComparisonMethodFactory> _comparisonFactories;
/** The registered scope factories (initially null) */
private Map<Class<?>, IModelScopeDefinitionFactory> _scopeFactories;
/**
* Constructor
* This should normally not be used by clients: get {@link EMFDiffMergeUIPlugin#getDefault()}
* and use {@link EMFDiffMergeUIPlugin#getSetupManager()} instead.
*/
public ComparisonSetupManager() {
_comparisonFactories = null;
_scopeFactories = null;
}
/**
* Return a default compare editor input for the given entry points
* @param entrypoint1_p a non-null object
* @param entrypoint2_p a non-null object
* @param entrypoint3_p an optional object
* @return a potentially null object (null means failure)
*/
public EMFDiffMergeEditorInput createDefaultEditorInput(Object entrypoint1_p,
Object entrypoint2_p, Object entrypoint3_p) {
EMFDiffMergeEditorInput result = null;
try {
ComparisonSetup setup =
createComparisonSetup(entrypoint1_p, entrypoint2_p, entrypoint3_p);
if (setup != null && setup.getComparisonMethod() != null)
result = new EMFDiffMergeEditorInput(setup.getComparisonMethod());
} catch (IllegalArgumentException e) {
handleSetupError(null, e.getLocalizedMessage());
}
return result;
}
/**
* Create and return a compare editor input as a result of user interactions
* for the given entry points, if possible
* @param shell_p a non-null shell
* @param entrypoint1_p a non-null object
* @param entrypoint2_p a non-null object
* @param entrypoint3_p an optional object
*/
public EMFDiffMergeEditorInput createEditorInputWithUI(Shell shell_p, Object entrypoint1_p,
Object entrypoint2_p, Object entrypoint3_p) {
EMFDiffMergeEditorInput result = null;
try {
ComparisonSetupManager manager = EMFDiffMergeUIPlugin.getDefault().getSetupManager();
ComparisonSetup setup = manager.createComparisonSetup(
entrypoint1_p, entrypoint2_p, entrypoint3_p);
result = createEditorInputWithUI(shell_p, setup);
} catch (IllegalArgumentException e) {
handleSetupError(shell_p, e.getLocalizedMessage());
}
return result;
}
/**
* Create and return a compare editor input as a result of user interactions
* for the given comparison setup, if possible
* @param shell_p a non-null shell
* @param setup_p a comparison setup or null if none could be computed
*/
public EMFDiffMergeEditorInput createEditorInputWithUI(Shell shell_p, ComparisonSetup setup_p) {
EMFDiffMergeEditorInput result = null;
if (setup_p != null) {
ComparisonSetupWizard wizard = new ComparisonSetupWizard(setup_p);
WizardDialog dialog = new WizardDialog(shell_p, wizard);
dialog.setHelpAvailable(false);
if (Window.OK == dialog.open()) {
IComparisonMethod method = setup_p.getComparisonMethod();
if (method != null)
result = new EMFDiffMergeEditorInput(method);
}
} else {
handleSetupError(shell_p, null);
}
return result;
}
/**
* Return a comparison setup for the given entry points
* @param entrypoint1_p a non-null object
* @param entrypoint2_p a non-null object
* @param entrypoint3_p an optional object
* @throws IllegalArgumentException if the entry points are not supported
* @return a potentially null object (null means failure)
*/
public ComparisonSetup createComparisonSetup(Object entrypoint1_p,
Object entrypoint2_p, Object entrypoint3_p) {
ComparisonSetup result = null;
List<IModelScopeDefinitionFactory> factories1 =
getApplicableModelScopeFactories(entrypoint1_p);
List<IModelScopeDefinitionFactory> factories2 =
getApplicableModelScopeFactories(entrypoint2_p);
List<IModelScopeDefinitionFactory> factories3 = entrypoint3_p == null?
Collections.<IModelScopeDefinitionFactory>emptyList():
getApplicableModelScopeFactories(entrypoint3_p);
if (!factories1.isEmpty() && !factories2.isEmpty()) {
try {
IModelScopeDefinition scopeSpec1 =
factories1.get(0).createScopeDefinition(entrypoint1_p, null, true);
IModelScopeDefinition scopeSpec2 =
factories2.get(0).createScopeDefinition(entrypoint2_p, null, true);
IModelScopeDefinition scopeSpec3 = factories3.isEmpty()? null:
factories3.get(0).createScopeDefinition(entrypoint3_p, null, true);
List<IComparisonMethodFactory> cFactories =
getApplicableComparisonMethodFactories(scopeSpec1, scopeSpec2, scopeSpec3);
if (!cFactories.isEmpty())
result = new ComparisonSetup(scopeSpec1, scopeSpec2, scopeSpec3, cFactories);
} catch (Exception e) {
EMFDiffMergeUIPlugin.getDefault().getLog().log(new Status(
IStatus.ERROR, EMFDiffMergeUIPlugin.getDefault().getPluginId(), e.getMessage(), e));
}
} else {
handleUnsupportedEntrypoints(
factories1.isEmpty()? entrypoint1_p: null,
factories2.isEmpty()? entrypoint2_p: null);
}
return result;
}
/**
* Discover the comparison contexts which are registered through the dedicated
* extension point, if any
* Postcondition: _comparisonFactories != null && _scopeFactories != null
*/
protected void discoverRegisteredComparisonContexts() {
_comparisonFactories = new HashMap<Class<?>, IComparisonMethodFactory>();
_scopeFactories = new HashMap<Class<?>, IModelScopeDefinitionFactory>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IConfigurationElement[] config = registry.getConfigurationElementsFor(
MODEL_COMPARISON_CONTEXT_EXTENSION_POINT);
for (IConfigurationElement e : config) {
String name = e.getName();
if (EXTENSION_POINT_METHOD.equals(name)) {
try {
Object o = e.createExecutableExtension(EXTENSION_POINT_PROPERTY_METHOD);
if (o instanceof IComparisonMethodFactory)
_comparisonFactories.put(o.getClass(), (IComparisonMethodFactory)o);
} catch (CoreException ex) {
// Proceed
}
} else if (EXTENSION_POINT_SCOPE.equals(name)) {
try {
Object o = e.createExecutableExtension(EXTENSION_POINT_PROPERTY_SCOPE);
if (o instanceof IModelScopeDefinitionFactory)
_scopeFactories.put(o.getClass(), (IModelScopeDefinitionFactory)o);
} catch (CoreException ex) {
// Proceed
}
}
}
}
/**
* Return the set of comparison method factories that are applicable to the given
* entry point
* @param leftScopeSpec_p a non-null scope definition
* @param rightScopeSpec_p a non-null scope definition
* @param ancestorScopeSpec_p an optional scope definition
* @return a non-null, potentially empty, unmodifiable list
*/
public List<IComparisonMethodFactory> getApplicableComparisonMethodFactories(
IModelScopeDefinition leftScopeSpec_p, IModelScopeDefinition rightScopeSpec_p,
IModelScopeDefinition ancestorScopeSpec_p) {
List<IComparisonMethodFactory> result = new ArrayList<IComparisonMethodFactory>();
for (IComparisonMethodFactory factory : getRegisteredComparisonMethodFactories()) {
if (factory.isApplicableTo(leftScopeSpec_p, rightScopeSpec_p, ancestorScopeSpec_p))
result.add(factory);
}
result = reduceByOverride(result, _comparisonFactories);
return result;
}
/**
* Return the set of scope definition factories that are applicable to the given
* entry point
* @param entrypoint_p a non-null object
* @return a non-null, unmodifiable list which cannot be empty if isValidEntrypoint(entrypoint_p)
*/
public List<IModelScopeDefinitionFactory> getApplicableModelScopeFactories(
Object entrypoint_p) {
List<IModelScopeDefinitionFactory> result = new ArrayList<IModelScopeDefinitionFactory>();
for (IModelScopeDefinitionFactory factory : getRegisteredModelScopeDefinitionFactories()) {
if (factory.isApplicableTo(entrypoint_p))
result.add(factory);
}
result = reduceByOverride(result, _scopeFactories);
return Collections.unmodifiableList(result);
}
/**
* Return the comparison method factories which are registered through the dedicated
* extension point, if any
* @return a non-null, potentially empty list
*/
protected final Collection<IComparisonMethodFactory> getRegisteredComparisonMethodFactories() {
if (_comparisonFactories == null)
discoverRegisteredComparisonContexts();
return _comparisonFactories.values();
}
/**
* Return the scope definition factories which are registered through the dedicated
* extension point, if any
* @return a non-null, potentially empty list
*/
protected final Collection<IModelScopeDefinitionFactory> getRegisteredModelScopeDefinitionFactories() {
if (_scopeFactories == null)
discoverRegisteredComparisonContexts();
return _scopeFactories.values();
}
/**
* Handle the given user-level setup error message
* @param shell_p an optional shell (if null, error message is being logged)
* @param message_p an optional message (if null, replaced by a default setup error message)
*/
public void handleSetupError(Shell shell_p, String message_p) {
String message = (message_p != null)? message_p: Messages.CompareModelsAction_ModelsOnly;
if (shell_p != null) {
MessageDialog.openError(shell_p, EMFDiffMergeUIPlugin.LABEL, message);
} else {
EMFDiffMergeUIPlugin.getDefault().getLog().log(new Status(
IStatus.ERROR, EMFDiffMergeUIPlugin.getDefault().getPluginId(), message, null));
}
}
/**
* Handle the fact that the given left and/or right entry points are not supported by
* available scope factories
* @param unsupportedLeft_p an optional entry point for the left-hand side
* @param unsupportedRight_p an optional entry point for the right-hand side
*/
protected void handleUnsupportedEntrypoints(Object unsupportedLeft_p, Object unsupportedRight_p) {
StringBuilder msgBuilder = new StringBuilder();
boolean leftKO = unsupportedLeft_p != null;
boolean rightKO = unsupportedRight_p != null;
if (leftKO) {
String leftMessage;
if (unsupportedLeft_p instanceof ITypedElement) {
String leftName = ((ITypedElement)unsupportedLeft_p).getName();
leftMessage = String.format(Messages.ComparisonSetupManager_CannotLoadLeftKnown, leftName);
} else {
leftMessage = Messages.ComparisonSetupManager_CannotLoadLeftUnknown;
}
msgBuilder.append(leftMessage);
}
if (leftKO && rightKO)
msgBuilder.append('\n');
if (rightKO) {
String rightMessage;
if (unsupportedRight_p instanceof ITypedElement) {
String rightName = ((ITypedElement)unsupportedRight_p).getName();
rightMessage = String.format(Messages.ComparisonSetupManager_CannotLoadRightKnown, rightName);
} else {
rightMessage = Messages.ComparisonSetupManager_CannotLoadRightUnknown;
}
msgBuilder.append(rightMessage);
}
throw new IllegalArgumentException(msgBuilder.toString());
}
/**
* Return whether the given object is a valid entry point for a comparison, i.e.,
* whether a model scope can be derived from it
* @param entrypoint_p a non-null object
*/
public boolean isValidEntrypoint(Object entrypoint_p) {
for (IModelScopeDefinitionFactory factory : getRegisteredModelScopeDefinitionFactories()) {
if (factory.isApplicableTo(entrypoint_p))
return true;
}
return false;
}
/**
* Return the reduced version of the given list of factories based on the "override" relation
* @param factories_p a non-null, potentially empty list
* @param configurationFactories_p a map from factory classes to the corresponding registered instances
* @return a non-null, unmodifiable list which is not empty if factories_p is not empty
*/
protected <T extends IOverridableFactory> List<T> reduceByOverride(List<T> factories_p,
Map<Class<?>, T> configurationFactories_p) {
List<T> result = new ArrayList<T>(factories_p);
for (T factory : factories_p) {
for (Class<?> factoryClass : factory.getOverridenClasses()) {
result.remove(configurationFactories_p.get(factoryClass));
}
}
return Collections.unmodifiableList(result);
}
}