Skip to main content
summaryrefslogtreecommitdiffstats
blob: ce297516fe9bec5cd0ab6cf9f8a76d71c2027f46 (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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.ui.operations;

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

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.window.Window;
import org.eclipse.team.core.mapping.IResourceMappingScope;
import org.eclipse.team.core.mapping.ISynchronizationContext;
import org.eclipse.team.core.mapping.provider.ScopeGenerator;
import org.eclipse.team.internal.core.mapping.ResourceMappingScope;
import org.eclipse.team.internal.ui.TeamUIMessages;
import org.eclipse.team.internal.ui.dialogs.AdditionalMappingsDialog;
import org.eclipse.ui.IWorkbenchPart;

/**
 * Here's a summary of the input determination scheme
 * <ol>
 * <li>Obtain selected mappings
 * <li>Project mappings onto resources using the appropriate
 * context(s) in order to obtain a set of ResourceTraverals
 * <li>Determine what model providers are interested in the targeted resources
 * <li>From those model providers, obtain the set of affected resource mappings
 * <li>If the original set is the same as the new set, we are done.
 * <li>if the set differs from the original selection, rerun the mapping process
 * for any new mappings
 *     <ul>
 *     <li>Only need to query model providers for mappings for new resources
 *     <li>If new mappings are obtained, 
 *     ask model provider to compress the mappings?
 *     <li>keep repeating until no new mappings or resources are added
 *     </ul> 
 * <li>Use model provider relationships to result?
 * <li>Display the original set and the new set with an explanation
 *     <ul>
 *     <li>The original set and final set may involve mappings from
 *     multiple providers.
 *     <li>The number of providers can be reduced by assuming that
 *     extending models can display the elements of extended models.
 *     Then we are only left with conflicting models.
 *     <li>Could use a content provider approach a.k.a. Common Navigator
 *     or component based approach
 *     </ul> 
 * </ol> 
 * 
 * <p>
 * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
 * part of a work in progress. There is a guarantee neither that this API will
 * work nor that it will remain the same. Please do not use this API without
 * consulting with the Platform/Team team.
 * </p>
 * 
 * @since 3.2
 */
public abstract class ResourceMappingOperation extends ModelProviderOperation {
	
	private static final ScopeGenerator DEFAULT_SCOPE_BUILDER = new ScopeGenerator();
	private final ResourceMapping[] selectedMappings;
	private final ResourceMappingContext context;
	private IResourceMappingScope scope;
    
    /**
     * Create a resource mapping based operation
     * @param part the workspace part from which the operation was launched
     * @param input the input to the operation (which must have already been built by
     * invoking <code>buildInput</code>.
     */
	protected ResourceMappingOperation(IWorkbenchPart part, ResourceMapping[] selectedMappings, ResourceMappingContext context) {
		super(part);
		this.selectedMappings = selectedMappings;
		this.context = context;
	}

	public void run(IProgressMonitor monitor) throws InvocationTargetException,
			InterruptedException {
		buildScope(monitor);
		execute(monitor);
	}

	/**
	 * Adjust the input of the operation according to the selected
	 * resource mappings and the set of interested participants. This method
	 * will prompt the user in the following cases:
	 * <ol>
	 * <li>The scope contains additional resources than those in the input.
	 * <li>The scope has additional mappings from a model in the input
	 * <li>The input contains elements from multiple models
	 * </ol>
	 * @param monitor a progress monitor
	 */
	protected void buildScope(IProgressMonitor monitor) throws InvocationTargetException {
		try {
			scope = getScopeGenerator().prepareScope(selectedMappings, context, monitor);
			IResourceMappingScope inputScope = getScopeGenerator().asInputScope(scope);
			if (scope.hasAdditionalMappings()) {
				boolean prompt = false;
				// There are additional mappings so we may need to prompt
				ModelProvider[] inputModelProviders = inputScope.getModelProviders();
				if (hasAdditionalMappingsFromIndependantModel(inputModelProviders, scope.getModelProviders())) {
					// Prompt if the is a new model provider in the scope that is independant
					// of any of the input mappings
					prompt = true;
				} else if (scope.hasAdditonalResources()) {
					// We definitely need to prompt to indicate that additional resources
					prompt = true;
				} else if (inputModelProviders.length == 1) {
					// We may need to prompt depending on the nature of the additional mappings
					// We need to prompt if the additional mappings are from the same model as
					// the input or if they are from a model that has no relationship to the input model
					String modelProviderId = inputModelProviders[0].getDescriptor().getId();
					ResourceMapping[] mappings = scope.getMappings();
					for (int i = 0; i < mappings.length; i++) {
						ResourceMapping mapping = mappings[i];
						if (inputScope.getTraversals(mapping) == null) {
							// This mapping was not in the input
							String id = mapping.getModelProviderId();
							if (id.equals(modelProviderId)) {
								prompt = true;
								break;
							} else if (isIndependantModel(modelProviderId, id)) {
								prompt = true;
								break;
							}
						}
					}
				} else {
					// We need to prompt if there are additional mappings from an input
					// provider whose traversals overlap those of the input mappings.
					for (int i = 0; i < inputModelProviders.length; i++) {
						ModelProvider provider = inputModelProviders[i];
						String id = provider.getDescriptor().getId();
						ResourceMapping[] inputMappings = inputScope.getMappings(id);
						ResourceMapping[] scopeMappings = scope.getMappings(id);
						if (inputMappings.length != scopeMappings.length) {
							// There are more mappings for this provider.
							// We need to see if any of the new ones overlap the old ones.
							for (int j = 0; j < scopeMappings.length; j++) {
								ResourceMapping mapping = scopeMappings[j];
								ResourceTraversal[] inputTraversals = inputScope.getTraversals(mapping);
								if (inputTraversals == null) {
									// This mapping was not in the input.
									// We need to prompt if the traversal for this mapping overlaps with
									// the input mappings for the model provider
									// TODO could check for project overlap first
									ResourceTraversal[] scopeTraversals = scope.getTraversals(mapping);
									ResourceTraversal[] inputModelTraversals = getTraversals(inputScope, inputMappings);
									if (overlaps(scopeTraversals, inputModelTraversals)) {
										prompt = true;
										break;
									}
								}
							}
						}
					}
				}
				if (prompt)
					promptForInputChange(monitor);
			}
		} catch (CoreException e) {
			throw new InvocationTargetException(e);
		}
	}

	private boolean hasAdditionalMappingsFromIndependantModel(ModelProvider[] inputModelProviders, ModelProvider[] modelProviders) {
		ModelProvider[] additionalProviders = getAdditionalProviders(inputModelProviders, modelProviders);
		for (int i = 0; i < additionalProviders.length; i++) {
			ModelProvider additionalProvider = additionalProviders[i];
			boolean independant = true;
			// Return true if the new provider is independant of all input providers
			for (int j = 0; j < inputModelProviders.length; j++) {
				ModelProvider inputProvider = inputModelProviders[j];
				if (!isIndependantModel(additionalProvider.getDescriptor().getId(), inputProvider.getDescriptor().getId())) {
					independant = false;
				}
			}
			if (independant)
				return true;
		}
		return false;
	}

	private ModelProvider[] getAdditionalProviders(ModelProvider[] inputModelProviders, ModelProvider[] modelProviders) {
		Set input = new HashSet();
		List result = new ArrayList();
		input.addAll(Arrays.asList(inputModelProviders));
		for (int i = 0; i < modelProviders.length; i++) {
			ModelProvider provider = modelProviders[i];
			if (!input.contains(provider))
				result.add(provider);
		}
		return (ModelProvider[]) result.toArray(new ModelProvider[result.size()]);
	}

	private boolean overlaps(ResourceTraversal[] scopeTraversals, ResourceTraversal[] inputModelTraversals) {
		for (int i = 0; i < inputModelTraversals.length; i++) {
			ResourceTraversal inputTraversal = inputModelTraversals[i];
			for (int j = 0; j < scopeTraversals.length; j++) {
				ResourceTraversal scopeTraversal = scopeTraversals[j];
				if (overlaps(inputTraversal, scopeTraversal)) {
					return true;
				}
			}
		}
		return false;
	}

	private boolean overlaps(ResourceTraversal inputTraversal, ResourceTraversal scopeTraversal) {
		IResource[] inputRoots = inputTraversal.getResources();
		IResource[] scopeRoots = scopeTraversal.getResources();
		for (int i = 0; i < scopeRoots.length; i++) {
			IResource scopeResource = scopeRoots[i];
			for (int j = 0; j < inputRoots.length; j++) {
				IResource inputResource = inputRoots[j];
				if (overlaps(scopeResource, scopeTraversal.getDepth(), inputResource, inputTraversal.getDepth()))
					return true;
			}
		}
		return false;
	}

	private boolean overlaps(IResource scopeResource, int scopeDepth, IResource inputResource, int inputDepth) {
		if (scopeResource.equals(inputResource))
			return true;
		if (scopeDepth == IResource.DEPTH_INFINITE && scopeResource.getFullPath().isPrefixOf(inputResource.getFullPath())) {
			return true;
		}
		if (scopeDepth == IResource.DEPTH_ONE && scopeResource.equals(inputResource.getParent())) {
			return true;
		}
		if (inputDepth == IResource.DEPTH_INFINITE && inputResource.getFullPath().isPrefixOf(scopeResource.getFullPath())) {
			return true;
		}
		if (inputDepth == IResource.DEPTH_ONE && inputResource.equals(scopeResource.getParent())) {
			return true;
		}
		return false;
	}

	private ResourceTraversal[] getTraversals(IResourceMappingScope inputScope, ResourceMapping[] inputMappings) {
		List result = new ArrayList();
		for (int i = 0; i < inputMappings.length; i++) {
			ResourceMapping mapping = inputMappings[i];
			result.addAll(Arrays.asList(inputScope.getTraversals(mapping)));
		}
		return ResourceMappingScope.combineTraversals((ResourceTraversal[]) result.toArray(new ResourceTraversal[result.size()]));
	}

	private boolean isIndependantModel(String modelProviderId, String id) {
		IModelProviderDescriptor desc1 = ModelProvider.getModelProviderDescriptor(modelProviderId);
		IModelProviderDescriptor desc2 = ModelProvider.getModelProviderDescriptor(id);
		
		return !(isExtension(desc1, desc2) || isExtension(desc2, desc1));
	}

	/*
	 * Return whether the desc1 model extends the desc2 model
	 */
	private boolean isExtension(IModelProviderDescriptor desc1, IModelProviderDescriptor desc2) {
		String[] ids = desc1.getExtendedModels();
		// First check direct extension
		for (int i = 0; i < ids.length; i++) {
			String id = ids[i];
			if (id.equals(desc2.getId())) {
				return true;
			}
		}
		// Now check for indirect extension
		for (int i = 0; i < ids.length; i++) {
			String id = ids[i];
			IModelProviderDescriptor desc3 = ModelProvider.getModelProviderDescriptor(id);
			if (isExtension(desc3, desc2)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Return the scope builder used to build the scope of this
	 * operation from the input mappings. This method can be
	 * overridden by subclasses.
	 * @return the scope builder used to build the scope of this
	 * operation from the input mappings.
	 */
	protected ScopeGenerator getScopeGenerator() {
		return DEFAULT_SCOPE_BUILDER;
	}

	/**
	 * Prompt the user to inform them that additional resource mappings
	 * have been included in the operations.
	 * @param monitor a progress monitor
	 * @throws OperationCanceledException if the user choose to cancel
	 */
	protected void promptForInputChange(IProgressMonitor monitor) {
		showAllMappings();
	}

    private void showAllMappings() {
        final boolean[] canceled = new boolean[] { false };
        getShell().getDisplay().syncExec(new Runnable() {
            public void run() {
                AdditionalMappingsDialog dialog = new AdditionalMappingsDialog(getShell(), TeamUIMessages.ResourceMappingOperation_0, getScope(), getContext());
                int result = dialog.open();
                canceled[0] = result != Window.OK;
            }
        
        });
        
        if (canceled[0]) {
            throw new OperationCanceledException();
        }
    }
    
	/**
	 * Return the synchronization context for the operation or <code>null</code>
	 * if the operation doesn't have one or if it has not yet been created.
	 * By default, the method always returns <code>null</code>. Subclasses may override.
	 * @return the synchronization context for the operation or <code>null</code>
	 */
	protected ISynchronizationContext getContext() {
		return null;
	}

	protected abstract void execute(IProgressMonitor monitor) throws InvocationTargetException,
			InterruptedException;

	public IResourceMappingScope getScope() {
		return scope;
	}
	
}

Back to the top