/** * <copyright> * * Copyright (c) 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.viewers; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.util.EMap; import org.eclipse.emf.diffmerge.api.IComparison; import org.eclipse.emf.diffmerge.api.IMatch; import org.eclipse.emf.diffmerge.api.Role; import org.eclipse.emf.diffmerge.api.diff.IDifference; import org.eclipse.emf.diffmerge.api.diff.IElementPresence; import org.eclipse.emf.diffmerge.api.diff.IElementRelativeDifference; import org.eclipse.emf.diffmerge.api.diff.IElementRelativePresence; import org.eclipse.emf.diffmerge.api.diff.IMergeableDifference; import org.eclipse.emf.diffmerge.api.diff.IPresenceDifference; import org.eclipse.emf.diffmerge.api.diff.IReferenceValuePresence; import org.eclipse.emf.diffmerge.api.diff.IValuePresence; import org.eclipse.emf.diffmerge.diffdata.EMatch; import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin; import org.eclipse.emf.diffmerge.ui.diffuidata.MatchAndFeature; import org.eclipse.emf.diffmerge.ui.util.DifferenceKind; import org.eclipse.emf.diffmerge.util.structures.FHashMap; import org.eclipse.emf.diffmerge.util.structures.FOrderedSet; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.jface.viewers.TreePath; /** * A manager of difference categories for a given diff node. * @author Olivier Constant */ public class CategoryManager { /** The non-null diff node that is the context of this manager */ protected final EMFDiffNode _node; /** The modifiable set of (ID, category) registered categories */ private final Map<String, IDifferenceCategory> _categories; /** The modifiable set of active categories among the registered ones */ protected final Set<IDifferenceCategory> _activeCategories; /** The set of default category states */ protected final Set<IDifferenceCategory> _defaultConfiguration; /** The modifiable list of root category items that should be visible in the UI */ protected final List<IDifferenceCategoryItem> _uiRootItems; /** The modifiable (parent, children) map of category items that should be visible in the UI */ protected final Map<IDifferenceCategorySet, Collection<IDifferenceCategoryItem>> _uiChildrenItems; /** The map from matches to difference numbers */ private final EMap<EMatch, Integer> _matchToNb; /** * Constructor * @param node_p a non-null diff node as context */ public CategoryManager(EMFDiffNode node_p) { _node = node_p; _categories = new LinkedHashMap<String, IDifferenceCategory>(); _activeCategories = new HashSet<IDifferenceCategory>(); _defaultConfiguration = new HashSet<IDifferenceCategory>(); _uiRootItems = new ArrayList<IDifferenceCategoryItem>(); _uiChildrenItems = new HashMap<IDifferenceCategorySet, Collection<IDifferenceCategoryItem>>(); _matchToNb = new FHashMap<EMatch, Integer>(); } /** * Add and register the categories that are recursively associated to the given * category set. * @param categorySet_p a non-null category set * @return whether no already-registered category was unregistered as a result */ public boolean addCategories(IDifferenceCategorySet categorySet_p) { boolean result = true; for (IDifferenceCategoryItem categoryItem : categorySet_p.getChildren()) { result = result && addCategoryItem(categoryItem); } return result; } /** * Add and register the given category to this manager. * If a category with the same ID was already registered, then it is unregistered. * @param category_p a non-null category * @return whether the operation had no side effect, i.e., no other category was unregistered */ public boolean addCategory(IDifferenceCategory category_p) { IDifferenceCategory squatter = _categories.put(category_p.getID(), category_p); return squatter == null || squatter == category_p; } /** * Add and register the categories that are recursively associated to the given * category item. * @param categoryItem_p a non-null category set * @return whether no already-registered category was unregistered as a result */ protected boolean addCategoryItem(IDifferenceCategoryItem categoryItem_p) { boolean result = true; if (categoryItem_p instanceof IDifferenceCategory) result = addCategory((IDifferenceCategory)categoryItem_p); else if (categoryItem_p instanceof IDifferenceCategorySet) result = addCategories((IDifferenceCategorySet)categoryItem_p); return result; } /** * Count and return the number of differences on the given match, * excluding differences on children * @param match_p a non-null match * @param withFilters_p whether filters must be taken into account * @return a positive int */ protected int countDifferences(IMatch match_p, boolean withFilters_p) { // Non-containment differences int result = countNonContainmentDifferences(match_p, withFilters_p); // Move if (isMove(match_p, withFilters_p)) result++; // Addition/deletion if (isUnmatched(match_p, withFilters_p)) result++; return result; } /** * Count and return the number of non-containment differences on the given match * @param match_p a non-null match * @param withFilters_p whether filters must be taken into account * @return a positive int or 0 */ protected int countNonContainmentDifferences(IMatch match_p, boolean withFilters_p) { int result = 0; if (!match_p.isPartial()) { Set<EStructuralFeature> uniFeatures = new HashSet<EStructuralFeature>(); for (IDifference difference : match_p.getRelatedDifferences()) { if (difference instanceof IElementRelativeDifference && (!withFilters_p || !isFiltered(difference))) { IElementRelativeDifference eltDiff = (IElementRelativeDifference)difference; if (eltDiff.isUnrelatedToContainmentTree()) { if (eltDiff instanceof IValuePresence) { EStructuralFeature feature = ((IValuePresence)eltDiff).getFeature(); if (feature != null && (!feature.isMany() || ((IValuePresence)eltDiff).isOrder())) { // A value presence on a non-many feature if (!uniFeatures.contains(feature)) { // Not counted yet result++; uniFeatures.add(feature); } } else { // A value presence on a feature such that isMany() result++; } } else { // Not a value presence result++; } } } } } return result; } /** * Return the set of registered difference categories * @return a non-null, non-modifiable collection */ public Collection<IDifferenceCategory> getCategories() { return _categories.values(); } /** * Return the registered category of the given ID, if any * @param categoryID_p a potentially null category ID * @return a potentially null category */ public IDifferenceCategory getCategory(String categoryID_p) { IDifferenceCategory result = _categories.get(categoryID_p); return result; } /** /** * Return the list of visible children for merge of the given match * @param match_p a non-null match * @return a non-null, potentially empty, unmodifiable list */ public List<IMatch> getChildrenForMerge(IMatch match_p) { List<IMatch> result = new FOrderedSet<IMatch>(); IComparison comparison = match_p.getMapping().getComparison(); List<IMatch> candidates = comparison.getContentsOf(match_p); for (IMatch candidate : candidates) { if (isMove(candidate, false) && comparison.getContainerOf(candidate, _node.getDrivingRole().opposite()) == match_p) continue; // Move origin if (getDifferenceNumber(candidate) > 0) result.add(candidate); } return Collections.unmodifiableList(result); } /** * Return the difference kind of the given match with filtering * @param match_p a non-null match * @return FROM_LEFT*, FROM_RIGHT*, CONFLICT, MODIFIED, COUNTED or NONE */ public DifferenceKind getDifferenceKind(IMatch match_p) { DifferenceKind result = DifferenceKind.NONE; IElementPresence presence = match_p.getElementPresenceDifference(); boolean considerReference = _node.getReferenceRole() != null; if (presence != null) { result = getDifferenceKind(presence); } else { result = getModificationKind(match_p); result = result.with(getOwnershipDifferenceKind(match_p), considerReference); result = result.keepOnlyDirection(considerReference); } if (result == DifferenceKind.NONE && getDifferenceNumber(match_p) > 0) result = DifferenceKind.COUNTED; return result; } /** * Return the difference kind of the given match and feature with filtering * @param maf_p a non-null match and feature * @return FROM_LEFT*, FROM_RIGHT*, CONFLICT, MODIFIED, COUNTED or NONE */ @SuppressWarnings({ "unchecked", "rawtypes" }) public DifferenceKind getDifferenceKind(MatchAndFeature maf_p) { DifferenceKind result = DifferenceKind.NONE; EStructuralFeature feature = maf_p.getFeature(); if (feature == EMFDiffMergeUIPlugin.getDefault().getOwnershipFeature()) { // Ownership feature (move) result = getOwnershipDifferenceKind(maf_p.getMatch()); } else { // Standard feature Collection<? extends IValuePresence> presences; if (feature instanceof EReference) { if (((EReference)feature).isContainment()) { // Containment on feature which is not ownership: // consider order only presences = new ArrayList<IValuePresence>(); IValuePresence orderDiff = maf_p.getMatch().getOrderDifference(feature, _node.getDrivingRole()); if (orderDiff != null) ((List)presences).add(orderDiff); orderDiff = maf_p.getMatch().getOrderDifference(feature, _node.getDrivingRole().opposite()); if (orderDiff != null) ((List)presences).add(orderDiff); } else { presences = maf_p.getMatch().getReferenceDifferences((EReference)feature); } } else { presences = maf_p.getMatch().getAttributeDifferences((EAttribute)feature); } Iterator<? extends IValuePresence> it = presences.iterator(); while (it.hasNext() && result != DifferenceKind.CONFLICT && result != DifferenceKind.FROM_BOTH && result != DifferenceKind.MODIFIED) { DifferenceKind current = getDifferenceKind(it.next()); result = result.with(current, true); } } return result; } /** * Return the set of category states which is registered as the default configuration * @return a non-null, unmodifiable, potentially empty collection */ public Collection<IDifferenceCategory> getDefaultConfiguration() { return Collections.unmodifiableSet(_defaultConfiguration); } /** * Return the kind of the given difference with filtering * @param difference_p a non-null difference * @return FROM_LEFT_*, FROM_RIGHT_*, CONFLICT, MODIFIED or NONE */ public DifferenceKind getDifferenceKind(IDifference difference_p) { DifferenceKind result = DifferenceKind.NONE; if (!isFiltered(difference_p)) { if (difference_p.isConflicting()) { result = DifferenceKind.CONFLICT; } else if (difference_p instanceof IPresenceDifference) { IPresenceDifference presence = (IPresenceDifference)difference_p; boolean isMany = isMany(presence); if (presence.getPresenceRole() == _node.getRoleForSide(true)) { // Present on the left if (isAlignedWithReference(presence)) result = DifferenceKind.FROM_RIGHT_DEL; else if (isMany || _node.getReferenceRole() != null) result = DifferenceKind.FROM_LEFT_ADD; else result = DifferenceKind.MODIFIED; } else { // Present on the right if (isAlignedWithReference(presence)) result = DifferenceKind.FROM_LEFT_DEL; else if (isMany || _node.getReferenceRole() != null) result = DifferenceKind.FROM_RIGHT_ADD; else result = DifferenceKind.MODIFIED; } } } return result; } /** * Return the number of differences associated to the given match, without recounting * @param match_p a non-null match * @return a positive int or 0 */ public int getDifferenceNumber(IMatch match_p) { Integer currentNb = getMatchToNb().get(match_p); if (currentNb == null) currentNb = Integer.valueOf(0); return currentNb.intValue(); } /** * Return the map from matches to differences numbers * @return a non-null, modifiable map */ protected EMap<EMatch, Integer> getMatchToNb() { return _matchToNb; } /** * Return the modification status of the given match with filtering * @param match_p a non-null match * @return a non-null kind */ protected DifferenceKind getModificationKind(IMatch match_p) { DifferenceKind result = DifferenceKind.NONE; boolean considerReference = _node.getReferenceRole() != null; IElementPresence eltPresence = match_p.getElementPresenceDifference(); if (eltPresence == null || isFiltered(eltPresence)) { for (IDifference diff : match_p.getRelatedDifferences()) { if (diff instanceof IElementRelativeDifference && ((IElementRelativeDifference)diff).isUnrelatedToContainmentTree()) { DifferenceKind diffKind = getDifferenceKind(diff); result = result.with(diffKind, considerReference); if (result == DifferenceKind.CONFLICT) break; } } } return result; } /** * Return the difference kind for the ownership of the given match * @param match_p a non-null match * @return a non-null difference kind */ protected DifferenceKind getOwnershipDifferenceKind(IMatch match_p) { DifferenceKind result = DifferenceKind.NONE; if (representAsMove(match_p)) { result = DifferenceKind.MODIFIED; if (_node.getReferenceRole() != null) { IReferenceValuePresence onLeft = match_p.getOwnershipDifference(_node.getRoleForSide(true)); boolean fromLeft = onLeft != null && !isAlignedWithReference(onLeft); IReferenceValuePresence onRight = match_p.getOwnershipDifference(_node.getRoleForSide(false)); boolean fromRight = onRight != null && !isAlignedWithReference(onRight); if (fromLeft && fromRight) result = DifferenceKind.CONFLICT; else if (fromLeft) result = DifferenceKind.FROM_LEFT; else if (fromRight) result = DifferenceKind.FROM_RIGHT; } } return result; } /** * Return the differences within the given set that are not filtered and that * can be processed by the user * @param differences_p a non-null set of differences * @return a non-null, potentially empty, unmodifiable list */ public List<IDifference> getPendingDifferencesFiltered(Iterable<? extends IDifference> differences_p) { List<IDifference> result = new ArrayList<IDifference>(); for (IDifference difference : differences_p) { if (isPending(difference) && !isFiltered(difference)) result.add(difference); } return Collections.unmodifiableList(result); } /** * Return the list of category items that should be visible in the UI as children of * the given category set * @return a non-null, potentially empty, unmodifiable list */ public List<IDifferenceCategoryItem> getUIChildrenItems(IDifferenceCategorySet categorySet_p) { List<IDifferenceCategoryItem> result = new ArrayList<IDifferenceCategoryItem>( categorySet_p.getChildren()); Collection<IDifferenceCategoryItem> visibleChildren = _uiChildrenItems.get(categorySet_p); if (visibleChildren != null) { result.retainAll(visibleChildren); result = Collections.unmodifiableList(result); } else { result = Collections.emptyList(); } return result; } /** * Return the number of differences associated to the given match from a user's perspective, * without recounting * @param match_p a non-null match * @return a positive int or 0 */ public int getUIDifferenceNumber(IMatch match_p) { if (_node.isHideDifferenceNumbers()) return 0; int result = getDifferenceNumber(match_p); IElementPresence eltPresence = match_p.getElementPresenceDifference(); if (eltPresence != null && !isFiltered(eltPresence)) result--; return result; } /** * Return the root category items that should be visible in the UI * @return a non-null, potentially empty, unmodifiable list */ public List<IDifferenceCategoryItem> getUIRootItems() { return Collections.unmodifiableList(_uiRootItems); } /** * Return whether the given match has visible children for merge * @param match_p a non-null match */ public boolean hasChildrenForMergeFiltered(IMatch match_p) { IComparison comparison = match_p.getMapping().getComparison(); Role role = match_p.isPartial()? match_p.getUncoveredRole().opposite(): _node.getDrivingRole(); List<IMatch> candidates = comparison.getContentsOf(match_p, role); for (IMatch candidate : candidates) { if (getDifferenceNumber(candidate) > 0) return true; } return false; } /** * Return whether the given match has visible non-containment differences for merge * @param match_p a non-null match */ public boolean hasNonContainmentDifferencesForMergeFiltered(IMatch match_p) { if (!match_p.isPartial()) { for (IDifference difference : match_p.getRelatedDifferences()) { if (difference instanceof IElementRelativeDifference && !isFiltered(difference)) { IElementRelativeDifference eltDiff = (IElementRelativeDifference)difference; if (eltDiff.isUnrelatedToContainmentTree()) return true; } } } return false; } /** * Increment the number of differences by the given increment for the given match * @param match_p a non-null match * @param increment_p a positive int */ protected void incrementDifferenceNumbers(EMatch match_p, int increment_p) { int currentNb = getDifferenceNumber(match_p); Integer newNb = Integer.valueOf(increment_p + currentNb); getMatchToNb().put(match_p, newNb); } /** * Increment the number of differences by the given increment for the given match * and its parents according to the driving role * @param match_p a non-null match * @param increment_p a positive int */ protected void incrementDifferenceNumbersInHierarchy(EMatch match_p, int increment_p) { if (increment_p > 0) { incrementDifferenceNumbers(match_p, increment_p); EMatch current = _node.getContainerOf(match_p); while (current != null) { incrementDifferenceNumbers(current, increment_p); current = _node.getContainerOf(current); } } } /** * Return whether the given difference is aligned with the reference model if any. * If there is no reference model, then false is returned. * @see EMFDiffNode#getReferenceRole() * @param presence_p a non-null difference */ protected boolean isAlignedWithReference(IPresenceDifference presence_p) { boolean result; if (_node.isThreeWay()) result = presence_p.isAlignedWithAncestor(); else result = presence_p.getPresenceRole() == _node.getReferenceRole(); return result; } /** * Return whether the are still differences that the user has to handle */ public boolean isEmpty() { IComparison comparison = _node.getActualComparison(); if (comparison != null) { for (IMatch match : comparison.getMapping().getContents()) { for (IDifference difference : match.getAllDifferences()) { if (isPending(difference) && !isFiltered(difference)) return false; } } } return true; } /** * Return whether the given difference is filtered out by categories * @param difference_p a non-null difference */ public boolean isFiltered(IDifference difference_p) { boolean globalFocus = false; // At least one category is in focus mode boolean diffFocus = false; // At least one covering category is in focus mode for (IDifferenceCategory category : _activeCategories) { boolean catFocus = category.isInFocusMode(); globalFocus = globalFocus || catFocus; boolean covered = category.covers(difference_p, _node); if (covered) { // Covered by active category if (!catFocus) { // Covered by category in filtering mode return true; } // Else covered by category in focus mode: proceed diffFocus = true; } } // Not filtered out by any category return globalFocus && !diffFocus; // All categories in focus mode are non-covering } /** * Return whether the given difference is being explicitly ignored by the user * @param difference_p a non-null difference */ public boolean isIgnored(IDifference difference_p) { return _node.getUIComparison().getDifferencesToIgnore().contains(difference_p); } /** * Return whether the given difference represents a value presence with no * multiplicity constraint * @param difference_p a non-null difference */ public boolean isMany(IDifference difference_p) { boolean result = true; if (difference_p instanceof IValuePresence) { IValuePresence valuePresence = (IValuePresence)difference_p; if (!valuePresence.isOrder()) { EStructuralFeature feature = valuePresence.getFeature(); result = (feature == null || feature.isMany()); } } return result; } /** * Return whether the given difference can still be merged * @param difference_p a non-null difference */ public boolean isMergeable(IDifference difference_p) { boolean result = false; if (difference_p instanceof IMergeableDifference) { IMergeableDifference casted = (IMergeableDifference)difference_p; result = casted.canMergeTo(_node.getRoleForSide(true)) && _node.isEditable(true) || casted.canMergeTo(_node.getRoleForSide(false)) && _node.isEditable(false); } return result; } /** * Return whether the given difference represents a move * @param difference_p a potentially null difference */ public boolean isMove(IDifference difference_p) { boolean result = false; if (isOwnership(difference_p)) { IMatch valueMatch = ((IReferenceValuePresence)difference_p).getValueMatch(); result = valueMatch != null && !valueMatch.isPartial(); } return result; } /** * Return whether the given match represents a moved element * @param match_p a non-null match * @param withFilters_p whether filters must be taken into account */ public boolean isMove(IMatch match_p, boolean withFilters_p) { boolean result = false; if (!match_p.isPartial()) { IReferenceValuePresence onTarget = match_p.getOwnershipDifference(Role.TARGET); IReferenceValuePresence onReference = match_p.getOwnershipDifference(Role.REFERENCE); result = (onTarget != null && (!withFilters_p || !isFiltered(onTarget))) || (onReference != null && (!withFilters_p || !isFiltered(onReference))); } return result; } /** * Return whether the given path represents a moved element on the side of * the source of the move, where source corresponds to the opposite of the driving role * @param path_p a non-null path */ public boolean isMoveOrigin(TreePath path_p) { boolean result = false; IMatch end = (IMatch)path_p.getLastSegment(); if (end != null && isMove(end, false)) { TreePath parentPath = path_p.getParentPath(); IMatch father = parentPath == null? null: (IMatch)parentPath.getLastSegment(); IComparison comparison = end.getMapping().getComparison(); Role drivingRole = _node.getDrivingRole(); result = comparison.getContainerOf(end, drivingRole.opposite()) == father && comparison.getContainerOf(end, drivingRole) != father; } return result; } /** * Return whether the given difference represents an ordering difference * @param difference_p a non-null difference */ public boolean isOrder(IDifference difference_p) { return difference_p instanceof IValuePresence && ((IValuePresence)difference_p).isOrder(); } /** * Return whether the given difference represents an ownership * @param difference_p a potentially null difference */ public boolean isOwnership(IDifference difference_p) { boolean result = false; if (difference_p instanceof IReferenceValuePresence) { IReferenceValuePresence presence = (IReferenceValuePresence)difference_p; if (!presence.isOrder()) { EReference ref = presence.getFeature(); result = ref == null || ref.isContainment(); } } return result; } /** * Return whether the given difference is still pending for the user * (mergeable, not merged, not ignored) * @param difference_p a non-null difference */ public boolean isPending(IDifference difference_p) { return isMergeable(difference_p) && !isIgnored(difference_p); } /** * Return whether pending differences are filtered from a user point of view, * that is, categories that are visible, modifiable and active may be filtering out * pending differences */ public boolean isUIFiltering() { for (IDifferenceCategory category : _activeCategories) { if (isUIFiltering(category)) return true; } return false; } /** * Return whether the given category is filtering from a user point of view, * that is, categories that are visible, modifiable and active may be filtering out * pending differences */ public boolean isUIFiltering(IDifferenceCategory category_p) { return category_p.isVisible() && category_p.isModifiable() && _activeCategories.contains(category_p) && (category_p.mayCoverPendingDifferences() || category_p.isInFocusMode()); } /** * Return whether the given match represents an element addition/deletion * @param match_p a non-null match * @param withFilters_p whether filters must be taken into account */ public boolean isUnmatched(IMatch match_p, boolean withFilters_p) { IElementPresence presence = match_p.getElementPresenceDifference(); return presence != null && (!withFilters_p || !isFiltered(presence)); } /** * Remove (i.e., unregister) the category of the given ID * @param categoryID_p a potentially null category ID * @return whether the category was successfully found and removed */ public boolean removeCategory(String categoryID_p) { IDifferenceCategory category = _categories.remove(categoryID_p); return category != null; } /** * Return whether the given match is represented as a modification * @param match_p a non-null match */ public boolean representAsModification(IMatch match_p) { IElementPresence eltPresence = match_p.getElementPresenceDifference(); if (eltPresence != null && !isFiltered(eltPresence)) return false; for (IDifference diff : match_p.getRelatedDifferences()) { if (representAsModificationDifference(diff)) return true; } return false; } /** * Return whether the given difference is represented as a modification * of the corresponding element * @param difference_p a non-null difference */ protected boolean representAsModificationDifference(IDifference difference_p) { boolean result = false; if (difference_p instanceof IElementRelativePresence) { IElementRelativePresence presence = (IElementRelativePresence)difference_p; return presence.isUnrelatedToContainmentTree() && !isFiltered(difference_p); } return result; } /** * Return whether the given match is represented as a move * @param match_p a non-null match */ public boolean representAsMove(IMatch match_p) { return isMove(match_p, true); } /** * Return whether the given path is represented as a moved element on the side of * the source of the move * @param path_p a non-null path */ public boolean representAsMoveOrigin(TreePath path_p) { IMatch end = (IMatch)path_p.getLastSegment(); return representAsMove(end) && isMoveOrigin(path_p); } /** * Return whether the given match contains differences to represent * @param match_p a non-null match */ public boolean representAsUserDifference(IMatch match_p) { DifferenceKind kind = getDifferenceKind(match_p); boolean result = kind != DifferenceKind.NONE && kind != DifferenceKind.COUNTED; return result; } /** * Return whether the given path contains differences to represent * @param path_p a non-null path */ public boolean representAsUserDifference(TreePath path_p) { boolean result = false; IMatch end = (IMatch)path_p.getLastSegment(); if (end != null) result = representAsUserDifference(end) && !representAsMoveOrigin(path_p); return result; } /** * Reset the states of the current categories to match the default ones */ public void resetToDefault() { for (IDifferenceCategory defaultCat : _defaultConfiguration) { String id = defaultCat.getID(); IDifferenceCategory actualCat = getCategory(id); if (actualCat != null) actualCat.copyState(defaultCat); } } /** * Set the set of current states of all current categories as the default */ public void setDefaultConfiguration() { _defaultConfiguration.clear(); for (IDifferenceCategory category : getCategories()) { try { IDifferenceCategory clone = category.clone(); _defaultConfiguration.add(clone); } catch (CloneNotSupportedException e) { // Exclude from default configuration and proceed } } } /** * Re-compute filtering and differences numbers */ public void update() { updateActiveCategories(); updateUIItems(); updateDifferenceNumbers(); } /** * Re-compute the set of active categories */ protected void updateActiveCategories() { _activeCategories.clear(); for (IDifferenceCategory category : getCategories()) { if (category.isApplicable(_node) && category.isActive()) _activeCategories.add(category); } } /** * Re-compute difference numbers */ protected void updateDifferenceNumbers() { getMatchToNb().clear(); IComparison comparison = _node.getActualComparison(); if (comparison != null) { for (IMatch match : comparison.getMapping().getContents()) { int nb = countDifferences(match, true); incrementDifferenceNumbersInHierarchy((EMatch)match, nb); } } } /** * Re-compute the forest of category items that should be visible in the UI */ protected void updateUIItems() { _uiRootItems.clear(); _uiChildrenItems.clear(); List<IDifferenceCategoryItem> visibleItems = new LinkedList<IDifferenceCategoryItem>(); for (IDifferenceCategory category : getCategories()) { if (category.isVisible() && category.isApplicable(_node)) { // Category must be visible in the UI visibleItems.add(category); } } while (!visibleItems.isEmpty()) { IDifferenceCategoryItem visibleItem = visibleItems.get(0); IDifferenceCategorySet parent = visibleItem.getParent(); if (parent == null) { // It is a root if (!_uiRootItems.contains(visibleItem)) _uiRootItems.add(visibleItem); } else { // It is a child: remember its parent Collection<IDifferenceCategoryItem> children = _uiChildrenItems.get(parent); if (children == null) { children = new HashSet<IDifferenceCategoryItem>(); _uiChildrenItems.put(parent, children); } children.add(visibleItem); visibleItems.add(parent); } visibleItems.remove(visibleItem); } } }