/** * <copyright> * * Copyright (c) 2013-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.impl.policies; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.emf.diffmerge.api.scopes.IFeaturedModelScope; import org.eclipse.emf.diffmerge.api.scopes.IModelScope; import org.eclipse.emf.diffmerge.util.structures.comparable.ComparableLinkedList; import org.eclipse.emf.diffmerge.util.structures.comparable.ComparableTreeMap; import org.eclipse.emf.diffmerge.util.structures.comparable.IComparableStructure; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.Resource; /** * A multi-criteria match policy that supports the computation of the match ID * of certain elements according to the match ID of other elements. * @author Olivier Constant */ public class ConfigurableMatchPolicy extends DefaultMatchPolicy { /** * A predefined set of criteria for matching. */ public static enum MatchCriterionKind { /** * The 'semantics' criterion kind: support for semantical criteria. */ SEMANTICS, /** * The 'structure' criterion kind: support for structure-based criteria. */ STRUCTURE, /** * The 'name' criterion kind: support for qualified names. */ NAME, /** * The 'intrinsic ID' criterion kind: support for Ecore unique IDs. */ INTRINSIC_ID, /** * The 'extrinsic ID' criterion kind: support for persistence-related IDs such as XMI IDs. */ EXTRINSIC_ID } /** The set of match criteria to use */ private final Set<MatchCriterionKind> _selectedCriteria; /** * Constructor */ public ConfigurableMatchPolicy() { _selectedCriteria = new HashSet<MatchCriterionKind>( MatchCriterionKind.values().length); _selectedCriteria.addAll(getDefaultCriteria()); } /** * Return the set of applicable match criteria in decreasing priority * @return a non-null collection */ public Collection<MatchCriterionKind> getApplicableCriteria() { return Arrays.asList( MatchCriterionKind.STRUCTURE, MatchCriterionKind.NAME, MatchCriterionKind.INTRINSIC_ID, MatchCriterionKind.EXTRINSIC_ID); } /** * Return a match ID for the given element from the given scope * based on the ID of its container and the given match criterion * @param element_p a non-null element * @param scope_p a non-null scope that covers the element * @param inScopeOnly_p whether only the scope may be considered, or the underlying EMF model * @param criterion_p a match criterion which is NAME or STRUCTURE * @return a potentially null object */ protected IComparableStructure<?> getContainerRelativeID(EObject element_p,IModelScope scope_p, boolean inScopeOnly_p, MatchCriterionKind criterion_p) { IComparableStructure<?> result = null; String lastIDPart; if (criterion_p == MatchCriterionKind.STRUCTURE) lastIDPart = getStructureMatchIDPart(element_p, scope_p, inScopeOnly_p); else lastIDPart = getUniqueName(element_p, scope_p, inScopeOnly_p); if (lastIDPart != null && lastIDPart.length() > 0) result = getContainerRelativeID(element_p, scope_p, inScopeOnly_p, lastIDPart); return result; } /** * Return a match ID for the given element from the given scope * based on the ID of its container and the given ID suffix * @param element_p a non-null element * @param scope_p a non-null scope that covers the element * @param inScopeOnly_p whether only the scope may be considered, or the underlying EMF model * @param idSuffix_p a non-null suffix for the ID that identifies the element within its container * @return a potentially null object */ @SuppressWarnings("unchecked") protected IComparableStructure<?> getContainerRelativeID(EObject element_p, IModelScope scope_p, boolean inScopeOnly_p, String idSuffix_p) { IComparableStructure<?> result = null; EObject container = getContainer(element_p, scope_p, inScopeOnly_p); if (container != null) { IComparableStructure<?> containerID = getMatchID(container, scope_p); if (containerID instanceof ComparableLinkedList<?>) { result = containerID; ((ComparableLinkedList<String>)result).add(idSuffix_p); } else if (containerID != null) { IComparableStructure<String> typeID = getEncapsulateOrNull( element_p.getClass().getName()); IComparableStructure.IComparableMap<String, IComparableStructure<?>> id = new ComparableTreeMap<String, IComparableStructure<?>>(); id.put("CONTAINER_RELATIVE_ID_TYPE", typeID); //$NON-NLS-1$ id.put("CONTAINER_ID", containerID); //$NON-NLS-1$ id.put("ELEMENT_ID", getEncapsulateOrNull(idSuffix_p)); //$NON-NLS-1$ result = id; } } else { // Root ComparableLinkedList<String> id = new ComparableLinkedList<String>(); id.add(idSuffix_p); result = id; } return result; } /** * Return the container of the given element within the given scope * @param element_p a non-null element * @param scope_p a non-null scope * @param inScopeOnly_p whether only the scope may be considered, or the underlying EMF model * @return a potentially null reference */ protected EObject getContainer(EObject element_p, IModelScope scope_p, boolean inScopeOnly_p) { EObject result; if (inScopeOnly_p) { if (scope_p instanceof IFeaturedModelScope) result = ((IFeaturedModelScope)scope_p).getContainer(element_p); else result = null; } else { result = element_p.eContainer(); } return result; } /** * Return the containment reference of the given element within the given scope * @param element_p a non-null element * @param scope_p a non-null scope * @param inScopeOnly_p whether only the scope may be considered, or the underlying EMF model * @return a potentially null reference */ protected EReference getContainment(EObject element_p, IModelScope scope_p, boolean inScopeOnly_p) { EReference result; if (inScopeOnly_p) { if (scope_p instanceof IFeaturedModelScope) result = ((IFeaturedModelScope)scope_p).getContainment(element_p); else result = null; } else { result = element_p.eContainmentFeature(); } return result; } /** * Return the set of default match criteria among the applicable ones * @return a non-null collection */ public Collection<MatchCriterionKind> getDefaultCriteria() { return Arrays.asList(MatchCriterionKind.INTRINSIC_ID, MatchCriterionKind.EXTRINSIC_ID); } /** * Return the given object within a comparable structure, or null if the object is null * @param <T> the type of the given object * @param object_p a potentially null object * @return a potentially null comparable structure */ protected <T extends Comparable<T>> ComparableLinkedList<T> getEncapsulateOrNull(T object_p) { ComparableLinkedList<T> result = null; if (object_p != null) { result = new ComparableLinkedList<T>(); result.add(object_p); } return result; } /** * @see org.eclipse.emf.diffmerge.impl.policies.DefaultMatchPolicy#getExtrinsicID(org.eclipse.emf.ecore.EObject, org.eclipse.emf.diffmerge.api.scopes.IModelScope) */ @Override protected String getExtrinsicID(EObject element_p, IModelScope scope_p) { String result = null; Comparable<?> superID = super.getExtrinsicID(element_p, scope_p); if (superID instanceof String) result = (String)superID; return result; } /** * @see org.eclipse.emf.diffmerge.impl.policies.DefaultMatchPolicy#getMatchID(org.eclipse.emf.ecore.EObject, org.eclipse.emf.diffmerge.api.scopes.IModelScope) */ @Override public IComparableStructure<?> getMatchID(EObject element_p, IModelScope scope_p) { // Intended return types: IComparableStructure<String>, // ComparableTreeMap<String, IComparableStructure<String>> IComparableStructure<?> result = null; Iterator<MatchCriterionKind> it = getApplicableCriteria().iterator(); while (result == null && it.hasNext()) { MatchCriterionKind criterion = it.next(); if (useMatchCriterion(criterion)) result = getMatchID(element_p, scope_p, criterion); } return result; } /** * Return a match ID for the given element from the given scope according * to the given criterion * @param element_p a non-null element * @param scope_p a non-null scope * @param criterion_p a non-null criterion * @return a potentially null object */ public IComparableStructure<?> getMatchID(EObject element_p, IModelScope scope_p, MatchCriterionKind criterion_p) { IComparableStructure<?> result; switch (criterion_p) { case EXTRINSIC_ID: result = getEncapsulateOrNull(getExtrinsicID(element_p, scope_p)); break; case INTRINSIC_ID: result = getEncapsulateOrNull(getIntrinsicID(element_p)); break; case NAME: case STRUCTURE: result = getContainerRelativeID(element_p, scope_p, isScopeOnly(), criterion_p); break; default: result = getSemanticID(element_p, scope_p, isScopeOnly()); break; } return result; } /** * Return the name of the given element, unique within the container if possible * @param element_p a non-null element * @param scope_p a non-null scope that covers the element * @param inScopeOnly_p whether only the scope should be considered or the underlying EMF model * @return a potentially null object */ protected String getUniqueName(EObject element_p, IModelScope scope_p, boolean inScopeOnly_p) { String result = null; if (element_p instanceof ENamedElement) result = ((ENamedElement)element_p).getName(); return result; } /** * Return a semantic-based match ID for the given element from the given scope * @param element_p a non-null element * @param scope_p a non-null scope that covers the element * @param inScopeOnly_p whether only the scope should be considered or the underlying EMF model * @return a potentially null object */ protected IComparableStructure<?> getSemanticID(EObject element_p, IModelScope scope_p, boolean inScopeOnly_p) { return null; } /** * Return the siblings of the given element from the given scope, including the element itself * @param element_p a non-null element * @param scope_p a non-null scope to which the element belongs * @param inScopeOnly_p whether only the scope may be considered, or the underlying EMF model */ protected List<EObject> getSiblings(EObject element_p, IModelScope scope_p, boolean inScopeOnly_p) { List<EObject> result; EReference containment = getContainment(element_p, scope_p, inScopeOnly_p); if (containment == null) { Resource resource = element_p.eResource(); if (inScopeOnly_p || resource == null) result = scope_p.getContents(); else result = resource.getContents(); } else if (scope_p instanceof IFeaturedModelScope) { EObject container = getContainer(element_p, scope_p, inScopeOnly_p); result = ((IFeaturedModelScope)scope_p).get(container, containment); } else { result = Collections.emptyList(); } return Collections.unmodifiableList(result); } /** * Return a match ID suffix for the given element which is relative to the match ID * of the container and made specific thanks to the structurally unique position of the * element in its container * @param element_p a non-null element * @param scope_p a non-null scope to which the element belongs * @param inScopeOnly_p whether only the scope should be considered or the underlying EMF model */ protected String getStructureMatchIDPart(EObject element_p, IModelScope scope_p, boolean inScopeOnly_p) { String result = null; EReference containment = getContainment(element_p, scope_p, inScopeOnly_p); if (isDiscriminatingContainment(element_p, containment)) { boolean validated = containment != null && !containment.isMany(); if (!validated) { Collection<EObject> siblings = getSiblings(element_p, scope_p, inScopeOnly_p); validated = isUniqueOfItsTypeAmong(element_p, siblings); } if (validated) result = getValidatedStructureMatchIDPart(element_p, scope_p, containment); } return result; } /** * Return a structural match ID suffix for the given element which is relative to * the given reference to the element, or in an absolute way if the element is a root * @param element_p a non-null element * @param scope_p a non-null scope to which the element belongs * @param containment_p a potentially null reference (null if and only if root) */ protected String getValidatedStructureMatchIDPart(EObject element_p, IModelScope scope_p, EReference containment_p) { StringBuilder builder = new StringBuilder(); builder.append("::"); //$NON-NLS-1$ if (containment_p != null && containment_p.getName() != null) builder.append(containment_p.getName()); builder.append('['); builder.append(element_p.eClass().getName()); builder.append(']'); return builder.toString(); } /** * Return whether the given containment reference is discriminating enough to uniquely * identify the given element as a child * @param element_p a non-null element * @param containment_p a potentially null containment reference, where null stands for root */ protected boolean isDiscriminatingContainment(EObject element_p, EReference containment_p) { return containment_p == null || !containment_p.isMany(); } /** * Return whether the given element is the first one of its type among those in the given collection * @param element_p a non-null element * @param collection_p a non-null, potentially empty collection */ protected boolean isFirstOfItsTypeAmong(EObject element_p, Collection<? extends EObject> collection_p) { Iterator<? extends EObject> it = collection_p.iterator(); EClass type = element_p.eClass(); while (it.hasNext()) { EObject root = it.next(); if (root == element_p) return true; if (root.eClass() == type) return false; } return false; } /** * Return whether the given element is an instance of one of the given types * @param element_p a non-null element * @param types_p a non-null, potentially empty collection */ protected boolean isInstanceOf(EObject element_p, Collection<? extends EClass> types_p) { Iterator<? extends EClass> typesIterator = types_p.iterator(); while (typesIterator.hasNext()) { EClass type = typesIterator.next(); if (type.isInstance(element_p)) return true; } return false; } /** * Return whether only the model scope should be considered for building match IDs, * or the whole underlying EMF model */ protected boolean isScopeOnly() { return true; } /** * Return whether the given element is the only one of its type among those in the given collection * @param element_p a non-null element * @param collection_p a non-null, potentially empty collection */ protected boolean isUniqueOfItsTypeAmong(EObject element_p, Collection<? extends EObject> collection_p) { Iterator<? extends EObject> it = collection_p.iterator(); EClass type = element_p.eClass(); boolean isPresent = false, sameType = false; while (it.hasNext() && !sameType) { EObject root = it.next(); if (root == element_p) { isPresent = true; } else { if (root.eClass() == type) sameType = true; } } boolean result = isPresent && !sameType; return result; } /** * Set whether the given match criterion must be used * @param criterion_p a non-null criterion * @param use_p whether it must be used */ public void setUseMatchCriterion(MatchCriterionKind criterion_p, boolean use_p) { if (use_p) _selectedCriteria.add(criterion_p); else _selectedCriteria.remove(criterion_p); } /** * Return whether the given match criterion is used by this match policy * @param criterion_p a non-null criterion */ public boolean useMatchCriterion(MatchCriterionKind criterion_p) { return _selectedCriteria.contains(criterion_p); } }