org.eclim.plugin.jdt.command.launching.JavaCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.eclim.plugin.jdt.command.launching.JavaCommand.java

Source

/**
 * Copyright (C) 2005 - 2014  Eric Van Dewoestine
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.eclim.plugin.jdt.command.launching;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.lang.reflect.Method;

import java.util.ArrayList;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildLogger;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;

import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.taskdefs.PumpStreamHandler;
import org.apache.tools.ant.taskdefs.Redirector;
import org.apache.tools.ant.taskdefs.StreamPumper;

import org.apache.tools.ant.taskdefs.condition.Os;

import org.apache.tools.ant.types.Commandline.Argument;
import org.apache.tools.ant.types.Environment.Variable;
import org.apache.tools.ant.types.Path;

import org.eclim.Services;

import org.eclim.annotation.Command;

import org.eclim.command.CommandLine;
import org.eclim.command.Options;

import org.eclim.plugin.core.command.AbstractCommand;

import org.eclim.plugin.core.util.ProjectUtils;

import org.eclim.plugin.jdt.util.ClasspathUtils;
import org.eclim.plugin.jdt.util.JavaUtils;

import org.eclim.util.StringUtils;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IncrementalProjectBuilder;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageDeclaration;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;

import org.eclipse.jdt.core.compiler.IProblem;

import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;

import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.JavaRuntime;

/**
 * Command to run the project's main class.
 *
 * @author Eric Van Dewoestine
 */
@Command(name = "java", options = "REQUIRED p project ARG," + "OPTIONAL d debug NOARG,"
        + "OPTIONAL c classname ARG," + "OPTIONAL w workingdir ARG," + "OPTIONAL v vmargs ANY,"
        + "OPTIONAL s sysprops ANY," + "OPTIONAL e envargs ANY," + "OPTIONAL a args ANY")
public class JavaCommand extends AbstractCommand {
    private static final String WORKINGDIR_OPTION = "w";
    private static final String VMARGS_OPTION = "v";
    private static final String SYSPROPS_OPTION = "s";
    private static final String ENVARGS_OPTION = "e";

    @Override
    public Object execute(CommandLine commandLine) throws Exception {
        String projectName = commandLine.getValue(Options.PROJECT_OPTION);
        String mainClass = commandLine.getValue(Options.CLASSNAME_OPTION);
        boolean debug = commandLine.hasOption(Options.DEBUG_OPTION);
        String workingDir = commandLine.getValue(WORKINGDIR_OPTION);

        IProject project = ProjectUtils.getProject(projectName);
        project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, null);

        IJavaProject javaProject = JavaUtils.getJavaProject(project);

        Project antProject = new Project();
        BuildLogger buildLogger = new DefaultLogger();
        buildLogger.setMessageOutputLevel(debug ? Project.MSG_DEBUG : Project.MSG_INFO);
        buildLogger.setOutputPrintStream(getContext().out);
        buildLogger.setErrorPrintStream(getContext().err);
        antProject.addBuildListener(buildLogger);
        antProject.setBasedir(ProjectUtils.getPath(project));
        antProject.setDefaultInputStream(System.in);

        if (mainClass == null) {
            mainClass = getPreferences().getValue(project, "org.eclim.java.run.mainclass");
        }

        if (mainClass == null || mainClass.trim().equals(StringUtils.EMPTY) || mainClass.trim().equals("none")) {
            // first try to locate a main method.
            mainClass = findMainClass(javaProject);
            if (mainClass == null) {
                throw new RuntimeException(Services.getMessage("setting.not.set", "org.eclim.java.run.mainclass"));
            }
        }

        if (mainClass.endsWith(".java") || mainClass.endsWith(".class")) {
            mainClass = mainClass.substring(0, mainClass.lastIndexOf('.'));
        }

        // validate that the main class doesn't contain errors
        IType type = javaProject.findType(mainClass);
        if (type != null) {
            ICompilationUnit src = type.getCompilationUnit();
            if (src != null) {
                IProblem[] problems = JavaUtils.getProblems(src);
                for (IProblem problem : problems) {
                    if (problem.isError()) {
                        println(Services.getMessage("src.contains.errors"));
                        return null;
                    }
                }
            }
        }

        Java java = new MyJava();
        java.setTaskName("java");
        java.setProject(antProject);
        java.setClassname(mainClass);
        java.setFork(true);

        // use the project configured jvm if possible
        IVMInstall jvm = JavaRuntime.getVMInstall(javaProject);
        if (jvm != null) {
            String path = jvm.getInstallLocation() + "/bin/java";
            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                path += ".exe";
            }
            if (new File(path).exists()) {
                java.setJvm(path);
            }
        }

        if (workingDir != null) {
            java.setDir(new File(workingDir));
        }

        // construct classpath
        Path classpath = new Path(antProject);
        String[] paths = ClasspathUtils.getClasspath(javaProject);
        for (String path : paths) {
            Path.PathElement pe = classpath.createPathElement();
            pe.setPath(path);
        }

        java.setClasspath(classpath);

        // add default vm args
        String[] defaultArgs = getPreferences().getArrayValue(project, "org.eclim.java.run.jvmargs");
        for (String vmarg : defaultArgs) {
            if (!vmarg.startsWith("-")) {
                continue;
            }
            Argument a = java.createJvmarg();
            a.setValue(vmarg);
        }

        // add any supplied vm args
        String[] vmargs = commandLine.getValues(VMARGS_OPTION);
        if (vmargs != null && vmargs.length > 0) {
            for (String vmarg : vmargs) {
                if (!vmarg.startsWith("-")) {
                    continue;
                }
                Argument a = java.createJvmarg();
                a.setValue(vmarg);
            }
        }

        // add any supplied system properties
        String[] props = commandLine.getValues(SYSPROPS_OPTION);
        if (props != null && props.length > 0) {
            for (String prop : props) {
                String[] sysprop = StringUtils.split(prop, "=", 2);
                if (sysprop.length != 2) {
                    continue;
                }
                if (sysprop[0].startsWith("-D")) {
                    sysprop[0] = sysprop[0].substring(2);
                }
                Variable var = new Variable();
                var.setKey(sysprop[0]);
                var.setValue(sysprop[1]);
                java.addSysproperty(var);
            }
        }

        // add any env vars
        String[] envs = commandLine.getValues(ENVARGS_OPTION);
        if (envs != null && envs.length > 0) {
            for (String env : envs) {
                String[] envvar = StringUtils.split(env, "=", 2);
                if (envvar.length != 2) {
                    continue;
                }
                Variable var = new Variable();
                var.setKey(envvar[0]);
                var.setValue(envvar[1]);
                java.addEnv(var);
            }
        }

        // add any supplied command line args
        String[] args = commandLine.getValues(Options.ARGS_OPTION);
        if (args != null && args.length > 0) {
            for (String arg : args) {
                Argument a = java.createArg();
                a.setValue(arg);
            }
        }

        java.execute();

        return null;
    }

    private String findMainClass(IJavaProject javaProject) throws Exception {
        ArrayList<IJavaElement> srcs = new ArrayList<IJavaElement>();
        for (IClasspathEntry entry : javaProject.getResolvedClasspath(true)) {
            if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                for (IPackageFragmentRoot root : javaProject.findPackageFragmentRoots(entry)) {
                    srcs.add(root);
                }
            }
        }

        final ArrayList<IMethod> methods = new ArrayList<IMethod>();
        int context = IJavaSearchConstants.DECLARATIONS;
        int type = IJavaSearchConstants.METHOD;
        int matchType = SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE;
        IJavaSearchScope scope = SearchEngine.createJavaSearchScope(srcs.toArray(new IJavaElement[srcs.size()]));
        SearchPattern pattern = SearchPattern.createPattern("main(String[])", type, context, matchType);
        SearchRequestor requestor = new SearchRequestor() {
            public void acceptSearchMatch(SearchMatch match) {
                if (match.getAccuracy() != SearchMatch.A_ACCURATE) {
                    return;
                }

                try {
                    IMethod method = (IMethod) match.getElement();
                    String[] params = method.getParameterTypes();
                    if (params.length != 1) {
                        return;
                    }

                    if (!Signature.SIG_VOID.equals(method.getReturnType())) {
                        return;
                    }

                    int flags = method.getFlags();
                    if (!Flags.isPublic(flags) || !Flags.isStatic(flags)) {
                        return;
                    }

                    methods.add(method);
                } catch (JavaModelException e) {
                    // ignore
                }
            }
        };

        SearchEngine engine = new SearchEngine();
        SearchParticipant[] participants = new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
        engine.search(pattern, participants, scope, requestor, null);

        // if we found only 1 result, we can use it.
        if (methods.size() == 1) {
            IMethod method = methods.get(0);
            ICompilationUnit cu = method.getCompilationUnit();
            IPackageDeclaration[] packages = cu.getPackageDeclarations();
            if (packages != null && packages.length > 0) {
                return packages[0].getElementName() + "." + cu.getElementName();
            }
            return cu.getElementName();
        }
        return null;
    }

    /* All of this is to ensure that System.out calls by the running class are
     * flushed immediatly to the console for things like user input prompts. */

    private class MyJava extends Java {
        public MyJava() {
            super();
            this.redirector = new MyRedirector(this);
        }
    }

    private class MyRedirector extends Redirector {
        public MyRedirector(Task task) {
            super(task);
        }

        @Override
        public synchronized ExecuteStreamHandler createHandler() throws BuildException {
            return new MyPumpStreamHandler();
        }

        @Override
        public synchronized void complete() throws IOException {
            getContext().out.flush();
            getContext().err.flush();
        }
    }

    private class MyPumpStreamHandler extends PumpStreamHandler {
        public MyPumpStreamHandler() {
            super(new FlushingOutputStream(getContext().out), getContext().err, getContext().in, true);
        }

        protected Thread createPump(InputStream is, OutputStream os, boolean closeWhenExhausted,
                boolean nonBlockingIO) {
            Thread pump = super.createPump(is, os, closeWhenExhausted, nonBlockingIO);
            try {
                Method getPumper = pump.getClass().getDeclaredMethod("getPumper");
                getPumper.setAccessible(true);
                StreamPumper pumper = (StreamPumper) getPumper.invoke(pump);
                Method setAutoflush = pumper.getClass().getDeclaredMethod("setAutoflush", Boolean.TYPE);
                setAutoflush.setAccessible(true);
                setAutoflush.invoke(pumper, Boolean.TRUE);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return pump;
        }
    }

    private class FlushingOutputStream extends OutputStream {
        private OutputStream out;

        public FlushingOutputStream(OutputStream out) {
            this.out = out;
        }

        @Override
        public void write(int b) throws IOException {
            out.write(b);
            out.flush();
        }

        @Override
        public void write(byte[] b) throws IOException {
            out.write(b);
            out.flush();
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            out.write(b, off, len);
            out.flush();
        }
    }
}