Java tutorial
/******************************************************************************* * Copyright (c) 2009 Casey Marshall 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: * Casey Marshall - initial API and implementation *******************************************************************************/ package ccw.launching; import static ccw.launching.LaunchUtils.findRunningLaunchesForProject; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Platform; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.debug.ui.IDebugModelPresentation; import org.eclipse.debug.ui.ILaunchShortcut; import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; import org.eclipse.jdt.internal.debug.ui.launcher.LauncherMessages; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IViewPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ElementListSelectionDialog; import ccw.CCWPlugin; import ccw.ClojureCore; import ccw.editors.clojure.ClojureEditor; import ccw.editors.clojure.LoadFileAction; import ccw.preferences.PreferenceConstants; import ccw.repl.REPLView; import ccw.util.ClojureInvoker; import ccw.util.DisplayUtil; import ccw.util.Pair; import clojure.lang.IFn; import clojure.lang.Keyword; public class ClojureLaunchShortcut implements ILaunchShortcut, IJavaLaunchConfigurationConstants { private static final Map<String, Long> tempLaunchCounters = new HashMap<String, Long>(); public interface IWithREPLView { void run(REPLView replView); } /** * Map of console/process names to Pair<promise,IWithREPLView> of their associated nREPL URL. * The runnable is code to run after the REPL Client is connected. * <p> * Insertion of promises is done at launch time. <br/> * Delivery of promises is done via Consoles PatternMatch listeners. <br/> * Removal of promises is done via disconnect of Consoles Pattern Match listeners * (approximation of process terminated signal). * </p> */ public static final ConcurrentMap<String, Pair<Object, IWithREPLView>> launchNameREPLURLPromiseAndWithREPLView = new ConcurrentHashMap<String, Pair<Object, IWithREPLView>>(); private ClojureInvoker leiningenConfiguration = ClojureInvoker.newInvoker(CCWPlugin.getDefault(), "ccw.leiningen.launch"); private ClojureInvoker launch = ClojureInvoker.newInvoker(CCWPlugin.getDefault(), "ccw.launch"); private static int incTempLaunchCount(String projectName) { synchronized (tempLaunchCounters) { Long cnt = tempLaunchCounters.get(projectName); cnt = cnt == null ? 1 : cnt + 1; tempLaunchCounters.put(projectName, cnt); return cnt.intValue(); } } @Override public void launch(final IEditorPart editor, final String mode) { launch(editor, mode, false); } public void launch(final IEditorPart editor, final String mode, final boolean forceLeinLaunchWhenPossible) { if (editor instanceof ClojureEditor) { // a new thread ensures we're not in the UI thread new Thread(new Runnable() { @Override public void run() { LoadFileAction.run((ClojureEditor) editor, mode, forceLeinLaunchWhenPossible); } }).start(); } } @Override public void launch(ISelection selection, final String mode) { launch(selection, mode, false); } public void launch(ISelection selection, final String mode, final boolean forceLeinLaunchWhenPossible) { if (selection instanceof IStructuredSelection) { IStructuredSelection strSel = (IStructuredSelection) selection; final List<IFile> files = new ArrayList<IFile>(); IProject proj = null; for (Object o : strSel.toList()) { IResource r = (IResource) Platform.getAdapterManager().getAdapter(o, IResource.class); if (r != null) { if (r.getType() == IResource.FILE) { files.add((IFile) r); } if (proj == null) { proj = r.getProject(); } } } if (proj != null) { final IProject theProj = proj; // a new thread ensures we're not in the UI thread new Thread(new Runnable() { @Override public void run() { launchProjectCheckRunning(theProj, files.toArray(new IFile[] {}), mode, forceLeinLaunchWhenPossible, null); } }).start(); } } } /** * @param mode if null, then global preferences run mode will be selected */ public void launchProject(final IProject project, final String runMode, final boolean forceLeinLaunchWhenPossible, final IWithREPLView runOnceREPLAvailable) { // a new thread ensures we're not in the UI thread new Thread(new Runnable() { @Override public void run() { launchProjectCheckRunning(project, new IFile[] {}, getRunMode(runMode), forceLeinLaunchWhenPossible, runOnceREPLAvailable); } }).start(); } private static String getDefaultRunMode() { boolean defaultIsDebugMode = getPreferences() .getBoolean(PreferenceConstants.CCW_GENERAL_LAUNCH_REPLS_IN_DEBUG_MODE); return defaultIsDebugMode ? ILaunchManager.DEBUG_MODE : ILaunchManager.RUN_MODE; } /** * Launches a project, first verifying if a running configuration for the * project is live. If so, first asks the user for confirmation that he * wants to start a new launch configuration for the project. * @param project * @param filesToLaunch * @param mode */ protected void launchProjectCheckRunning(IProject project, IFile[] filesToLaunch, String mode, boolean forceLeinLaunchWhenPossible, IWithREPLView runOnceREPLAvailable) { assert mode != null; String projectName = project.getName(); List<ILaunch> running = findRunningLaunchesForProject(projectName); System.out.println("found " + running.size() + " running launches"); if (running.size() == 0 || userConfirmsNewLaunch(project, running.size())) { launchProject(project, filesToLaunch, mode, forceLeinLaunchWhenPossible, runOnceREPLAvailable); } else { IViewPart replView = CCWPlugin.getDefault().getProjectREPL(project); if (replView != null) { replView.getViewSite().getPage().activate(replView); } else { System.out.println( "Should not be there: because in the normal course of things, a Launch does not survive its REPLView"); } } } private boolean userConfirmsNewLaunch(final IProject project, final int nb) { final boolean[] ret = new boolean[1]; final String title = "Clojure Application Launcher"; final String msg = (nb == 1 ? "A" : nb) + " REPL" + (nb == 1 ? " is" : "s are") + " already running for this project. Changes you made can " + "be evaluated in an existing REPL (see Clojure menu). " + "\n\nAre you sure you want to start up another REPL for this project?\n" + "(Cancel will open existing REPL)"; DisplayUtil.syncExec(new Runnable() { public void run() { Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); ret[0] = MessageDialog.openConfirm(shell, title, msg); } }); return ret[0]; } private boolean useLeiningenLaunchConfiguration(IProject project, boolean forceLeinLaunchWhenPossible) throws CoreException { return project.hasNature(CCWPlugin.LEININGEN_NATURE_ID) && (forceLeinLaunchWhenPossible || getPreferences().getBoolean(PreferenceConstants.CCW_GENERAL_USE_LEININGEN_LAUNCHER)); } protected void launchProject(IProject project, IFile[] filesToLaunch, String mode, boolean forceLeinLaunchWhenPossible, IWithREPLView runOnceREPLAvailable) { mode = getRunMode(mode); try { ILaunchConfiguration config; if (useLeiningenLaunchConfiguration(project, forceLeinLaunchWhenPossible)) { config = createLeiningenLaunchConfiguration(project, mode.equals(ILaunchManager.DEBUG_MODE)); } else { config = findLaunchConfiguration(project); if (config == null) { System.out.println("creating basic configuration (no lein configuration)"); config = createConfiguration(project, null); } } if (config != null) { final String name = config.getName() + " #" + incTempLaunchCount(project.getName()); launchNameREPLURLPromiseAndWithREPLView.put(name, new Pair<Object, IWithREPLView>(promise(), runOnceREPLAvailable)); ILaunchConfigurationWorkingCopy runnableConfiguration = config.copy(name); try { LaunchUtils.setFilesToLaunchString(runnableConfiguration, Arrays.asList(filesToLaunch)); if (filesToLaunch.length > 0) { runnableConfiguration.setAttribute(LaunchUtils.ATTR_NS_TO_START_IN, ClojureCore.findMaybeLibNamespace(filesToLaunch[0])); } runnableConfiguration.launch(mode, null); return; } finally { runnableConfiguration.delete(); } } } catch (CoreException e) { throw new RuntimeException(e); } } private Object promise() { IFn promise = clojure.java.api.Clojure.var("clojure.core", "promise"); return promise.invoke(); } private ILaunchConfiguration createLeiningenLaunchConfiguration(IProject project, boolean createInDebugMode) { String command = // Adding ccw/ccw.server for enabling ccw custom code completion, etc. " update-in :dependencies conj \"[ccw/ccw.server \\\"0.1.1\\\"]\" " + "-- update-in :injections conj \"(require 'ccw.debug.serverrepl)\" " // Starting repl :headless ; removing :main attribute because // it is causing problems: "leiningen repl :headless" defaults // to automatically requiring the namespace symbol found in the // [:main] project.clj key if there's no namespace symbol defined in the // the [:repl-options :init-ns] project.clj key. + "-- update-in : dissoc :main " // here ':' refers to project.clj's root + "-- repl :headless "; if (createInDebugMode) { command = " update-in :jvm-opts concat \"[\\\"-Xdebug\\\" \\\"-Xrunjdwp:transport=dt_socket,server=y,suspend=n\\\"]\" -- " + command; } clojure.lang.IPersistentMap configMap = (clojure.lang.IPersistentMap) leiningenConfiguration ._("lein-launch-configuration", project, command); configMap = configMap.assoc(Keyword.intern("type-id"), Keyword.intern("ccw")); configMap = configMap.assoc(Keyword.intern("name"), project.getName() + " Leiningen VM"); configMap = configMap.assoc(LaunchUtils.ATTR_CLOJURE_START_REPL, true); configMap = configMap.assoc(LaunchUtils.ATTR_LEININGEN_CONFIGURATION, true); configMap = configMap.assoc(Keyword.intern("private"), true); // Add LEIN_REPL_ACK_PORT as an additional environment variable final Map<String, String> additionalEnvVariables = new HashMap<String, String>(); additionalEnvVariables.put("LEIN_REPL_ACK_PORT", Integer.toString(CCWPlugin.getDefault().getREPLServerPort(), 10)); configMap = configMap.assoc(Keyword.intern("environment-variables"), additionalEnvVariables); configMap = configMap.assoc(Keyword.intern("append-environment-variables"), true); return (ILaunchConfiguration) launch._("launch-configuration", configMap); } private ILaunchConfiguration findLaunchConfiguration(IProject project) throws CoreException { ILaunchManager lm = DebugPlugin.getDefault().getLaunchManager(); ILaunchConfigurationType type = lm.getLaunchConfigurationType(LaunchUtils.LAUNCH_CONFIG_ID); List<ILaunchConfiguration> candidateConfigs = Collections.EMPTY_LIST; //boolean isLeinProject = project.hasNature(CCWPlugin.LEININGEN_NATURE_ID); try { ILaunchConfiguration[] configs = DebugPlugin.getDefault().getLaunchManager() .getLaunchConfigurations(type); candidateConfigs = new ArrayList<ILaunchConfiguration>(configs.length); for (ILaunchConfiguration config : configs) { if (config.getAttribute(ATTR_MAIN_TYPE_NAME, "").startsWith("clojure.") && config.getAttribute(ATTR_PROJECT_NAME, "").equals(project.getName()) && !config.getAttribute(ILaunchManager.ATTR_PRIVATE, false)) { if (true //(isLeinProject && ClojureLaunchDelegate.isLeiningenConfiguration(config)) //|| //(!isLeinProject && !ClojureLaunchDelegate.isLeiningenConfiguration(config)) ) { candidateConfigs.add(config); } } } } catch (CoreException e) { throw new RuntimeException(e); } int candidateCount = candidateConfigs.size(); if (candidateCount == 1) { return (ILaunchConfiguration) candidateConfigs.get(0); } else if (candidateCount > 1) { return chooseConfiguration(candidateConfigs); } return null; } protected ILaunchConfiguration createConfiguration(IProject project, IFile[] files) { if (files == null) files = new IFile[] {}; try { ILaunchManager lm = DebugPlugin.getDefault().getLaunchManager(); ILaunchConfigurationType type = lm.getLaunchConfigurationType(LaunchUtils.LAUNCH_CONFIG_ID); String basename = project.getName() + " REPL"; if (files.length == 1) { basename += " [" + files[0].getName() + "]"; } ILaunchConfigurationWorkingCopy wc = type.newInstance(null, DebugPlugin.getDefault().getLaunchManager().generateLaunchConfigurationName(basename)); LaunchUtils.setFilesToLaunchString(wc, Arrays.asList(files)); wc.setAttribute(ATTR_PROGRAM_ARGUMENTS, ""); wc.setAttribute(ATTR_MAIN_TYPE_NAME, LaunchUtils.CLOJURE_MAIN); wc.setAttribute(ATTR_PROJECT_NAME, project.getName()); wc.setMappedResources(new IResource[] { project }); return wc.doSave(); } catch (CoreException ce) { throw new RuntimeException(ce); } } protected ILaunchConfiguration chooseConfiguration(final List<ILaunchConfiguration> configList) { final AtomicReference<ILaunchConfiguration> ret = new AtomicReference<ILaunchConfiguration>(); DisplayUtil.syncExec(new Runnable() { @Override public void run() { IDebugModelPresentation labelProvider = null; try { labelProvider = DebugUITools.newDebugModelPresentation(); ElementListSelectionDialog dialog = new ElementListSelectionDialog( JDIDebugUIPlugin.getActiveWorkbenchShell(), labelProvider); dialog.setElements(configList.toArray()); dialog.setTitle("Choose a Clojure launch configuration"); dialog.setMessage(LauncherMessages.JavaLaunchShortcut_2); dialog.setMultipleSelection(false); dialog.setAllowDuplicates(true); int result = dialog.open(); if (result == Window.OK) { ret.set((ILaunchConfiguration) dialog.getFirstResult()); } } finally { if (labelProvider != null) { labelProvider.dispose(); } } } }); return ret.get(); } /** * @return the run mode, getting the default run mode if <code>mode</code> * is null. */ private String getRunMode(String mode) { return (mode != null) ? mode : getDefaultRunMode(); } private static IPreferenceStore getPreferences() { return CCWPlugin.getDefault().getCombinedPreferenceStore(); } }