/** * <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.impl.scopes; import java.util.Collections; import java.util.List; import java.util.Set; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.diffmerge.util.ModelsUtil; import org.eclipse.emf.diffmerge.util.ModelsUtil.IElementFilter; import org.eclipse.emf.diffmerge.util.structures.FArrayList; import org.eclipse.emf.diffmerge.util.structures.FHashSet; import org.eclipse.emf.diffmerge.util.structures.IEqualityTester; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.util.FeatureMapUtil; /** * A model scope which covers a subset of EMF containment trees based on a filter. * Root addition leaves the underlying model untouched. Root removal modifies the model; * use removeFromScope(EObject, boolean) for side-effect-free removal. * EMF undo/redo is not supported due to the local non-EMF state. * Removal of elements which are cross-referenced outside the scope is not supported. * @author Olivier Constant */ public class FilteredModelScope extends RootedModelScope { /** The elements which actually belong to this scope */ private final Set<EObject> _inScope; /** * Constructor * @param elements_p a non-null list of the elements initially in the scope */ public FilteredModelScope(List<? extends EObject> elements_p) { super(ModelsUtil.getRoots(elements_p)); _inScope = new FHashSet<EObject>(elements_p, null); } /** * @see org.eclipse.emf.diffmerge.impl.scopes.RootedModelScope#add(org.eclipse.emf.ecore.EObject) */ @Override public boolean add(EObject element_p) { return add(element_p, false); } /** * Add the given element to the scope * @param element_p a non-null element which is out of the scope * @param includeChildren_p whether all children must be added too * @return whether the operation succeeded */ public boolean add(EObject element_p, boolean includeChildren_p) { boolean result = true; // If parent is not in scope, then add as root if ((element_p.eContainer() == null || !_inScope.contains(element_p.eContainer())) && !_roots.contains(element_p)) result = super.add(element_p); if (result) { _inScope.add(element_p); // Remove direct children from roots _roots.removeAll(element_p.eContents()); if (includeChildren_p) addAllChildrenToScope(element_p, null); } return result; } /** * @see org.eclipse.emf.diffmerge.impl.scopes.RootedModelScope#add(org.eclipse.emf.ecore.EObject, org.eclipse.emf.ecore.EReference, org.eclipse.emf.ecore.EObject) */ @Override public boolean add(EObject source_p, EReference reference_p, EObject value_p) { // Remember previously owned value if relevant EObject previouslyContainedValue = null; if (!FeatureMapUtil.isMany(source_p, reference_p) && reference_p.isContainment()) { List<EObject> values = get(source_p, reference_p); if (!values.isEmpty()) previouslyContainedValue = values.get(0); } boolean result = super.add(source_p, reference_p, value_p); if (result) { // Remove previously owned value from scope if (previouslyContainedValue != null) _inScope.remove(previouslyContainedValue); // Add new value in scope _inScope.add(value_p); } return result; } /** * Redefine the whole content of the scope solely based on the current roots, * all their children and the given filter * @param filter_p an optional filter, where null stands for no filtering */ public void build(IElementFilter filter_p) { _inScope.clear(); for (EObject root : super.getContents()) { if (filter_p == null || filter_p.accepts(root)) { _inScope.add(root); addAllChildrenToScope(root, filter_p); } } } /** * Add all children of the given in-scope element that are accepted by the * given filter. The rejection of an element leads to the rejection of all * its children. * @param element_p a non-null element which already belongs to the scope * @param filter_p an optional filter, where null stands for no filtering */ protected void addAllChildrenToScope(EObject element_p, IElementFilter filter_p) { TreeIterator<EObject> technicalIterator = element_p.eAllContents(); while (technicalIterator.hasNext()) { EObject child = technicalIterator.next(); if (filter_p == null || filter_p.accepts(child)) _inScope.add(child); else technicalIterator.prune(); } } /** * @see org.eclipse.emf.diffmerge.impl.scopes.RootedModelScope#covers(org.eclipse.emf.ecore.EObject) */ @Override public boolean covers(EObject element_p) { return _inScope.contains(element_p); } /** * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#get(org.eclipse.emf.ecore.EObject, org.eclipse.emf.ecore.EReference, boolean) */ @Override protected List<EObject> get(EObject source_p, EReference reference_p, boolean resolveProxies_p) { List<EObject> originalValues = super.get(source_p, reference_p, resolveProxies_p); List<EObject> result = new FArrayList<EObject>(originalValues, null); result.retainAll(_inScope); return Collections.unmodifiableList(result); } /** * @see org.eclipse.emf.diffmerge.impl.scopes.RootedModelScope#getContents() */ @Override public List<EObject> getContents() { List<EObject> result = new FArrayList<EObject>(); result.addAll(super.getContents()); result.retainAll(_inScope); return Collections.unmodifiableList(result); } /** * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#getContents(org.eclipse.emf.ecore.EObject) */ @Override public List<EObject> getContents(EObject element_p) { List<EObject> result = new FArrayList<EObject>( super.getContents(element_p), IEqualityTester.BY_REFERENCE); result.retainAll(_inScope); return Collections.unmodifiableList(result); } /** * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#getAllContentsAsSet() * This implementation is computationally efficient. */ @Override public Set<EObject> getAllContentsAsSet() { return Collections.unmodifiableSet(_inScope); } /** * @see org.eclipse.emf.diffmerge.impl.scopes.RootedModelScope#remove(org.eclipse.emf.ecore.EObject) */ @Override public boolean remove(EObject element_p) { boolean result = super.remove(element_p); if (result) removeFromScope(element_p, true); return result; } /** * Remove the given element and optionally its children from the scope while leaving * the underlying model unchanged * @param element_p a non-null element * @param includeChildren_p whether all children must be removed too */ public void removeFromScope(EObject element_p, boolean includeChildren_p) { _inScope.remove(element_p); if (includeChildren_p) { TreeIterator<EObject> technicalIterator = element_p.eAllContents(); while (technicalIterator.hasNext()) { EObject child = technicalIterator.next(); _inScope.remove(child); } } else { for (EObject child : getContents(element_p)) { add(child, false); } } } }