| /******************************************************************************* |
| * Copyright (c) 2000, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ui.synchronize; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.core.diff.IDiff; |
| import org.eclipse.team.core.diff.IDiffChangeEvent; |
| import org.eclipse.team.core.diff.IDiffChangeListener; |
| import org.eclipse.team.core.diff.IDiffTree; |
| import org.eclipse.team.core.mapping.IResourceDiffTree; |
| import org.eclipse.team.core.mapping.provider.ResourceDiffTree; |
| import org.eclipse.team.core.synchronize.ISyncInfoSetChangeEvent; |
| import org.eclipse.team.core.synchronize.SyncInfo; |
| import org.eclipse.team.core.synchronize.SyncInfoSet; |
| import org.eclipse.team.core.synchronize.SyncInfoTree; |
| import org.eclipse.team.internal.core.subscribers.ActiveChangeSetManager; |
| import org.eclipse.team.internal.core.subscribers.ChangeSet; |
| import org.eclipse.team.internal.core.subscribers.DiffChangeSet; |
| import org.eclipse.team.internal.core.subscribers.IChangeSetChangeListener; |
| import org.eclipse.team.internal.core.subscribers.SubscriberChangeSetManager; |
| import org.eclipse.team.internal.ui.TeamUIPlugin; |
| import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration; |
| import org.eclipse.team.ui.synchronize.ISynchronizeParticipant; |
| import org.eclipse.team.ui.synchronize.SubscriberParticipant; |
| |
| /** |
| * Group incoming changes according to the active change set that are |
| * located in |
| */ |
| public class ActiveChangeSetCollector implements IDiffChangeListener { |
| |
| private final ISynchronizePageConfiguration configuration; |
| |
| /* |
| * Map active change sets to infos displayed by the participant |
| */ |
| private final Map<ChangeSet, SyncInfoSet> activeSets = new HashMap<>(); |
| |
| /* |
| * Set which contains those changes that are not part of an active set |
| */ |
| private SyncInfoTree rootSet = new SyncInfoTree(); |
| |
| private final ChangeSetModelProvider provider; |
| |
| /* |
| * Listener registered with active change set manager |
| */ |
| private IChangeSetChangeListener activeChangeSetListener = new IChangeSetChangeListener() { |
| |
| @Override |
| public void setAdded(final ChangeSet set) { |
| // Remove any resources that are in the new set |
| provider.performUpdate(monitor -> { |
| remove(set.getResources()); |
| createSyncInfoSet(set); |
| }, true, true); |
| } |
| |
| @Override |
| public void defaultSetChanged(final ChangeSet previousDefault, final ChangeSet set) { |
| provider.performUpdate(monitor -> { |
| if (listener != null) |
| listener.defaultSetChanged(previousDefault, set); |
| }, true, true); |
| } |
| |
| @Override |
| public void setRemoved(final ChangeSet set) { |
| provider.performUpdate(monitor -> { |
| remove(set); |
| if (!set.isEmpty()) { |
| add(getSyncInfos(set).getSyncInfos()); |
| } |
| }, true, true); |
| } |
| |
| @Override |
| public void nameChanged(final ChangeSet set) { |
| provider.performUpdate(monitor -> { |
| if (listener != null) |
| listener.nameChanged(set); |
| }, true, true); |
| } |
| |
| @Override |
| public void resourcesChanged(final ChangeSet set, final IPath[] paths) { |
| // Look for any resources that were removed from the set but are still out-of sync. |
| // Re-add those resources |
| final List<SyncInfo> outOfSync = new ArrayList<>(); |
| for (int i = 0; i < paths.length; i++) { |
| IPath path = paths[i]; |
| if (!((DiffChangeSet)set).contains(path)) { |
| SyncInfo info = getSyncInfo(path); |
| if (info != null && info.getKind() != SyncInfo.IN_SYNC) { |
| outOfSync.add(info); |
| } |
| } |
| } |
| if (!outOfSync.isEmpty()) { |
| provider.performUpdate(monitor -> add(outOfSync.toArray(new SyncInfo[outOfSync.size()])), true, true); |
| } |
| } |
| }; |
| |
| /** |
| * Listener that wants to receive change events from this collector |
| */ |
| private IChangeSetChangeListener listener; |
| |
| public ActiveChangeSetCollector(ISynchronizePageConfiguration configuration, ChangeSetModelProvider provider) { |
| this.configuration = configuration; |
| this.provider = provider; |
| getActiveChangeSetManager().addListener(activeChangeSetListener); |
| } |
| |
| public ISynchronizePageConfiguration getConfiguration() { |
| return configuration; |
| } |
| |
| public ActiveChangeSetManager getActiveChangeSetManager() { |
| ISynchronizeParticipant participant = getConfiguration().getParticipant(); |
| if (participant instanceof IChangeSetProvider) { |
| return ((IChangeSetProvider)participant).getChangeSetCapability().getActiveChangeSetManager(); |
| } |
| return null; |
| } |
| |
| /** |
| * Re-populate the change sets from the seed set. |
| * If <code>null</code> is passed, the state |
| * of the collector is cleared but the set is not |
| * re-populated. |
| * <p> |
| * This method is invoked by the model provider when the |
| * model provider changes state. It should not |
| * be invoked by other clients. The model provider |
| * will invoke this method from a particular thread (which may |
| * or may not be the UI thread). Updates done to the collector |
| * from within this thread will be thread-safe and update the view |
| * properly. Updates done from other threads should use the |
| * <code>performUpdate</code> method to ensure the view is |
| * updated properly. |
| * @param seedSet |
| */ |
| public void reset(SyncInfoSet seedSet) { |
| // First, clean up |
| rootSet.clear(); |
| ChangeSet[] sets = activeSets.keySet().toArray(new ChangeSet[activeSets.size()]); |
| for (int i = 0; i < sets.length; i++) { |
| ChangeSet set = sets[i]; |
| remove(set); |
| } |
| activeSets.clear(); |
| |
| // Now re-populate |
| if (seedSet != null) { |
| if (getConfiguration().getComparisonType() == ISynchronizePageConfiguration.THREE_WAY) { |
| // Show all active change sets even if they are empty |
| sets = getActiveChangeSetManager().getSets(); |
| for (int i = 0; i < sets.length; i++) { |
| ChangeSet set = sets[i]; |
| add(set); |
| } |
| // The above will add all sync info that are contained in sets. |
| // We still need to add uncontained infos to the root set |
| SyncInfo[] syncInfos = seedSet.getSyncInfos(); |
| for (int i = 0; i < syncInfos.length; i++) { |
| SyncInfo info = syncInfos[i]; |
| if (isLocalChange(info)) { |
| ChangeSet[] containingSets = findChangeSets(info); |
| if (containingSets.length == 0) { |
| rootSet.add(info); |
| } |
| } |
| } |
| } else { |
| add(seedSet.getSyncInfos()); |
| } |
| } |
| } |
| |
| /** |
| * Handle a sync info set change event from the provider's |
| * seed set. |
| * <p> |
| * This method is invoked by the model provider when the |
| * model provider changes state. It should not |
| * be invoked by other clients. The model provider |
| * will invoke this method from a particular thread (which may |
| * or may not be the UI thread). Updates done to the collector |
| * from within this thread will be thread-safe and update the view |
| * properly. Updates done from other threads should use the |
| * <code>performUpdate</code> method to ensure the view is |
| * updated properly. |
| */ |
| public void handleChange(ISyncInfoSetChangeEvent event) { |
| List<IResource> removals = new ArrayList<>(); |
| List<SyncInfo> additions = new ArrayList<>(); |
| removals.addAll(Arrays.asList(event.getRemovedResources())); |
| additions.addAll(Arrays.asList(event.getAddedResources())); |
| SyncInfo[] changed = event.getChangedResources(); |
| for (int i = 0; i < changed.length; i++) { |
| SyncInfo info = changed[i]; |
| additions.add(info); |
| removals.add(info.getLocal()); |
| } |
| if (!removals.isEmpty()) { |
| remove(removals.toArray(new IResource[removals.size()])); |
| } |
| if (!additions.isEmpty()) { |
| add(additions.toArray(new SyncInfo[additions.size()])); |
| } |
| } |
| |
| /** |
| * Remove the given resources from all sets of this collector. |
| * @param resources the resources to be removed |
| */ |
| protected void remove(IResource[] resources) { |
| for (Iterator iter = activeSets.values().iterator(); iter.hasNext();) { |
| SyncInfoSet set = (SyncInfoSet) iter.next(); |
| set.removeAll(resources); |
| } |
| rootSet.removeAll(resources); |
| } |
| |
| protected void add(SyncInfo[] infos) { |
| rootSet.beginInput(); |
| for (int i = 0; i < infos.length; i++) { |
| SyncInfo info = infos[i]; |
| if (isLocalChange(info) && select(info)) { |
| ChangeSet[] sets = findChangeSets(info); |
| if (sets.length == 0) { |
| rootSet.add(info); |
| } else { |
| for (int j = 0; j < sets.length; j++) { |
| ChangeSet set = sets[j]; |
| SyncInfoSet targetSet = getSyncInfoSet(set); |
| if (targetSet == null) { |
| // This will add all the appropriate sync info to the set |
| createSyncInfoSet(set); |
| } else { |
| targetSet.add(info); |
| } |
| } |
| } |
| } |
| } |
| rootSet.endInput(null); |
| } |
| |
| private ChangeSet[] findChangeSets(SyncInfo info) { |
| ActiveChangeSetManager manager = getActiveChangeSetManager(); |
| ChangeSet[] sets = manager.getSets(); |
| List<ChangeSet> result = new ArrayList<>(); |
| for (int i = 0; i < sets.length; i++) { |
| ChangeSet set = sets[i]; |
| if (set.contains(info.getLocal())) { |
| result.add(set); |
| } |
| } |
| return result.toArray(new ChangeSet[result.size()]); |
| } |
| |
| /* |
| * Return if this sync info is an outgoing change. |
| */ |
| private boolean isLocalChange(SyncInfo info) { |
| if (!info.getComparator().isThreeWay()) { |
| try { |
| // Obtain the sync info from the subscriber and use it to see if the change is local |
| info = ((SubscriberChangeSetManager)getActiveChangeSetManager()).getSubscriber().getSyncInfo(info.getLocal()); |
| } catch (TeamException e) { |
| TeamUIPlugin.log(e); |
| } |
| } |
| return (info.getComparator().isThreeWay() |
| && ((info.getKind() & SyncInfo.DIRECTION_MASK) == SyncInfo.OUTGOING || |
| (info.getKind() & SyncInfo.DIRECTION_MASK) == SyncInfo.CONFLICTING)); |
| } |
| |
| public SyncInfoTree getRootSet() { |
| return rootSet; |
| } |
| |
| /* |
| * Add the set from the collector. |
| */ |
| public void add(ChangeSet set) { |
| SyncInfoSet targetSet = getSyncInfoSet(set); |
| if (targetSet == null) { |
| createSyncInfoSet(set); |
| } |
| if (listener != null) { |
| listener.setAdded(set); |
| } |
| } |
| |
| private SyncInfoTree createSyncInfoSet(ChangeSet set) { |
| SyncInfoTree sis = getSyncInfoSet(set); |
| // Register the listener last since the add will |
| // look for new elements |
| boolean added = false; |
| // Use a variable to ensure that both begin and end are invoked |
| try { |
| if (sis == null) { |
| sis = new SyncInfoTree(); |
| activeSets.put(set, sis); |
| added = true; |
| } |
| sis.beginInput(); |
| if (!sis.isEmpty()) |
| sis.removeAll(sis.getResources()); |
| sis.addAll(getSyncInfos(set)); |
| } finally { |
| if (sis != null) |
| sis.endInput(null); |
| } |
| if (added) { |
| ((DiffChangeSet)set).getDiffTree().addDiffChangeListener(this); |
| if (listener != null) |
| listener.setAdded(set); |
| } |
| return sis; |
| } |
| |
| private SyncInfoSet getSyncInfos(ChangeSet set) { |
| IDiff[] diffs = ((ResourceDiffTree)((DiffChangeSet)set).getDiffTree()).getDiffs(); |
| return asSyncInfoSet(diffs); |
| } |
| |
| private SyncInfoSet asSyncInfoSet(IDiff[] diffs) { |
| SyncInfoSet result = new SyncInfoSet(); |
| for (int i = 0; i < diffs.length; i++) { |
| IDiff diff = diffs[i]; |
| if (select(diff)) { |
| SyncInfo info = asSyncInfo(diff); |
| if (info != null) |
| result.add(info); |
| } |
| } |
| return result; |
| } |
| |
| private SyncInfo asSyncInfo(IDiff diff) { |
| try { |
| return ((SubscriberParticipant)getConfiguration().getParticipant()).getSubscriber().getSyncInfo(ResourceDiffTree.getResourceFor(diff)); |
| } catch (TeamException e) { |
| TeamUIPlugin.log(e); |
| } |
| return null; |
| } |
| |
| private boolean select(IDiff diff) { |
| return getSeedSet().getSyncInfo(ResourceDiffTree.getResourceFor(diff)) != null; |
| } |
| |
| /* private */ SyncInfo getSyncInfo(IPath path) { |
| return getSyncInfo(getSeedSet(), path); |
| } |
| |
| /* private */ IResource[] getResources(SyncInfoSet set, IPath[] paths) { |
| List<IResource> result = new ArrayList<>(); |
| for (int i = 0; i < paths.length; i++) { |
| IPath path = paths[i]; |
| SyncInfo info = getSyncInfo(set, path); |
| if (info != null) { |
| result.add(info.getLocal()); |
| } |
| } |
| return result.toArray(new IResource[result.size()]); |
| } |
| |
| private SyncInfo getSyncInfo(SyncInfoSet set, IPath path) { |
| SyncInfo[] infos = set.getSyncInfos(); |
| for (int i = 0; i < infos.length; i++) { |
| SyncInfo info = infos[i]; |
| if (info.getLocal().getFullPath().equals(path)) |
| return info; |
| } |
| return null; |
| } |
| |
| /* |
| * Remove the set from the collector. |
| */ |
| public void remove(ChangeSet set) { |
| ((DiffChangeSet)set).getDiffTree().removeDiffChangeListener(this); |
| activeSets.remove(set); |
| if (listener != null) { |
| listener.setRemoved(set); |
| } |
| } |
| |
| /* |
| * Return the sync info set for the given active change set |
| * or null if there isn't one. |
| */ |
| public SyncInfoTree getSyncInfoSet(ChangeSet set) { |
| return (SyncInfoTree)activeSets.get(set); |
| } |
| |
| private ChangeSet getChangeSet(IDiffTree tree) { |
| for (Iterator iter = activeSets.keySet().iterator(); iter.hasNext();) { |
| ChangeSet changeSet = (ChangeSet) iter.next(); |
| if (((DiffChangeSet)changeSet).getDiffTree() == tree) { |
| return changeSet; |
| } |
| } |
| return null; |
| } |
| |
| private boolean select(SyncInfo info) { |
| return getSeedSet().getSyncInfo(info.getLocal()) != null; |
| } |
| |
| private SyncInfoSet getSeedSet() { |
| return provider.getSyncInfoSet(); |
| } |
| |
| public void dispose() { |
| getActiveChangeSetManager().removeListener(activeChangeSetListener); |
| } |
| |
| /** |
| * Set the change set listener for this collector. There is |
| * only one for this type of collector. |
| * @param listener change set change listener |
| */ |
| public void setChangeSetChangeListener(IChangeSetChangeListener listener) { |
| this.listener = listener; |
| if (listener == null) { |
| getActiveChangeSetManager().removeListener(activeChangeSetListener); |
| } else { |
| getActiveChangeSetManager().addListener(activeChangeSetListener); |
| } |
| } |
| |
| @Override |
| public void diffsChanged(final IDiffChangeEvent event, IProgressMonitor monitor) { |
| provider.performUpdate(monitor1 -> { |
| ChangeSet changeSet = getChangeSet(event.getTree()); |
| if (changeSet != null) { |
| SyncInfoSet targetSet = getSyncInfoSet(changeSet); |
| if (targetSet != null) { |
| targetSet.removeAll(getResources(targetSet, event.getRemovals())); |
| targetSet.addAll(asSyncInfoSet(event.getAdditions())); |
| targetSet.addAll(asSyncInfoSet(event.getChanges())); |
| rootSet.removeAll(((IResourceDiffTree)event.getTree()).getAffectedResources()); |
| } |
| } |
| }, true /* preserver expansion */, true /* run in UI thread */); |
| } |
| |
| @Override |
| public void propertyChanged(IDiffTree tree, int property, IPath[] paths) { |
| // Nothing to do |
| } |
| } |