blob: c34010237dd41f2eb7d7baaef822572b85f15c62 [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.core.subscribers;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.mapping.provider.ResourceDiffTree;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.internal.core.BackgroundEventHandler;
import org.eclipse.team.internal.core.Messages;
import org.eclipse.team.internal.core.Policy;
import org.eclipse.team.internal.core.TeamPlugin;
import org.osgi.service.prefs.Preferences;
/**
* This class manages the active change sets associated with a subscriber.
*/
public class SubscriberChangeSetManager extends ActiveChangeSetManager {
private static final String PREF_CHANGE_SETS = "changeSets"; //$NON-NLS-1$
private static final int RESOURCE_REMOVAL = 1;
private static final int RESOURCE_CHANGE = 2;
private EventHandler handler;
private ResourceCollector collector;
/*
* Background event handler for serializing and batching change set changes
*/
private class EventHandler extends BackgroundEventHandler {
private List<Event> dispatchEvents = new ArrayList<>();
protected EventHandler(String jobName, String errorTitle) {
super(jobName, errorTitle);
}
@Override
protected void processEvent(Event event, IProgressMonitor monitor) throws CoreException {
// Handle everything in the dispatch
if (isShutdown())
throw new OperationCanceledException();
dispatchEvents.add(event);
}
@Override
protected boolean doDispatchEvents(IProgressMonitor monitor) throws TeamException {
if (dispatchEvents.isEmpty()) {
return false;
}
if (isShutdown())
throw new OperationCanceledException();
ResourceDiffTree[] locked = null;
try {
locked = beginDispath();
for (Iterator iter = dispatchEvents.iterator(); iter.hasNext();) {
Event event = (Event) iter.next();
switch (event.getType()) {
case RESOURCE_REMOVAL:
handleRemove(event.getResource());
break;
case RESOURCE_CHANGE:
handleChange(event.getResource(), ((ResourceEvent)event).getDepth());
break;
default:
break;
}
if (isShutdown())
throw new OperationCanceledException();
}
} catch (CoreException e) {
throw TeamException.asTeamException(e);
} finally {
try {
endDispatch(locked, monitor);
} finally {
dispatchEvents.clear();
}
}
return true;
}
/*
* Begin input on all the sets and return the sync sets that were
* locked. If this method throws an exception then the client
* can assume that no sets were locked
*/
private ResourceDiffTree[] beginDispath() {
ChangeSet[] sets = getSets();
List<ResourceDiffTree> lockedSets = new ArrayList<>();
try {
for (int i = 0; i < sets.length; i++) {
ActiveChangeSet set = (ActiveChangeSet)sets[i];
ResourceDiffTree tree = set.internalGetDiffTree();
lockedSets.add(tree);
tree.beginInput();
}
return lockedSets.toArray(new ResourceDiffTree[lockedSets.size()]);
} catch (RuntimeException e) {
try {
for (Iterator iter = lockedSets.iterator(); iter.hasNext();) {
ResourceDiffTree tree = (ResourceDiffTree) iter.next();
try {
tree.endInput(null);
} catch (Throwable e1) {
// Ignore so that original exception is not masked
}
}
} catch (Throwable e1) {
// Ignore so that original exception is not masked
}
throw e;
}
}
private void endDispatch(ResourceDiffTree[] locked, IProgressMonitor monitor) {
if (locked == null) {
// The begin failed so there's nothing to unlock
return;
}
monitor.beginTask(null, 100 * locked.length);
for (int i = 0; i < locked.length; i++) {
ResourceDiffTree tree = locked[i];
try {
tree.endInput(Policy.subMonitorFor(monitor, 100));
} catch (RuntimeException e) {
// Don't worry about ending every set if an error occurs.
// Instead, log the error and suggest a restart.
TeamPlugin.log(IStatus.ERROR, Messages.SubscriberChangeSetCollector_0, e);
throw e;
}
}
monitor.done();
}
@Override
protected synchronized void queueEvent(Event event, boolean front) {
// Override to allow access from enclosing class
super.queueEvent(event, front);
}
/*
* Handle the removal
*/
private void handleRemove(IResource resource) {
ChangeSet[] sets = getSets();
for (int i = 0; i < sets.length; i++) {
ChangeSet set = sets[i];
// This will remove any descendants from the set and callback to
// resourcesChanged which will batch changes
if (!set.isEmpty()) {
set.rootRemoved(resource, IResource.DEPTH_INFINITE);
if (set.isEmpty()) {
remove(set);
}
}
}
}
/*
* Handle the change
*/
private void handleChange(IResource resource, int depth) throws CoreException {
IDiff diff = getDiff(resource);
if (isModified(diff)) {
ActiveChangeSet[] containingSets = getContainingSets(resource);
if (containingSets.length == 0) {
// Consider for inclusion in the default set
// if the resource is not already a member of another set
if (getDefaultSet() != null) {
getDefaultSet().add(diff);
}
} else {
for (int i = 0; i < containingSets.length; i++) {
ActiveChangeSet set = containingSets[i];
// Update the sync info in the set
set.add(diff);
}
}
} else {
removeFromAllSets(resource);
}
if (depth != IResource.DEPTH_ZERO) {
IResource[] members = getSubscriber().members(resource);
for (int i = 0; i < members.length; i++) {
IResource member = members[i];
handleChange(member, depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : IResource.DEPTH_INFINITE);
}
}
}
private void removeFromAllSets(IResource resource) {
List<ChangeSet> toRemove = new ArrayList<>();
ChangeSet[] sets = getSets();
for (int i = 0; i < sets.length; i++) {
ChangeSet set = sets[i];
if (set.contains(resource)) {
set.remove(resource);
if (set.isEmpty()) {
toRemove.add(set);
}
}
}
for (Object element : toRemove) {
ActiveChangeSet set = (ActiveChangeSet) element;
remove(set);
}
}
private ActiveChangeSet[] getContainingSets(IResource resource) {
Set<ActiveChangeSet> result = new HashSet<>();
ChangeSet[] sets = getSets();
for (int i = 0; i < sets.length; i++) {
ChangeSet set = sets[i];
if (set.contains(resource)) {
result.add((ActiveChangeSet) set);
}
}
return result.toArray(new ActiveChangeSet[result.size()]);
}
}
private class ResourceCollector extends SubscriberResourceCollector {
public ResourceCollector(Subscriber subscriber) {
super(subscriber);
}
@Override
protected void remove(IResource resource) {
if (handler != null)
handler.queueEvent(new BackgroundEventHandler.ResourceEvent(resource, RESOURCE_REMOVAL, IResource.DEPTH_INFINITE), false);
}
@Override
protected void change(IResource resource, int depth) {
if (handler != null)
handler.queueEvent(new BackgroundEventHandler.ResourceEvent(resource, RESOURCE_CHANGE, depth), false);
}
@Override
protected boolean hasMembers(IResource resource) {
return SubscriberChangeSetManager.this.hasMembers(resource);
}
}
public SubscriberChangeSetManager(Subscriber subscriber) {
collector = new ResourceCollector(subscriber);
handler = new EventHandler(NLS.bind(Messages.SubscriberChangeSetCollector_1, new String[] { subscriber.getName() }), NLS.bind(Messages.SubscriberChangeSetCollector_2, new String[] { subscriber.getName() })); //
}
@Override
protected void initializeSets() {
load(getPreferences());
}
public boolean hasMembers(IResource resource) {
ChangeSet[] sets = getSets();
for (int i = 0; i < sets.length; i++) {
ActiveChangeSet set = (ActiveChangeSet)sets[i];
if (set.getDiffTree().getChildren(resource.getFullPath()).length > 0)
return true;
}
if (getDefaultSet() != null)
return (getDefaultSet().getDiffTree().getChildren(resource.getFullPath()).length > 0);
return false;
}
/**
* Return the sync info for the given resource obtained
* from the subscriber.
* @param resource the resource
* @return the sync info for the resource
* @throws CoreException
*/
@Override
public IDiff getDiff(IResource resource) throws CoreException {
Subscriber subscriber = getSubscriber();
return subscriber.getDiff(resource);
}
/**
* Return the subscriber associated with this collector.
* @return the subscriber associated with this collector
*/
public Subscriber getSubscriber() {
return collector.getSubscriber();
}
@Override
public void dispose() {
handler.shutdown();
collector.dispose();
super.dispose();
save(getPreferences());
}
private Preferences getPreferences() {
return getParentPreferences().node(getSubscriberIdentifier());
}
private static Preferences getParentPreferences() {
return getTeamPreferences().node(PREF_CHANGE_SETS);
}
private static Preferences getTeamPreferences() {
return InstanceScope.INSTANCE.getNode(TeamPlugin.getPlugin().getBundle().getSymbolicName());
}
/**
* Return the id that will uniquely identify the subscriber across
* restarts.
* @return the id that will uniquely identify the subscriber across
*/
protected String getSubscriberIdentifier() {
return getSubscriber().getName();
}
/**
* Wait until the collector is done processing any events.
* This method is for testing purposes only.
* @param monitor
*/
public void waitUntilDone(IProgressMonitor monitor) {
monitor.worked(1);
// wait for the event handler to process changes.
while(handler.getEventHandlerJob().getState() != Job.NONE) {
monitor.worked(1);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
Policy.checkCanceled(monitor);
}
monitor.worked(1);
}
@Override
protected String getName() {
return getSubscriber().getName();
}
}