Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 7055649090493b5809678c65917d46bab961316a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/*******************************************************************************
 * Copyright (c) 2000, 2010 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.ccvs.ui.operations;

import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.CVSStatus;
import org.eclipse.team.internal.ccvs.ui.*;
import org.eclipse.team.internal.ccvs.ui.console.CVSOutputConsole;
import org.eclipse.team.internal.ui.dialogs.MultipleYesNoPrompter;
import org.eclipse.team.ui.TeamOperation;
import org.eclipse.ui.IWorkbenchPart;


/**
 * This class is the abstract superclass for CVS operations. It provides
 * error handling, prompting and other UI.
 */
public abstract class CVSOperation extends TeamOperation implements IShellProvider {

	private int statusCount;

	private boolean involvesMultipleResources = false;

	private List<IStatus> errors = new ArrayList<>(); // of IStatus

	protected static final IStatus OK = Status.OK_STATUS; 
	
	private Shell shell;

	private MultipleYesNoPrompter prompter;
	
	protected CVSOperation(IWorkbenchPart part) {
		super(part);
	}
	
	@Override
	protected String getJobName() {
		return getTaskName();
	}
		
	@Override
	protected URL getOperationIcon() {
		return Platform.find(CVSUIPlugin.getPlugin().getBundle(), new Path(ICVSUIConstants.ICON_PATH + ICVSUIConstants.IMG_CVS_PERSPECTIVE));
	}
	
	@Override
	public final void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
		startOperation();
		try {
			monitor = Policy.monitorFor(monitor);
			monitor.beginTask(null, 100);
			monitor.setTaskName(getTaskName());
			execute(Policy.subMonitorFor(monitor, 100));
			endOperation();
		} catch (CVSException e) {
			// TODO: errors may not be empty (i.e. endOperation has not been executed)
			throw new InvocationTargetException(e);
		} finally {
			monitor.done();
		}
	}
	
	protected void startOperation() {
		statusCount = 0;
		resetErrors();
	}
	
	protected void endOperation() throws CVSException {
		handleErrors(errors.toArray(new IStatus[errors.size()]));
	}

	/**
	 * Subclasses must override this method to perform the operation.
	 * Clients should never call this method directly.
	 * 
	 * @param monitor
	 * @throws CVSException
	 * @throws InterruptedException
	 */
	protected abstract void execute(IProgressMonitor monitor) throws CVSException, InterruptedException;

	protected void addError(IStatus status) {
		if (status.isOK()) return;
		if (isLastError(status)) return;
		errors.add(status);
	}

	protected void collectStatus(IStatus status)  {
		if (isLastError(status)) return;
		statusCount++;
		if (!status.isOK()) addError(status);
	}
	
	protected void resetErrors() {
		errors.clear();
		statusCount = 0;
	}
	
	protected IStatus[] getErrors() {
		return errors.toArray(new IStatus[errors.size()]);
	}
	
	/**
	 * Get the last error taht occured. This can be useful when a method
	 * has a return type but wants to signal an error. The method in question
	 * can add the error using <code>addError(IStatus)</code> and return null.
	 * The caller can then query the error using this method. Also, <code>addError(IStatus)</code>
	 * will not add the error if it is already on the end of the list (using identity comparison)
	 * which allows the caller to still perform a <code>collectStatus(IStatus)</code>
	 * to get a valid operation count.
	 * @return
	 */
	protected IStatus getLastError() {
		Assert.isTrue(errors.size() > 0);
		IStatus status = errors.get(errors.size() - 1);
		return status;
	}
	
	private boolean isLastError(IStatus status) {
		return (errors.size() > 0 && getLastError() == status);
	}
	
	/**
	 * Throw an exception that contains the given error status
	 * @param errors the errors that occurred during the operation
	 * @throws CVSException an exception that wraps the errors
	 */
	protected void asException(IStatus[] errors) throws CVSException {
		if (errors.length == 0) return;
		if (errors.length == 1 && statusCount == 1)  {
			throw new CVSException(errors[0]);
		}
		MultiStatus result = new MultiStatus(CVSUIPlugin.ID, 0, getErrorMessage(errors, statusCount), null);
		for (int i = 0; i < errors.length; i++) {
			IStatus s = errors[i];
			if (s.isMultiStatus()) {
				result.add(new CVSStatus(s.getSeverity(), s.getMessage(), s.getException()));
				result.addAll(s);
			} else {
				result.add(s);
			}
		}
		throw new CVSException(result);
	}

	/**
	 * Handle the errors that occured during an operation.
	 * The default is to throw an exception containing an status
	 * that are reportable (determined using <code>isReportableError</code>).
	 * @param errors the errors that occurred during the operation.
	 * Subclasses may override.
	 * @throws CVSException an exception if appropriate
	 */
	protected final void handleErrors(IStatus[] errors) throws CVSException {
		// We are only concerned with reportable errors.
	    // Others will appear in the console
		List<IStatus> reportableErrors = new ArrayList<>();
		for (int i = 0; i < errors.length; i++) {
			IStatus status = errors[i];
			if (isReportableError(status)) {
				reportableErrors.add(status);
			} else if (status.isMultiStatus()) {
				IStatus[] children = status.getChildren();
				for (int j = 0; j < children.length; j++) {
					IStatus child = children[j];
					if (isReportableError(child)) {
						reportableErrors.add(status);
						break;
					}
				}
			}
		}
		if (!reportableErrors.isEmpty())
		    asException(reportableErrors.toArray(new IStatus[reportableErrors.size()]));
	}

	/**
	 * Return whether the given status is reportable. By default,
	 * only server errors are reportable. Subclasses may override.
	 * @param status an error status
	 * @return whether the status is reportable or should be ignored
	 */
    protected boolean isReportableError(IStatus status) {
        return status.getCode() == CVSStatus.SERVER_ERROR || CVSStatus.isInternalError(status) || status.getCode() == TeamException.UNABLE;
    }

    protected String getErrorMessage(IStatus[] failures, int totalOperations) {
		return NLS.bind(CVSUIMessages.CVSOperation_0, new String[] { String.valueOf(failures.length), String.valueOf(totalOperations) }); 
	}

	/**
	 * This method prompts the user to overwrite an existing resource. It uses the
	 * <code>involvesMultipleResources</code> to determine what buttons to show.
	 * @param resource 
	 * @param project
	 * @return
	 */
	protected boolean promptToOverwrite(final String title, final String message, IResource resource) {
		if (prompter == null) {
			prompter = new MultipleYesNoPrompter(this, title, involvesMultipleResources(), false);
		} else {
			prompter.setTitle(title);
		}
		try {
			return prompter.shouldInclude(message);
		} catch (InterruptedException e) {
			throw new OperationCanceledException();
		}
	}

	/**
	 * This method is used by <code>promptToOverwrite</code> to determine which 
	 * buttons to show in the prompter.
	 * 
	 * @return
	 */
	protected boolean involvesMultipleResources() {
		return involvesMultipleResources;
	}

	public void setInvolvesMultipleResources(boolean b) {
		involvesMultipleResources = b;
	}

	/**
	 * Return the string that is to be used as the task name for the operation
	 * 
	 * @return the task name
	 */
	protected abstract String getTaskName();
	
	/**
	 * Return true if any of the accumulated status have a severity of ERROR
	 * @return
	 */
	protected boolean errorsOccurred() {
		for (Iterator iter = errors.iterator(); iter.hasNext();) {
			IStatus status = (IStatus) iter.next();
			if (isReportableError(status)) return true;
			if (status.isMultiStatus()) {
				IStatus[] children = status.getChildren();
				for (int j = 0; j < children.length; j++) {
					IStatus child = children[j];
					if (isReportableError(child)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	@Override
	public Shell getShell() {
		// Use the shell assigned to the operation if possible
		if (shell != null && !shell.isDisposed()) {
			return shell;
		}
		return super.getShell();
	}
	
	/**
	 * Set the shell to be used by the operation. This only needs
	 * to be done if the operation does not have a workbench part.
	 * For example, if the operation is being run in a wizard.
	 * @param shell The shell to set.
	 */
	public void setShell(Shell shell) {
		this.shell = shell;
	}
	
	@Override
	protected boolean canRunAsJob() {
		// Put CVS jobs in the background by default.
		return true;
	}
	
    @Override
	protected boolean isSameFamilyAs(TeamOperation operation) {
        // Trat all CVS operations as a single family
        return operation instanceof CVSOperation;
    }
    
    /*
     * Action to show the console that can be used by subclasses
     * that wish to link the progress service to the console
     */
    protected IAction getShowConsoleAction() {
        // Show the console as the goto action
        return new Action(CVSUIMessages.CVSOperation_1) { 
            @Override
			public void run() {
                CVSOutputConsole console = CVSUIPlugin.getPlugin().getConsole();
                if (console != null)
                    console.show(true);
            }
            @Override
			public String getToolTipText() {
                return CVSUIMessages.CVSOperation_2; 
            }
        };
    }
}

Back to the top