/** * <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.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.diffmerge.util.structures.FOrderedSet; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; /** * A utility class related to the structure of models. * @author Olivier Constant */ public final class ModelsUtil { /** * An interface defining filters for model elements */ public static interface IElementFilter { /** * Return whether the given element is accepted by this filter * @param element_p a non-null element */ boolean accepts(EObject element_p); } /** * Constructor */ private ModelsUtil() { // Forbids instantiation } /** * From a set of elements, return all the elements of their containment trees in * depth-first order * Postcondition: elements_p is not modified. * @param elements_p a non-null collection * @param filter_p an optional filter * @return a non-null, modifiable list */ private static List<EObject> getAllContentsDF(Collection<? extends EObject> elements_p, IElementFilter filter_p) { List<EObject> result = new FOrderedSet<EObject>(); for (EObject element : elements_p) { result.addAll(getAllContentsDF(element, filter_p)); } return result; } /** * Return all the elements of the containment tree of the given element in depth-first * order * @param element_p a non-null element * @param filter_p an optional filter * @return a non-null, modifiable list */ private static List<EObject> getAllContentsDF(EObject element_p, IElementFilter filter_p) { List<EObject> result = new FOrderedSet<EObject>(); if (filter_p == null || filter_p.accepts(element_p)) result.add(element_p); TreeIterator<EObject> it = element_p.eAllContents(); while (it.hasNext()) { EObject current = it.next(); if (filter_p == null || filter_p.accepts(current)) result.add(current); } return result; } /** * From a set of elements, build a list of all the elements of their containment trees in * breadth-first order * Postcondition: elements_p is not modified. * We use a LinkedList for queuing behavior. * @param elements_p a non-null, modifiable, potentially empty queue of the roots * @param result_p the non-null modifiable result being built * @param filter_p an optional filter */ private static void getAllContentsBF(LinkedList<EObject> elements_p, List<EObject> result_p, IElementFilter filter_p) { if (!elements_p.isEmpty()) { EObject current = elements_p.poll(); if (filter_p == null || filter_p.accepts(current)) { result_p.add(current); elements_p.addAll(current.eContents()); } getAllContentsBF(elements_p, result_p, filter_p); } } /** * Return all the elements in the containment tree of the given element * @param element_p a non-null element * @param depthFirst_p whether the elements must be returned in breadth-first order or in * depth-first order * @param filter_p an optional filter * @return a non-null, modifiable list */ public static List<EObject> getAllContents(EObject element_p, boolean depthFirst_p, IElementFilter filter_p) { return getAllContents(Collections.singletonList(element_p), depthFirst_p, filter_p); } /** * From a set of elements, return all the elements in their containment trees * Postcondition: elements_p is not modified. * @param elements_p a non-null collection * @param depthFirst_p whether the elements must be returned in breadth-first order or in * depth-first order * @param filter_p an optional filter * @return a non-null, modifiable list */ public static List<EObject> getAllContents(Collection<? extends EObject> elements_p, boolean depthFirst_p, IElementFilter filter_p) { List<EObject> result; if (depthFirst_p) { result = getAllContentsDF(elements_p, filter_p); } else { result = new FOrderedSet<EObject>(); getAllContentsBF(new LinkedList<EObject>(elements_p), result, filter_p); } return result; } /** * Return the list of ancestors including self, from higher to deeper. * The result is not immutable but modifying it has no impact whatsoever. * @param element_p a potentially null element * @return a non-null, modifiable ordered set */ public static List<EObject> getAncestors(EObject element_p) { if (element_p == null) return new FOrderedSet<EObject>(); List<EObject> containerList = getAncestors(element_p.eContainer()); containerList.add(element_p); return containerList; } /** * Return the lowest common ancestor in the containment hierarchy, if any, * of the given set of elements * @param acceptSelf_p whether the result can be any of the given elements * @return a potentially null element */ public static EObject getCommonAncestor( Collection<? extends EObject> elements_p, boolean acceptSelf_p) { if (elements_p == null || elements_p.isEmpty()) return null; Iterator<? extends EObject> it = elements_p.iterator(); List<EObject> commonHierarchy = getAncestors(it.next()); while (it.hasNext()) { List<EObject> currentHierarchy = getAncestors(it.next()); // Compute intersection of ancestors commonHierarchy.retainAll(currentHierarchy); } // Exclude the given elements if (!acceptSelf_p) commonHierarchy.removeAll(elements_p); // Take lowest ancestor in common hierarchy if (commonHierarchy.isEmpty()) return null; return commonHierarchy.get(commonHierarchy.size()-1); } /** * Return the lowest common ancestor, in the containment hierarchy, * of the two given elements (inclusive) * @param first_p a non-null element * @param second_p a non-null element * @return a potentially null element */ public static EObject getCommonAncestor(EObject first_p, EObject second_p) { if (null == first_p || null == second_p) return null; return getCommonAncestor(Arrays.asList(new EObject[] {first_p, second_p}), true); } /** * Given a set of elements, find their lowest common meta-class * @param elements_p a non-null collection of model elements * @return a meta-class which is not null if elements_p is not empty */ public static EClass getCommonType(Collection<? extends EObject> elements_p) { EClass result = null; if (!elements_p.isEmpty()) { List<EClass> common = new ArrayList<EClass>( getSuperTypes(elements_p.iterator().next().eClass())); for(EObject elt : elements_p) { common.retainAll(getSuperTypes(elt.eClass())); } if (!common.isEmpty()) { result = common.get(common.size()-1); } } return result; } /** * Return the depth in the containment tree of the given element * @param element_p a potentially null element * @return 0 if null, a strictly positive integer otherwise */ public static int getDepth(EObject element_p) { if (element_p == null) return 0; return 1 + getDepth(element_p.eContainer()); } /** * Return the overall depth in the containment tree of the given collection of elements, * where overall means maximum if max_p is true, or minimum otherwise * @param elements_p a non-null, potentially empty collection of elements */ public static int getDepth(Iterable<? extends EObject> elements_p, boolean max_p) { int result = max_p? 0: Integer.MAX_VALUE; for (EObject element : elements_p) { int depth = getDepth(element); result = max_p? Math.max(result, depth): Math.min(result, depth); } return result; } /** * From a set of elements, return all the leaves in their containment trees * @param elements_p a non-null collection * @return a non-null list */ public static List<EObject> getLeaves(Collection<? extends EObject> elements_p) { List<EObject> result = new FOrderedSet<EObject>(); for (EObject element : elements_p) { result.addAll(getLeaves(element)); } return result; } /** * Return all the leaves in the containment tree of the given element * @param element_p a non-null element * @return a non-null list */ public static List<EObject> getLeaves(EObject element_p) { List<EObject> result; if (element_p.eContents().isEmpty()) { result = Collections.singletonList(element_p); } else { result = getLeaves(element_p.eContents()); } return result; } /** * From a set of elements, filter out those which are transitively contained * in others * @param elements_p a non-null collection * @return a non-null list */ public static <T extends EObject> List<T> getRoots( Collection<? extends T> elements_p) { List<T> result = new FOrderedSet<T>(); Collection<T> elements = new FOrderedSet<T>(elements_p, null); for (T element : elements) { if (!result.contains(element) && isRootAmong(element, elements)) result.add(element); } return result; } /** * Return the super types of the given meta-class including the class itself, * ordered from higher to lower in the hierarchy * @param class_p a non-null meta-class * @return a non-null, non-empty, unmodifiable list */ private static List<EClass> getSuperTypes(EClass class_p) { List<EClass> allButSelf = class_p.getEAllSuperTypes(); List<EClass> result = new ArrayList<EClass>(allButSelf.size() + 1); result.addAll(allButSelf); result.add(class_p); return Collections.unmodifiableList(result); } /** * Return whether the given element is not transitively contained by any * of the given elements, unless it is one of the given elements * @param element_p a non-null element * @param elements_p a non-null collection */ private static boolean isRootAmong(EObject element_p, Collection<? extends EObject> elements_p) { Collection<EObject> filtered = new FOrderedSet<EObject>(elements_p, null); filtered.remove(element_p); return !EcoreUtil.isAncestor(filtered, element_p); } }