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