blob: 797249982b2b19f0989ed4c03a8ecd448fc19858 [file] [log] [blame]
/*******************************************************************************
* 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
}
}