/*******************************************************************************
* Copyright (c) 2013, 2016 Robin Stocker 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:
* Thomas Wolf <thomas.wolf@paranor.ch>
*******************************************************************************/
package org.eclipse.egit.ui.internal.branch;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
import org.eclipse.debug.core.sourcelookup.containers.ProjectSourceContainer;
import org.eclipse.egit.core.internal.util.ProjectUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.ui.PlatformUI;
/**
* Utility class for finding launch configurations.
*/
public final class LaunchFinder {
private LaunchFinder() {
// Utility class shall not be instantiated
}
/**
* If there is a running launch covering at least one project from the given
* repositories, return the first such launch configuration.
*
* @param repositories
* to determine projects to be checked whether they are used in
* running launches
* @param monitor
* for progress reporting and cancellation
* @return the {@link ILaunchConfiguration}, or {@code null} if none found.
*/
@Nullable
public static ILaunchConfiguration getRunningLaunchConfiguration(
final Collection<Repository> repositories,
IProgressMonitor monitor) {
SubMonitor progress = SubMonitor.convert(monitor, 1);
final ILaunchConfiguration[] result = { null };
IRunnableWithProgress operation = new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor m)
throws InvocationTargetException, InterruptedException {
Set<IProject> projects = new HashSet<>();
for (Repository repository : repositories) {
projects.addAll(
Arrays.asList(ProjectUtil.getProjects(repository)));
}
result[0] = findLaunch(projects, m);
}
};
try {
if (ModalContext.isModalContextThread(Thread.currentThread())) {
operation.run(progress);
} else {
ModalContext.run(operation, true, progress,
PlatformUI.getWorkbench().getDisplay());
}
} catch (InvocationTargetException e) {
// ignore
} catch (InterruptedException e) {
// ignore
}
return result[0];
}
private static ILaunchConfiguration findLaunch(Set<IProject> projects,
IProgressMonitor monitor) {
ILaunchManager launchManager = DebugPlugin.getDefault()
.getLaunchManager();
ILaunch[] launches = launchManager.getLaunches();
SubMonitor progress = SubMonitor.convert(monitor,
UIText.LaunchFinder_SearchLaunchConfiguration,
launches.length);
for (ILaunch launch : launches) {
if (progress.isCanceled()) {
break;
}
if (launch.isTerminated()) {
progress.worked(1);
continue;
}
ISourceLocator locator = launch.getSourceLocator();
if (locator instanceof ISourceLookupDirector) {
ISourceLookupDirector director = (ISourceLookupDirector) locator;
ISourceContainer[] containers = director.getSourceContainers();
if (isAnyProjectInSourceContainers(containers, projects,
progress.newChild(1))) {
return launch.getLaunchConfiguration();
}
} else {
progress.worked(1);
}
}
return null;
}
private static boolean isAnyProjectInSourceContainers(
ISourceContainer[] containers, Set<IProject> projects,
IProgressMonitor monitor) {
SubMonitor progress = SubMonitor.convert(monitor, containers.length);
for (ISourceContainer container : containers) {
if (progress.isCanceled()) {
break;
}
if (container instanceof ProjectSourceContainer) {
ProjectSourceContainer projectContainer = (ProjectSourceContainer) container;
if (projects.contains(projectContainer.getProject())) {
progress.worked(1);
return true;
}
}
try {
boolean found = isAnyProjectInSourceContainers(
container.getSourceContainers(), projects,
progress.newChild(1));
if (found) {
return true;
}
} catch (CoreException e) {
// Ignore the child source containers, continue search
}
}
return false;
}
/**
* Checks whether there are any running launches based on projects belonging
* to the given repository. If so, asks the user whether to cancel, and
* returns the user's choice. The user has the possibility to suppress the
* dialog, in which case this method returns {@code false} without checking
* for running launches.
*
* @param repository
* to determine projects to be checked whether they are used in
* running launches
* @param monitor
* for progress reporting and cancellation
* @return {@code true} if the operation should be canceled, {@code false}
* otherwise
*/
public static boolean shouldCancelBecauseOfRunningLaunches(
Repository repository, IProgressMonitor monitor) {
return shouldCancelBecauseOfRunningLaunches(
Collections.singleton(repository), monitor);
}
/**
* Checks whether there are any running launches based on projects belonging
* to the given repositories. If so, asks the user whether to cancel, and
* returns the user's choice. The user has the possibility to suppress the
* dialog, in which case this method returns {@code false} without checking
* for running launches.
*
* @param repositories
* to determine projects to be checked whether they are used in
* running launches
* @param monitor
* for progress reporting and cancellation
* @return {@code true} if the operation should be canceled, {@code false}
* otherwise
*/
public static boolean shouldCancelBecauseOfRunningLaunches(
Collection<Repository> repositories, IProgressMonitor monitor) {
final IPreferenceStore store = Activator.getDefault()
.getPreferenceStore();
if (!store.getBoolean(
UIPreferences.SHOW_RUNNING_LAUNCH_ON_CHECKOUT_WARNING)) {
return false;
}
SubMonitor progress = SubMonitor.convert(monitor);
final ILaunchConfiguration launchConfiguration = getRunningLaunchConfiguration(
repositories,
progress);
if (launchConfiguration != null) {
final boolean[] dialogResult = new boolean[1];
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
dialogResult[0] = showContinueDialogInUI(store,
launchConfiguration);
}
});
return dialogResult[0];
}
return false;
}
private static boolean showContinueDialogInUI(final IPreferenceStore store,
final ILaunchConfiguration launchConfiguration) {
String[] buttons = new String[] { UIText.BranchOperationUI_Continue,
IDialogConstants.CANCEL_LABEL };
String message = NLS.bind(UIText.LaunchFinder_RunningLaunchMessage,
launchConfiguration.getName()) + ' '
+ UIText.LaunchFinder_ContinueQuestion;
MessageDialogWithToggle continueDialog = new MessageDialogWithToggle(
PlatformUI.getWorkbench().getModalDialogShellProvider()
.getShell(),
UIText.LaunchFinder_RunningLaunchTitle, null,
message, MessageDialog.NONE, buttons, 0,
UIText.LaunchFinder_RunningLaunchDontShowAgain, false);
int result = continueDialog.open();
// cancel
if (result == IDialogConstants.CANCEL_ID || result == SWT.DEFAULT)
return true;
boolean dontWarnAgain = continueDialog.getToggleState();
if (dontWarnAgain)
store.setValue(
UIPreferences.SHOW_RUNNING_LAUNCH_ON_CHECKOUT_WARNING,
false);
return false;
}
}