/**********************************************************************
* Copyright (c) 2005-2009 ant4eclipse project team.
*
* 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:
* Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich
**********************************************************************/
package org.ant4eclipse.ant.jdt.ecj;
import org.ant4eclipse.lib.core.CoreExceptionCode;
import org.ant4eclipse.lib.core.exception.Ant4EclipseException;
import org.ant4eclipse.lib.core.logging.A4ELogging;
import org.ant4eclipse.lib.core.util.Utilities;
import org.ant4eclipse.lib.jdt.ecj.CompileJobDescription;
import org.ant4eclipse.lib.jdt.ecj.CompileJobResult;
import org.ant4eclipse.lib.jdt.ecj.SourceFile;
import org.ant4eclipse.lib.jdt.ecj.internal.tools.CompileJobResultImpl;
import org.apache.tools.ant.types.Path;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* <p>
* Implements a javac compiler adapter for sun's javac commandline. This adapter is capable to understand eclipse
* project settings.
* </p>
*
* @author Daniel Kasmeroglu (Daniel.Kasmeroglu@Kasisoft.net)
*/
public class JavacCompilerAdapter extends A4ECompilerAdapter {
private static final String SUFFIX_JAVA = ".java";
private static final String PATH_SEPARATOR = System.getProperty("path.separator");
private Object _javac;
private Method _compile;
private StringBuffer _buffer;
private IProblemFactory _problemfactory;
private PrintStream _stdout;
private PrintStream _stderr;
private ByteArrayOutputStream _byteout;
/**
* Initialises this compiler adapter instance.
*/
public JavacCompilerAdapter() {
this._buffer = new StringBuffer();
this._byteout = new ByteArrayOutputStream();
// use reflection to access the compiler
try {
this._javac = Utilities.newInstance("com.sun.tools.javac.Main");
this._compile = this._javac.getClass().getMethod("compile", new Class[] { String[].class });
} catch (SecurityException ex) {
failedCompile(ex);
} catch (NoSuchMethodException ex) {
failedCompile(ex);
}
// create the problem factory
this._problemfactory = new DefaultProblemFactory(Locale.getDefault());
}
/**
* Provides a classpath from the supplied compile job description.
*
* @param description
* The description providing all necessary information. Not <code>null</code>.
*
* @return The classpath which has been setup for the compile job. Not <code>null</code>.
*/
private String getClasspath(CompileJobDescription description) {
this._buffer.setLength(0);
File[] classpath = description.getClassFileLoader().getClasspath();
if (classpath.length > 0) {
this._buffer.append(classpath[0].getAbsolutePath());
for (int i = 1; i < classpath.length; i++) {
this._buffer.append(PATH_SEPARATOR);
this._buffer.append(classpath[i].getAbsolutePath());
}
}
return this._buffer.toString();
}
/**
* Concatenates the supplied list of path entries.
*
* @param list
* The list of path entries. Not <code>null</code>.
*
* @return The concatenated path list. Not <code>null</code>.
*/
private String getConcatenatedPath(String[] list) {
this._buffer.setLength(0);
if (list.length > 0) {
this._buffer.append(list[0]);
for (int i = 1; i < list.length; i++) {
this._buffer.append(PATH_SEPARATOR);
this._buffer.append(list[i]);
}
}
return this._buffer.toString();
}
/**
* Calculates the debugging options according to the supplied description.
*
* @param description
* The descriptional instance providing all information to run a compile job. Not <code>null</code>.
*
* @return The compile options used for the generation of byte code. Not <code>null</code>.
*/
private String getDebugOptions(CompileJobDescription description) {
this._buffer.setLength(0);
if (description.getCompilerOptions().containsKey(CompilerOptions.OPTION_LineNumberAttribute)) {
this._buffer.append("lines");
}
if (description.getCompilerOptions().containsKey(CompilerOptions.OPTION_LocalVariableAttribute)) {
if (this._buffer.length() > 0) {
this._buffer.append(",");
}
this._buffer.append("vars");
}
if (description.getCompilerOptions().containsKey(CompilerOptions.OPTION_SourceFileAttribute)) {
if (this._buffer.length() > 0) {
this._buffer.append(",");
}
this._buffer.append("source");
}
if (this._buffer.length() == 0) {
// nothing has been specified
this._buffer.append("none");
}
return this._buffer.toString();
}
/**
* Evaluates some arguments to setup output options for the compilation process.
*
* @param description
* The descriptional instance providing all information to run a compile job. Not <code>null</code>.
*
* @return The options used to control the compilation process. Not <code>null</code>.
*/
private String getCompileOptions(CompileJobDescription description) {
this._buffer.setLength(0);
if (description.getCompilerOptions().containsKey(CompilerOptions.OPTION_ReportMissingSerialVersion)) {
this._buffer.append("serial");
} else {
this._buffer.append("-serial");
}
this._buffer.append(",");
if (description.getCompilerOptions().containsKey(CompilerOptions.OPTION_ReportFallthroughCase)) {
this._buffer.append("fallthrough");
} else {
this._buffer.append("-fallthrough");
}
this._buffer.append(",");
if (description.getCompilerOptions().containsKey(CompilerOptions.OPTION_ReportUncheckedTypeOperation)) {
this._buffer.append("unchecked");
} else {
this._buffer.append("-unchecked");
}
this._buffer.append(",");
if (description.getCompilerOptions().containsKey(CompilerOptions.OPTION_ReportFinallyBlockNotCompletingNormally)) {
this._buffer.append("finally");
} else {
this._buffer.append("-finally");
}
if (this._buffer.length() == 0) {
// the recommended options
return "-Xlint";
} else {
// the specified options
return String.format("-Xlint:%s", this._buffer);
}
}
/**
* Creates a list with commandline arguments shared among all source files.
*
* @param description
* The description used for the compilation process. Not <code>null</code>.
*
* @return The list with commandline arguments. Not <code>null</code>.
*/
private List<String> createCommonArgs(CompileJobDescription description) {
Map<String, String> options = description.getCompilerOptions();
List<String> result = new ArrayList<String>();
result.add(getCompileOptions(description));
Path bootclasspath = getJavac().getBootclasspath();
if (bootclasspath != null) {
result.add("-bootclasspath");
result.add(getConcatenatedPath(bootclasspath.list()));
}
Path extdirs = getJavac().getExtdirs();
if (extdirs != null) {
result.add("-extdirs");
result.add(getConcatenatedPath(extdirs.list()));
}
result.add("-classpath");
result.add(getClasspath(description));
result.add(String.format("-g:%s", getDebugOptions(description)));
if (A4ELogging.isDebuggingEnabled()) {
result.add("-verbose");
}
if (options.containsKey(CompilerOptions.OPTION_Source)) {
result.add("-source");
result.add(options.get(CompilerOptions.OPTION_Source));
}
if (options.containsKey(CompilerOptions.OPTION_Compliance)) {
result.add("-target");
result.add(options.get(CompilerOptions.OPTION_Compliance));
}
if (options.containsKey(CompilerOptions.OPTION_Encoding)) {
result.add("-encoding");
result.add(options.get(CompilerOptions.OPTION_Encoding));
}
if (options.containsKey(CompilerOptions.OPTION_ReportDeprecation)
|| options.containsKey(CompilerOptions.OPTION_ReportDeprecationInDeprecatedCode)
|| options.containsKey(CompilerOptions.OPTION_ReportDeprecationWhenOverridingDeprecatedMethod)) {
result.add("-deprecation");
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
protected CompileJobResult compile(CompileJobDescription description) {
try {
List<CategorizedProblem> problems = new ArrayList<CategorizedProblem>();
List<String> preparedargs = createCommonArgs(description);
List<String> arguments = new ArrayList<String>();
boolean succeeded = true;
for (SourceFile sourcefile : description.getSourceFiles()) {
// setup the commandline arguments for the javac executable
arguments.clear();
arguments.addAll(preparedargs);
arguments.add("-d");
arguments.add(sourcefile.getDestinationFolder().getAbsolutePath());
arguments.add(sourcefile.getSourceFile().getAbsolutePath());
boolean singlesuccess = true;
String[] arglist = arguments.toArray(new String[arguments.size()]);
startCapturing();
try {
Integer returncode = (Integer) this._compile.invoke(this._javac, new Object[] { arglist });
if (returncode.intValue() != 0) {
singlesuccess = false;
}
} finally {
stopCapturing();
}
if (!singlesuccess) {
succeeded = false;
// we're using the platform encoding as this is the encoding used for the out/err stream
// from the console
createProblems(problems, sourcefile.getSourceFileName(), new String(this._byteout.toByteArray()));
}
}
CompileJobResultImpl result = new CompileJobResultImpl();
result.setSucceeded(succeeded);
result.setCategorizedProblems(problems.toArray(new CategorizedProblem[problems.size()]));
return result;
} catch (IllegalArgumentException ex) {
throw failedCompile(ex);
} catch (IllegalAccessException ex) {
throw failedCompile(ex);
} catch (InvocationTargetException ex) {
throw failedCompile(ex);
}
}
/**
* Creates the problem instances which can be used to report some issues.
*
* @param problems
* The receiving list for the problems. Not <code>null</code>.
* @param sourcefilename
* The filename these problems are related to. Not <code>null</code>.
* @param text
* The content of the javac outcome. Not <code>null</code>.
*/
private void createProblems(List<CategorizedProblem> problems, String sourcefilename, String text) {
List<String> lines = Utilities.splitText(text);
Collections.reverse(lines);
for (int i = 0; i < lines.size() - 2; i++) {
int col = lines.get(i).indexOf('^');
if (col != -1) {
// we've got a marker for the issue which usually looks like in the following example
//
// mysource.java:12 : there's an error
// public class mysource- {
// ^
//
String line = lines.get(i + 2);
int suffix = line.indexOf(SUFFIX_JAVA);
if (suffix != -1) {
line = line.substring(suffix + SUFFIX_JAVA.length() + 1); // +1 to skip a ':' before the line number
int colon = line.indexOf(':');
if (colon != -1) {
int lineno = Integer.parseInt(line.substring(0, colon));
String error = line.substring(colon + 1).trim();
problems.add(this._problemfactory.createProblem(sourcefilename.toCharArray(), IProblem.Unclassified,
new String[] { error }, new String[] { error }, ProblemSeverities.Error, -1, -1, lineno, problems
.size()));
}
}
}
}
}
/**
* Enables the capturing so we can analyze the outcome of the java compiler.
*/
private void startCapturing() {
this._stdout = System.out;
this._stderr = System.err;
this._byteout.reset();
System.setErr(new PrintStream(this._byteout));
System.setOut(new PrintStream(this._byteout));
}
/**
* Disables the capturing so the collected outcome can be processed.
*/
private void stopCapturing() {
System.setOut(this._stdout);
System.setErr(this._stderr);
}
private Ant4EclipseException failedCompile(Exception ex) {
return new Ant4EclipseException(CoreExceptionCode.COULD_NOT_ACCESS_METHOD, "compile", "com.sun.tools.javac.Main");
}
} /* ENDCALSS */