SelectionUtils now contains methods to return multiple repositories
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / selection / SelectionUtils.java
blob9729f0b7353f36a2bd424c982fc92a1406213d62
1 /*******************************************************************************
2 * Copyright (C) 2014, 2016 Robin Stocker <robin@nibor.org> and others.
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
11 * Contributors:
12 * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 486857
13 *******************************************************************************/
14 package org.eclipse.egit.ui.internal.selection;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.Iterator;
21 import java.util.LinkedHashSet;
22 import java.util.List;
23 import java.util.Set;
25 import org.eclipse.core.expressions.IEvaluationContext;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.resources.IResource;
28 import org.eclipse.core.resources.mapping.ResourceMapping;
29 import org.eclipse.core.runtime.IPath;
30 import org.eclipse.egit.core.AdapterUtils;
31 import org.eclipse.egit.core.internal.util.ResourceUtil;
32 import org.eclipse.egit.core.project.RepositoryMapping;
33 import org.eclipse.egit.ui.internal.CommonUtils;
34 import org.eclipse.egit.ui.internal.UIText;
35 import org.eclipse.egit.ui.internal.history.HistoryPageInput;
36 import org.eclipse.egit.ui.internal.revision.FileRevisionEditorInput;
37 import org.eclipse.egit.ui.internal.trace.GitTraceLocation;
38 import org.eclipse.jface.dialogs.MessageDialog;
39 import org.eclipse.jface.text.ITextSelection;
40 import org.eclipse.jface.viewers.ISelection;
41 import org.eclipse.jface.viewers.IStructuredSelection;
42 import org.eclipse.jface.viewers.StructuredSelection;
43 import org.eclipse.jgit.annotations.NonNull;
44 import org.eclipse.jgit.annotations.Nullable;
45 import org.eclipse.jgit.lib.Repository;
46 import org.eclipse.swt.widgets.Shell;
47 import org.eclipse.team.core.history.IFileRevision;
48 import org.eclipse.ui.IEditorInput;
49 import org.eclipse.ui.IEditorPart;
50 import org.eclipse.ui.ISources;
51 import org.eclipse.ui.IWorkbenchWindow;
52 import org.eclipse.ui.PlatformUI;
53 import org.eclipse.ui.handlers.IHandlerService;
54 import org.eclipse.ui.part.MultiPageEditorPart;
56 /**
57 * Utilities for working with selections.
59 public class SelectionUtils {
61 /**
62 * @param selection
63 * @return the single selected repository, or <code>null</code>
65 @Nullable
66 public static Repository getRepository(
67 @NonNull IStructuredSelection selection) {
68 return getRepository(false, selection, null);
71 /**
72 * @param evaluationContext
73 * @return array of selected repositories
75 @NonNull
76 public static Repository[] getRepositories(
77 @Nullable IEvaluationContext evaluationContext) {
78 return getRepositories(getSelection(evaluationContext));
81 /**
82 * Retrieves all the repositories associated with the current selection. It
83 * attempts to first identify the selections as projects and if that yields
84 * an empty result, it then changes to adapt the selections to the
85 * repository class
87 * @param selection
88 * @return array of repositories
90 @NonNull
91 public static Repository[] getRepositories(
92 @NonNull IStructuredSelection selection) {
94 IProject[] selectedProjects = getSelectedProjects(selection);
96 if (selectedProjects.length > 0)
97 return getRepositoriesFor(selectedProjects);
99 if (selection.isEmpty()) {
100 return new Repository[0];
103 Set<Repository> repos = new LinkedHashSet<>();
104 for (Object o : selection.toArray()) {
105 Repository repo = AdapterUtils.adapt(o, Repository.class);
106 if (repo != null) {
107 repos.add(repo);
108 } else {
109 // no repository found for one of the objects!
110 return new Repository[0];
113 return repos.toArray(new Repository[0]);
117 * @param evaluationContext
118 * @return the single selected repository, or <code>null</code>
120 @Nullable
121 public static Repository getRepository(
122 @Nullable IEvaluationContext evaluationContext) {
123 return getRepository(false, getSelection(evaluationContext), null);
127 * Get the single selected repository or warn if no repository or multiple
128 * different repositories could be found.
130 * @param selection
131 * @param shell
132 * the shell for showing the warning
133 * @return the single selected repository, or <code>null</code>
135 @Nullable
136 public static Repository getRepositoryOrWarn(
137 @NonNull IStructuredSelection selection, @NonNull Shell shell) {
138 return getRepository(true, selection, shell);
142 * @param context
143 * @return the structured selection of the evaluation context
145 @NonNull
146 public static IStructuredSelection getSelection(
147 @Nullable IEvaluationContext context) {
148 if (context == null)
149 return StructuredSelection.EMPTY;
151 Object selection = context
152 .getVariable(ISources.ACTIVE_MENU_SELECTION_NAME);
153 if (!(selection instanceof ISelection))
154 selection = context
155 .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
157 if (selection instanceof ITextSelection)
158 return getSelectionFromEditorInput(context);
159 else if (selection instanceof IStructuredSelection)
160 return (IStructuredSelection) selection;
161 return StructuredSelection.EMPTY;
165 * Tries to convert the passed selection to a structured selection.
166 * <p>
167 * E.g. in case of a text selection, it is converted to be a selection of
168 * the resource that is in the editor.
170 * @param selection
171 * @return the structured selection, or an empty selection
173 @NonNull
174 public static IStructuredSelection getStructuredSelection(
175 @NonNull ISelection selection) {
176 if (selection instanceof ITextSelection) {
177 IEvaluationContext evaluationContext = getEvaluationContext();
178 if (evaluationContext == null) {
179 return StructuredSelection.EMPTY;
181 return getSelectionFromEditorInput(evaluationContext);
182 } else if (selection instanceof IStructuredSelection) {
183 return (IStructuredSelection) selection;
185 return StructuredSelection.EMPTY;
189 * @param selection
190 * @return the selected locations
192 @NonNull
193 public static IPath[] getSelectedLocations(
194 @NonNull IStructuredSelection selection) {
195 Set<IPath> result = new LinkedHashSet<>();
196 for (Object o : selection.toList()) {
197 IResource resource = AdapterUtils.adaptToAnyResource(o);
198 if (resource != null) {
199 IPath location = resource.getLocation();
200 if (location != null)
201 result.add(location);
202 } else {
203 IPath location = AdapterUtils.adapt(o, IPath.class);
204 if (location != null)
205 result.add(location);
206 else
207 for (IResource r : extractResourcesFromMapping(o)) {
208 IPath l = r.getLocation();
209 if (l != null)
210 result.add(l);
214 return result.toArray(new IPath[0]);
218 * Returns the resources contained in the given selection.
220 * @param selection
221 * @return the resources in the selection
223 @NonNull
224 public static IResource[] getSelectedResources(
225 @NonNull IStructuredSelection selection) {
226 Set<IResource> result = getSelectedResourcesSet(selection);
227 return result.toArray(new IResource[0]);
231 * Returns the resources contained in the given selection.
233 * @param selection
234 * @return the resources in the selection
236 @NonNull
237 private static Set<IResource> getSelectedResourcesSet(
238 @NonNull IStructuredSelection selection) {
239 Set<IResource> result = new LinkedHashSet<>();
240 for (Object o : selection.toList()) {
241 IResource resource = AdapterUtils.adaptToAnyResource(o);
242 if (resource != null)
243 result.add(resource);
244 else
245 result.addAll(extractResourcesFromMapping(o));
247 return result;
250 private static List<IResource> extractResourcesFromMapping(Object o) {
251 ResourceMapping mapping = AdapterUtils.adapt(o, ResourceMapping.class);
252 return ResourceUtil.extractResourcesFromMapping(mapping);
256 * Determines the most fitting {@link HistoryPageInput} for the given
257 * {@link IStructuredSelection}. The {@code mandatoryObject} must be
258 * contained in the selection and in a repository.
259 * <p>
260 * Most fitting means that the input will contain all selected resources
261 * which are contained in the same repository as the given
262 * {@code mandatoryObject}.
263 * </p>
265 * @param selection
266 * The selection for which the most fitting HistoryPageInput is
267 * to be determined.
268 * @param mandatoryObject
269 * The object to which the HistoryPageInput is tailored. Must be
270 * contained in the given selection and in a repository.
271 * @return The most fitting HistoryPageInput. Will return {@code null} when
272 * the {@code mandatoryObject} is not contained in the given
273 * selection or in a repository.
275 @Nullable
276 public static HistoryPageInput getMostFittingInput(
277 @NonNull IStructuredSelection selection, Object mandatoryObject) {
278 Set<IResource> resources = getSelectedResourcesSet(selection);
279 if (!resources.contains(mandatoryObject)) {
280 return null;
283 Repository repository = ResourceUtil
284 .getRepository((IResource) mandatoryObject);
285 if (repository == null) {
286 return null;
289 for (Iterator<IResource> it = resources.iterator(); it.hasNext();) {
290 IResource resource = it.next();
291 if (ResourceUtil.getRepository(resource) != repository) {
292 it.remove();
296 IResource[] resourceArray = resources.toArray(new IResource[0]);
297 return new HistoryPageInput(repository, resourceArray);
301 * Determines a set of either {@link Repository}, {@link IResource}s or
302 * {@link IPath}s from a selection. For selection contents that adapt to
303 * {@link Repository}, {@link IResource} or {@link ResourceMapping}, the
304 * containing {@link Repository}s or {@link IResource}s are included in the
305 * result set; otherwise for selection contents that adapt to {@link IPath}
306 * these paths are included.
308 * @param selection
309 * to process
310 * @return the set of {@link Repository}, {@link IResource} and
311 * {@link IPath} objects from the selection; not containing
312 * {@code null} values
314 @NonNull
315 private static Set<Object> getSelectionContents(
316 @NonNull IStructuredSelection selection) {
317 Set<Object> result = new HashSet<>();
318 for (Object o : selection.toList()) {
319 Repository r = AdapterUtils.adapt(o, Repository.class);
320 if (r != null) {
321 result.add(r);
322 continue;
324 IResource resource = AdapterUtils.adaptToAnyResource(o);
325 if (resource != null) {
326 result.add(resource);
327 continue;
329 ResourceMapping mapping = AdapterUtils.adapt(o,
330 ResourceMapping.class);
331 if (mapping != null) {
332 result.addAll(extractResourcesFromMapping(mapping));
333 } else {
334 IPath location = AdapterUtils.adapt(o, IPath.class);
335 if (location != null) {
336 result.add(location);
340 return result;
344 * Figure out which repository to use. All selected resources must map to
345 * the same Git repository.
347 * @param warn
348 * Put up a message dialog to warn why a resource was not
349 * selected
350 * @param selection
351 * @param shell
352 * must be provided if warn = true
353 * @return repository for current project, or null
355 @Nullable
356 private static Repository getRepository(boolean warn,
357 @NonNull IStructuredSelection selection, Shell shell) {
358 if (selection.isEmpty()) {
359 return null;
361 Set<Object> elements = getSelectionContents(selection);
362 if (GitTraceLocation.SELECTION.isActive())
363 GitTraceLocation.getTrace().trace(
364 GitTraceLocation.SELECTION.getLocation(), "selection=" //$NON-NLS-1$
365 + selection + ", elements=" + elements.toString()); //$NON-NLS-1$
367 boolean hadNull = false;
368 Repository result = null;
369 for (Object location : elements) {
370 Repository repo = null;
371 if (location instanceof Repository) {
372 repo = (Repository) location;
373 } else if (location instanceof IResource) {
374 repo = ResourceUtil.getRepository((IResource) location);
375 } else if (location instanceof IPath) {
376 repo = ResourceUtil.getRepository((IPath) location);
378 if (repo == null) {
379 hadNull = true;
381 if (result == null) {
382 result = repo;
384 boolean mismatch = hadNull && result != null;
385 if (mismatch || result != repo) {
386 if (warn) {
387 MessageDialog.openError(shell,
388 UIText.RepositoryAction_multiRepoSelectionTitle,
389 UIText.RepositoryAction_multiRepoSelection);
391 return null;
395 if (result == null) {
396 for (Object o : selection.toArray()) {
397 Repository nextRepo = AdapterUtils.adapt(o, Repository.class);
398 if (nextRepo != null && result != null && result != nextRepo) {
399 if (warn)
400 MessageDialog
401 .openError(
402 shell,
403 UIText.RepositoryAction_multiRepoSelectionTitle,
404 UIText.RepositoryAction_multiRepoSelection);
405 return null;
407 result = nextRepo;
411 if (result == null) {
412 if (warn)
413 MessageDialog.openError(shell,
414 UIText.RepositoryAction_errorFindingRepoTitle,
415 UIText.RepositoryAction_errorFindingRepo);
416 return null;
419 return result;
422 private static IStructuredSelection getSelectionFromEditorInput(
423 IEvaluationContext context) {
424 Object part = context.getVariable(ISources.ACTIVE_PART_NAME);
425 if (!(part instanceof IEditorPart)) {
426 return StructuredSelection.EMPTY;
428 Object object = context.getVariable(ISources.ACTIVE_EDITOR_INPUT_NAME);
429 Object editor = context.getVariable(ISources.ACTIVE_EDITOR_NAME);
430 if (editor instanceof MultiPageEditorPart) {
431 Object nestedEditor = ((MultiPageEditorPart) editor)
432 .getSelectedPage();
433 if (nestedEditor instanceof IEditorPart) {
434 object = ((IEditorPart) nestedEditor).getEditorInput();
437 if (!(object instanceof IEditorInput)
438 && (editor instanceof IEditorPart)) {
439 object = ((IEditorPart) editor).getEditorInput();
441 if (object instanceof IEditorInput) {
442 IEditorInput editorInput = (IEditorInput) object;
443 // Note that there is both a getResource(IEditorInput) as well as a
444 // getResource(Object), which don't do the same thing. We explicitly
445 // want the first here.
446 IResource resource = org.eclipse.ui.ide.ResourceUtil
447 .getResource(editorInput);
448 if (resource != null)
449 return new StructuredSelection(resource);
450 if (editorInput instanceof FileRevisionEditorInput) {
451 FileRevisionEditorInput fileRevisionEditorInput = (FileRevisionEditorInput) editorInput;
452 IFileRevision fileRevision = fileRevisionEditorInput
453 .getFileRevision();
454 if (fileRevision != null)
455 return new StructuredSelection(fileRevision);
459 return StructuredSelection.EMPTY;
462 private static IEvaluationContext getEvaluationContext() {
463 IEvaluationContext ctx;
464 IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench()
465 .getActiveWorkbenchWindow();
466 // no active window during Eclipse shutdown
467 if (activeWorkbenchWindow == null)
468 return null;
469 IHandlerService hsr = CommonUtils.getService(activeWorkbenchWindow, IHandlerService.class);
470 ctx = hsr.getCurrentState();
471 return ctx;
475 * Retrieve the list of projects that contains the given resources. All
476 * resources must actually map to a project shared with egit, otherwise an
477 * empty array is returned. In case of a linked resource, the project
478 * returned is the one that contains the link target and is shared with
479 * egit, if any, otherwise an empty array is also returned.
481 * @param selection
482 * @return the projects hosting the selected resources
484 public static IProject[] getSelectedProjects(
485 IStructuredSelection selection) {
486 Set<IProject> ret = new LinkedHashSet<>();
487 for (IResource resource : getSelectedAdaptables(selection,
488 IResource.class)) {
489 RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
490 if (mapping != null && (mapping.getContainer() instanceof IProject))
491 ret.add((IProject) mapping.getContainer());
492 else
493 return new IProject[0];
495 ret.addAll(extractProjectsFromMappings(selection));
497 return ret.toArray(new IProject[0]);
500 private static Set<IProject> extractProjectsFromMappings(
501 IStructuredSelection selection) {
502 Set<IProject> ret = new LinkedHashSet<>();
503 for (ResourceMapping mapping : getSelectedAdaptables(selection,
504 ResourceMapping.class)) {
505 IProject[] mappedProjects = mapping.getProjects();
506 if (mappedProjects != null && mappedProjects.length != 0) {
507 // Some mappings (WorkingSetResourceMapping) return the projects
508 // in unpredictable order. Sort them like the navigator to
509 // correspond to the order the user usually sees.
510 List<IProject> projects = new ArrayList<>(
511 Arrays.asList(mappedProjects));
512 Collections.sort(projects,
513 CommonUtils.RESOURCE_NAME_COMPARATOR);
514 ret.addAll(projects);
517 return ret;
521 * Creates an array of the given class type containing all the objects in
522 * the selection that adapt to the given class.
524 * @param selection
525 * @param c
526 * @return the selected adaptables
528 @NonNull
529 private static <T> List<T> getSelectedAdaptables(ISelection selection,
530 Class<T> c) {
531 List<T> result;
532 if (selection != null && !selection.isEmpty()) {
533 result = new ArrayList<>();
534 Iterator elements = ((IStructuredSelection) selection).iterator();
535 while (elements.hasNext()) {
536 T adapter = AdapterUtils.adapt(elements.next(), c);
537 if (adapter != null) {
538 result.add(adapter);
541 } else {
542 result = Collections.emptyList();
544 return result;
548 * @param projects
549 * a list of projects
550 * @return the repositories that projects map to if all projects are mapped
552 @NonNull
553 private static Repository[] getRepositoriesFor(final IProject[] projects) {
554 Set<Repository> ret = new LinkedHashSet<>();
555 for (IProject project : projects) {
556 RepositoryMapping repositoryMapping = RepositoryMapping
557 .getMapping(project);
558 if (repositoryMapping == null) {
559 return new Repository[0];
561 ret.add(repositoryMapping.getRepository());
563 return ret.toArray(new Repository[0]);