ccw.launching.ClojureLaunchShortcut.java Source code

Java tutorial

Introduction

Here is the source code for ccw.launching.ClojureLaunchShortcut.java

Source

/*******************************************************************************
 * 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();
    }
}