
org.codehaus.plexus.compiler.javac.JavacCompiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plexus-compiler-javac Show documentation
Show all versions of plexus-compiler-javac Show documentation
Javac Compiler support for Plexus Compiler component.
package org.codehaus.plexus.compiler.javac;
/**
* The MIT License
*
* Copyright (c) 2005, The Codehaus
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.plexus.compiler.AbstractCompiler;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerException;
import org.codehaus.plexus.compiler.CompilerMessage;
import org.codehaus.plexus.compiler.CompilerOutputStyle;
import org.codehaus.plexus.compiler.CompilerResult;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
/**
* @author Trygve Laugstøl
* @author Matthew Pocock
* @author Jörg Waßmer
* @author Others
*
*/
@Named("javac")
@Singleton
public class JavacCompiler extends AbstractCompiler {
// see compiler.warn.warning in compiler.properties of javac sources
private static final String[] WARNING_PREFIXES = {"warning: ", "\u8b66\u544a: ", "\u8b66\u544a\uff1a "};
// see compiler.note.note in compiler.properties of javac sources
private static final String[] NOTE_PREFIXES = {"Note: ", "\u6ce8: ", "\u6ce8\u610f\uff1a "};
// see compiler.misc.verbose in compiler.properties of javac sources
private static final String[] MISC_PREFIXES = {"["};
private static final Object LOCK = new Object();
private static final String JAVAC_CLASSNAME = "com.sun.tools.javac.Main";
private volatile Class> javacClass;
private final Deque> javacClasses = new ConcurrentLinkedDeque<>();
private static final Pattern JAVA_MAJOR_AND_MINOR_VERSION_PATTERN = Pattern.compile("\\d+(\\.\\d+)?");
/** Cache of javac version per executable (never invalidated) */
private static final Map VERSION_PER_EXECUTABLE = new ConcurrentHashMap<>();
@Inject
private InProcessCompiler inProcessCompiler;
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
public JavacCompiler() {
super(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null);
}
// ----------------------------------------------------------------------
// Compiler Implementation
// ----------------------------------------------------------------------
@Override
public String getCompilerId() {
return "javac";
}
private String getInProcessJavacVersion() throws CompilerException {
return System.getProperty("java.version");
}
private String getOutOfProcessJavacVersion(String executable) throws CompilerException {
String version = VERSION_PER_EXECUTABLE.get(executable);
if (version == null) {
Commandline cli = new Commandline();
cli.setExecutable(executable);
/*
* The option "-version" should be supported by javac since 1.6 (https://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html)
* up to 21 (https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html#standard-options)
*/
cli.addArguments(new String[] {"-version"}); //
CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
try {
int exitCode = CommandLineUtils.executeCommandLine(cli, out, out);
if (exitCode != 0) {
throw new CompilerException("Could not retrieve version from " + executable + ". Exit code "
+ exitCode + ", Output: " + out.getOutput());
}
} catch (CommandLineException e) {
throw new CompilerException("Error while executing the external compiler " + executable, e);
}
version = extractMajorAndMinorVersion(out.getOutput());
VERSION_PER_EXECUTABLE.put(executable, version);
}
return version;
}
static String extractMajorAndMinorVersion(String text) {
Matcher matcher = JAVA_MAJOR_AND_MINOR_VERSION_PATTERN.matcher(text);
if (!matcher.find()) {
throw new IllegalArgumentException("Could not extract version from \"" + text + "\"");
}
return matcher.group();
}
@Override
public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
File destinationDir = new File(config.getOutputLocation());
if (!destinationDir.exists()) {
destinationDir.mkdirs();
}
String[] sourceFiles = getSourceFiles(config);
if ((sourceFiles == null) || (sourceFiles.length == 0)) {
return new CompilerResult();
}
logCompiling(sourceFiles, config);
final String javacVersion;
final String executable;
if (config.isFork()) {
executable = getJavacExecutable(config);
javacVersion = getOutOfProcessJavacVersion(executable);
} else {
javacVersion = getInProcessJavacVersion();
executable = null;
}
String[] args = buildCompilerArguments(config, sourceFiles, javacVersion);
CompilerResult result;
if (config.isFork()) {
result = compileOutOfProcess(config, executable, args);
} else {
if (hasJavaxToolProvider() && !config.isForceJavacCompilerUse()) {
// use fqcn to prevent loading of the class on 1.5 environment !
result = inProcessCompiler().compileInProcess(args, config, sourceFiles);
} else {
result = compileInProcess(args, config);
}
}
return result;
}
protected InProcessCompiler inProcessCompiler() {
return inProcessCompiler;
}
/**
*
* @return {@code true} if the current context class loader has access to {@code javax.tools.ToolProvider}
*/
protected static boolean hasJavaxToolProvider() {
try {
Thread.currentThread().getContextClassLoader().loadClass("javax.tools.ToolProvider");
return true;
} catch (Exception e) {
return false;
}
}
public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
final String javacVersion;
if (config.isFork()) {
String executable = getJavacExecutable(config);
javacVersion = getOutOfProcessJavacVersion(executable);
} else {
javacVersion = getInProcessJavacVersion();
}
return buildCompilerArguments(config, getSourceFiles(config), javacVersion);
}
public static String[] buildCompilerArguments(
CompilerConfiguration config, String[] sourceFiles, String javacVersion) {
List args = new ArrayList<>();
// ----------------------------------------------------------------------
// Set output
// ----------------------------------------------------------------------
File destinationDir = new File(config.getOutputLocation());
args.add("-d");
args.add(destinationDir.getAbsolutePath());
// ----------------------------------------------------------------------
// Set the class and source paths
// ----------------------------------------------------------------------
List classpathEntries = config.getClasspathEntries();
if (classpathEntries != null && !classpathEntries.isEmpty()) {
args.add("-classpath");
args.add(getPathString(classpathEntries));
}
List modulepathEntries = config.getModulepathEntries();
if (modulepathEntries != null && !modulepathEntries.isEmpty()) {
args.add("--module-path");
args.add(getPathString(modulepathEntries));
}
List sourceLocations = config.getSourceLocations();
if (sourceLocations != null && !sourceLocations.isEmpty()) {
// always pass source path, even if sourceFiles are declared,
// needed for jsr269 annotation processing, see MCOMPILER-98
args.add("-sourcepath");
args.add(getPathString(sourceLocations));
}
if (!hasJavaxToolProvider() || config.isForceJavacCompilerUse() || config.isFork()) {
args.addAll(Arrays.asList(sourceFiles));
}
if (JavaVersion.JAVA_1_6.isOlderOrEqualTo(javacVersion)) {
// now add jdk 1.6 annotation processing related parameters
if (config.getGeneratedSourcesDirectory() != null) {
config.getGeneratedSourcesDirectory().mkdirs();
args.add("-s");
args.add(config.getGeneratedSourcesDirectory().getAbsolutePath());
}
if (config.getProc() != null) {
args.add("-proc:" + config.getProc());
}
if (config.getAnnotationProcessors() != null) {
args.add("-processor");
String[] procs = config.getAnnotationProcessors();
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < procs.length; i++) {
if (i > 0) {
buffer.append(",");
}
buffer.append(procs[i]);
}
args.add(buffer.toString());
}
if (config.getProcessorPathEntries() != null
&& !config.getProcessorPathEntries().isEmpty()) {
args.add("-processorpath");
args.add(getPathString(config.getProcessorPathEntries()));
}
if (config.getProcessorModulePathEntries() != null
&& !config.getProcessorModulePathEntries().isEmpty()) {
args.add("--processor-module-path");
args.add(getPathString(config.getProcessorModulePathEntries()));
}
}
if (config.isOptimize()) {
args.add("-O");
}
if (config.isDebug()) {
if (StringUtils.isNotEmpty(config.getDebugLevel())) {
args.add("-g:" + config.getDebugLevel());
} else {
args.add("-g");
}
}
if (config.isVerbose()) {
args.add("-verbose");
}
if (JavaVersion.JAVA_1_8.isOlderOrEqualTo(javacVersion) && config.isParameters()) {
args.add("-parameters");
}
if (config.isEnablePreview()) {
args.add("--enable-preview");
}
if (config.getImplicitOption() != null) {
args.add("-implicit:" + config.getImplicitOption());
}
if (config.isShowDeprecation()) {
args.add("-deprecation");
// This is required to actually display the deprecation messages
config.setShowWarnings(true);
}
if (!config.isShowWarnings()) {
args.add("-nowarn");
} else {
String warnings = config.getWarnings();
if (config.isShowLint()) {
if (config.isShowWarnings() && StringUtils.isNotEmpty(warnings)) {
args.add("-Xlint:" + warnings);
} else {
args.add("-Xlint");
}
}
}
if (config.isFailOnWarning()) {
args.add("-Werror");
}
if (JavaVersion.JAVA_9.isOlderOrEqualTo(javacVersion) && !StringUtils.isEmpty(config.getReleaseVersion())) {
args.add("--release");
args.add(config.getReleaseVersion());
} else {
// TODO: this could be much improved
if (StringUtils.isEmpty(config.getTargetVersion())) {
// Required, or it defaults to the target of your JDK (eg 1.5)
args.add("-target");
args.add("1.1");
} else {
args.add("-target");
args.add(config.getTargetVersion());
}
if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion) && StringUtils.isEmpty(config.getSourceVersion())) {
// If omitted, later JDKs complain about a 1.1 target
args.add("-source");
args.add("1.3");
} else if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion)) {
args.add("-source");
args.add(config.getSourceVersion());
}
}
if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion) && !StringUtils.isEmpty(config.getSourceEncoding())) {
args.add("-encoding");
args.add(config.getSourceEncoding());
}
if (!StringUtils.isEmpty(config.getModuleVersion())) {
args.add("--module-version");
args.add(config.getModuleVersion());
}
for (Map.Entry entry : config.getCustomCompilerArgumentsEntries()) {
String key = entry.getKey();
if (StringUtils.isEmpty(key) || key.startsWith("-J")) {
continue;
}
args.add(key);
String value = entry.getValue();
if (StringUtils.isEmpty(value)) {
continue;
}
args.add(value);
}
if (!config.isFork() && !args.contains("-XDuseUnsharedTable=false")) {
args.add("-XDuseUnsharedTable=true");
}
return args.toArray(new String[0]);
}
/**
* Represents a particular Java version (through their according version prefixes)
*/
enum JavaVersion {
JAVA_1_3_OR_OLDER("1.3", "1.2", "1.1", "1.0"),
JAVA_1_4("1.4"),
JAVA_1_5("1.5"),
JAVA_1_6("1.6"),
JAVA_1_7("1.7"),
JAVA_1_8("1.8"),
JAVA_9("9"); // since Java 9 a different versioning scheme was used (https://openjdk.org/jeps/223)
final Set versionPrefixes;
JavaVersion(String... versionPrefixes) {
this.versionPrefixes = new HashSet<>(Arrays.asList(versionPrefixes));
}
/**
* The internal logic checks if the given version starts with the prefix of one of the enums preceding the current one.
*
* @param version the version to check
* @return {@code true} if the version represented by this enum is older than or equal (in its minor and major version) to a given version
*/
boolean isOlderOrEqualTo(String version) {
// go through all previous enums
JavaVersion[] allJavaVersionPrefixes = JavaVersion.values();
for (int n = ordinal() - 1; n > -1; n--) {
if (allJavaVersionPrefixes[n].versionPrefixes.stream().anyMatch(version::startsWith)) {
return false;
}
}
return true;
}
}
/**
* Compile the java sources in a external process, calling an external executable,
* like javac.
*
* @param config compiler configuration
* @param executable name of the executable to launch
* @param args arguments for the executable launched
* @return a CompilerResult object encapsulating the result of the compilation and any compiler messages
* @throws CompilerException
*/
protected CompilerResult compileOutOfProcess(CompilerConfiguration config, String executable, String[] args)
throws CompilerException {
Commandline cli = new Commandline();
cli.setWorkingDirectory(config.getWorkingDirectory().getAbsolutePath());
cli.setExecutable(executable);
try {
File argumentsFile =
createFileWithArguments(args, config.getBuildDirectory().getAbsolutePath());
cli.addArguments(
new String[] {"@" + argumentsFile.getCanonicalPath().replace(File.separatorChar, '/')});
if (!StringUtils.isEmpty(config.getMaxmem())) {
cli.addArguments(new String[] {"-J-Xmx" + config.getMaxmem()});
}
if (!StringUtils.isEmpty(config.getMeminitial())) {
cli.addArguments(new String[] {"-J-Xms" + config.getMeminitial()});
}
for (String key : config.getCustomCompilerArgumentsAsMap().keySet()) {
if (StringUtils.isNotEmpty(key) && key.startsWith("-J")) {
cli.addArguments(new String[] {key});
}
}
} catch (IOException e) {
throw new CompilerException("Error creating file with javac arguments", e);
}
CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
int returnCode;
List messages;
if (getLog().isDebugEnabled()) {
String debugFileName = StringUtils.isEmpty(config.getDebugFileName()) ? "javac" : config.getDebugFileName();
File commandLineFile = new File(
config.getBuildDirectory(),
StringUtils.trim(debugFileName) + "." + (Os.isFamily(Os.FAMILY_WINDOWS) ? "bat" : "sh"));
try {
FileUtils.fileWrite(
commandLineFile.getAbsolutePath(), cli.toString().replaceAll("'", ""));
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
Runtime.getRuntime().exec(new String[] {"chmod", "a+x", commandLineFile.getAbsolutePath()});
}
} catch (IOException e) {
if (getLog().isWarnEnabled()) {
getLog().warn("Unable to write '" + commandLineFile.getName() + "' debug script file", e);
}
}
}
try {
returnCode = CommandLineUtils.executeCommandLine(cli, out, out);
messages = parseModernStream(returnCode, new BufferedReader(new StringReader(out.getOutput())));
} catch (CommandLineException | IOException e) {
throw new CompilerException("Error while executing the external compiler.", e);
}
boolean success = returnCode == 0;
return new CompilerResult(success, messages);
}
/**
* Compile the java sources in the current JVM, without calling an external executable,
* using com.sun.tools.javac.Main
class
*
* @param args arguments for the compiler as they would be used in the command line javac
* @param config compiler configuration
* @return a CompilerResult object encapsulating the result of the compilation and any compiler messages
* @throws CompilerException
*/
CompilerResult compileInProcess(String[] args, CompilerConfiguration config) throws CompilerException {
final Class> javacClass = getJavacClass(config);
final Thread thread = Thread.currentThread();
final ClassLoader contextClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(javacClass.getClassLoader());
if (getLog().isDebugEnabled()) {
getLog().debug("ttcl changed run compileInProcessWithProperClassloader");
}
try {
return compileInProcessWithProperClassloader(javacClass, args);
} finally {
releaseJavaccClass(javacClass, config);
thread.setContextClassLoader(contextClassLoader);
}
}
protected CompilerResult compileInProcessWithProperClassloader(Class> javacClass, String[] args)
throws CompilerException {
return compileInProcess0(javacClass, args);
}
/**
* Helper method for compileInProcess()
*/
private static CompilerResult compileInProcess0(Class> javacClass, String[] args) throws CompilerException {
StringWriter out = new StringWriter();
Integer ok;
List messages;
try {
Method compile = javacClass.getMethod("compile", new Class[] {String[].class, PrintWriter.class});
ok = (Integer) compile.invoke(null, new Object[] {args, new PrintWriter(out)});
messages = parseModernStream(ok, new BufferedReader(new StringReader(out.toString())));
} catch (NoSuchMethodException | IOException | InvocationTargetException | IllegalAccessException e) {
throw new CompilerException("Error while executing the compiler.", e);
}
boolean success = ok == 0;
return new CompilerResult(success, messages);
}
// Match ~95% of existing JDK exception name patterns (last checked for JDK 21)
private static final Pattern STACK_TRACE_FIRST_LINE = Pattern.compile("^(?:[\\w+.-]+\\.)[\\w$]*?(?:"
+ "Exception|Error|Throwable|Failure|Result|Abort|Fault|ThreadDeath|Overflow|Warning|"
+ "NotSupported|NotFound|BadArgs|BadClassFile|Illegal|Invalid|Unexpected|Unchecked|Unmatched\\w+"
+ ").*$");
// Match exception causes, existing and omitted stack trace elements
private static final Pattern STACK_TRACE_OTHER_LINE =
Pattern.compile("^(?:Caused by:\\s.*|\\s*at .*|\\s*\\.\\.\\.\\s\\d+\\smore)$");
// Match generic javac errors with 'javac:' prefix, JMV init and boot layer init errors
private static final Pattern JAVAC_OR_JVM_ERROR =
Pattern.compile("^(?:javac:|Error occurred during initialization of (?:boot layer|VM)).*", Pattern.DOTALL);
/**
* Parse the output from the compiler into a list of CompilerMessage objects
*
* @param exitCode The exit code of javac.
* @param input The output of the compiler
* @return List of CompilerMessage objects
* @throws IOException
*/
static List parseModernStream(int exitCode, BufferedReader input) throws IOException {
List errors = new ArrayList<>();
String line;
StringBuilder buffer = new StringBuilder();
boolean hasPointer = false;
int stackTraceLineCount = 0;
while (true) {
line = input.readLine();
if (line == null) {
// javac output not detected by other parsing
// maybe better to ignore only the summary and mark the rest as error
String bufferAsString = buffer.toString();
if (buffer.length() > 0) {
if (JAVAC_OR_JVM_ERROR.matcher(bufferAsString).matches()) {
errors.add(new CompilerMessage(bufferAsString, CompilerMessage.Kind.ERROR));
} else if (hasPointer) {
// A compiler message remains in buffer at end of parse stream
errors.add(parseModernError(exitCode, bufferAsString));
} else if (stackTraceLineCount > 0) {
// Extract stack trace from end of buffer
String[] lines = bufferAsString.split("\\R");
int linesTotal = lines.length;
buffer = new StringBuilder();
int firstLine = linesTotal - stackTraceLineCount;
// Salvage Javac localized message 'javac.msg.bug' ("An exception has occurred in the
// compiler ... Please file a bug")
if (firstLine > 0) {
final String lineBeforeStackTrace = lines[firstLine - 1];
// One of those two URL substrings should always appear, without regard to JVM locale.
// TODO: Update, if the URL changes, last checked for JDK 21.
if (lineBeforeStackTrace.contains("java.sun.com/webapps/bugreport")
|| lineBeforeStackTrace.contains("bugreport.java.com")) {
firstLine--;
}
}
// Note: For message 'javac.msg.proc.annotation.uncaught.exception' ("An annotation processor
// threw an uncaught exception"), there is no locale-independent substring, and the header is
// also multi-line. It was discarded in the removed method 'parseAnnotationProcessorStream',
// and we continue to do so.
for (int i = firstLine; i < linesTotal; i++) {
buffer.append(lines[i]).append(EOL);
}
errors.add(new CompilerMessage(buffer.toString(), CompilerMessage.Kind.ERROR));
}
}
return errors;
}
if (stackTraceLineCount == 0 && STACK_TRACE_FIRST_LINE.matcher(line).matches()
|| STACK_TRACE_OTHER_LINE.matcher(line).matches()) {
stackTraceLineCount++;
} else {
stackTraceLineCount = 0;
}
// new error block?
if (!line.startsWith(" ") && hasPointer) {
// add the error bean
errors.add(parseModernError(exitCode, buffer.toString()));
// reset for next error block
buffer = new StringBuilder(); // this is quicker than clearing it
hasPointer = false;
}
// TODO: there should be a better way to parse these
if ((buffer.length() == 0) && line.startsWith("error: ")) {
errors.add(new CompilerMessage(line, CompilerMessage.Kind.ERROR));
} else if ((buffer.length() == 0) && line.startsWith("warning: ")) {
errors.add(new CompilerMessage(line, CompilerMessage.Kind.WARNING));
} else if ((buffer.length() == 0) && isNote(line)) {
// skip, JDK 1.5 telling us deprecated APIs are used but -Xlint:deprecation isn't set
} else if ((buffer.length() == 0) && isMisc(line)) {
// verbose output was set
errors.add(new CompilerMessage(line, CompilerMessage.Kind.OTHER));
} else {
buffer.append(line);
buffer.append(EOL);
}
if (line.endsWith("^")) {
hasPointer = true;
}
}
}
private static boolean isMisc(String line) {
return startsWithPrefix(line, MISC_PREFIXES);
}
private static boolean isNote(String line) {
return startsWithPrefix(line, NOTE_PREFIXES);
}
private static boolean startsWithPrefix(String line, String[] prefixes) {
for (String prefix : prefixes) {
if (line.startsWith(prefix)) {
return true;
}
}
return false;
}
/**
* Construct a CompilerMessage object from a line of the compiler output
*
* @param exitCode The exit code from javac.
* @param error output line from the compiler
* @return the CompilerMessage object
*/
static CompilerMessage parseModernError(int exitCode, String error) {
final StringTokenizer tokens = new StringTokenizer(error, ":");
boolean isError = exitCode != 0;
try {
// With Java 6 error output lines from the compiler got longer. For backward compatibility
// .. and the time being, we eat up all (if any) tokens up to the erroneous file and source
// .. line indicator tokens.
boolean tokenIsAnInteger;
StringBuilder file = null;
String currentToken = null;
do {
if (currentToken != null) {
if (file == null) {
file = new StringBuilder(currentToken);
} else {
file.append(':').append(currentToken);
}
}
currentToken = tokens.nextToken();
// Probably the only backward compatible means of checking if a string is an integer.
tokenIsAnInteger = true;
try {
Integer.parseInt(currentToken);
} catch (NumberFormatException e) {
tokenIsAnInteger = false;
}
} while (!tokenIsAnInteger);
final String lineIndicator = currentToken;
final int startOfFileName = file.toString().lastIndexOf(']');
if (startOfFileName > -1) {
file = new StringBuilder(file.substring(startOfFileName + 1 + EOL.length()));
}
final int line = Integer.parseInt(lineIndicator);
final StringBuilder msgBuffer = new StringBuilder();
String msg = tokens.nextToken(EOL).substring(2);
// Remove the 'warning: ' prefix
final String warnPrefix = getWarnPrefix(msg);
if (warnPrefix != null) {
isError = false;
msg = msg.substring(warnPrefix.length());
} else {
isError = exitCode != 0;
}
msgBuffer.append(msg);
msgBuffer.append(EOL);
String context = tokens.nextToken(EOL);
String pointer = null;
do {
final String msgLine = tokens.nextToken(EOL);
if (pointer != null) {
msgBuffer.append(msgLine);
msgBuffer.append(EOL);
} else if (msgLine.endsWith("^")) {
pointer = msgLine;
} else {
msgBuffer.append(context);
msgBuffer.append(EOL);
context = msgLine;
}
} while (tokens.hasMoreTokens());
msgBuffer.append(EOL);
final String message = msgBuffer.toString();
final int startcolumn = pointer.indexOf("^");
int endcolumn = (context == null) ? startcolumn : context.indexOf(" ", startcolumn);
if (endcolumn == -1) {
endcolumn = context.length();
}
return new CompilerMessage(file.toString(), isError, line, startcolumn, line, endcolumn, message.trim());
} catch (NoSuchElementException e) {
return new CompilerMessage("no more tokens - could not parse error message: " + error, isError);
} catch (Exception e) {
return new CompilerMessage("could not parse error message: " + error, isError);
}
}
private static String getWarnPrefix(String msg) {
for (String warningPrefix : WARNING_PREFIXES) {
if (msg.startsWith(warningPrefix)) {
return warningPrefix;
}
}
return null;
}
/**
* put args into a temp file to be referenced using the @ option in javac command line
*
* @param args
* @return the temporary file wth the arguments
* @throws IOException
*/
private File createFileWithArguments(String[] args, String outputDirectory) throws IOException {
PrintWriter writer = null;
try {
File tempFile;
if (getLog().isDebugEnabled()) {
tempFile = File.createTempFile(JavacCompiler.class.getName(), "arguments", new File(outputDirectory));
} else {
tempFile = File.createTempFile(JavacCompiler.class.getName(), "arguments");
tempFile.deleteOnExit();
}
writer = new PrintWriter(new FileWriter(tempFile));
for (String arg : args) {
String argValue = arg.replace(File.separatorChar, '/');
writer.write("\"" + argValue + "\"");
writer.println();
}
writer.flush();
return tempFile;
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* Get the path of the javac tool executable to use.
* Either given through explicit configuration or via {@link #getJavacExecutable()}.
* @param config the configuration
* @return the path of the javac tool
*/
protected String getJavacExecutable(CompilerConfiguration config) {
String executable = config.getExecutable();
if (StringUtils.isEmpty(executable)) {
try {
executable = getJavacExecutable();
} catch (IOException e) {
if (getLog().isWarnEnabled()) {
getLog().warn("Unable to autodetect 'javac' path, using 'javac' from the environment.");
}
executable = "javac";
}
}
return executable;
}
/**
* Get the path of the javac tool executable: try to find it depending the OS or the java.home
* system property or the JAVA_HOME
environment variable.
*
* @return the path of the javac tool
* @throws IOException if not found
*/
private static String getJavacExecutable() throws IOException {
String javacCommand = "javac" + (Os.isFamily(Os.FAMILY_WINDOWS) ? ".exe" : "");
String javaHome = System.getProperty("java.home");
File javacExe;
if (Os.isName("AIX")) {
javacExe = new File(javaHome + File.separator + ".." + File.separator + "sh", javacCommand);
} else if (Os.isName("Mac OS X")) {
javacExe = new File(javaHome + File.separator + "bin", javacCommand);
} else {
javacExe = new File(javaHome + File.separator + ".." + File.separator + "bin", javacCommand);
}
// ----------------------------------------------------------------------
// Try to find javacExe from JAVA_HOME environment variable
// ----------------------------------------------------------------------
if (!javacExe.isFile()) {
Properties env = CommandLineUtils.getSystemEnvVars();
javaHome = env.getProperty("JAVA_HOME");
if (StringUtils.isEmpty(javaHome)) {
throw new IOException("The environment variable JAVA_HOME is not correctly set.");
}
if (!new File(javaHome).isDirectory()) {
throw new IOException("The environment variable JAVA_HOME=" + javaHome
+ " doesn't exist or is not a valid directory.");
}
javacExe = new File(env.getProperty("JAVA_HOME") + File.separator + "bin", javacCommand);
}
if (!javacExe.isFile()) {
throw new IOException("The javadoc executable '" + javacExe
+ "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable.");
}
return javacExe.getAbsolutePath();
}
private void releaseJavaccClass(Class> javaccClass, CompilerConfiguration compilerConfiguration) {
if (compilerConfiguration.getCompilerReuseStrategy()
== CompilerConfiguration.CompilerReuseStrategy.ReuseCreated) {
javacClasses.add(javaccClass);
}
}
/**
* Find the main class of JavaC. Return the same class for subsequent calls.
*
* @return the non-null class.
* @throws CompilerException if the class has not been found.
*/
private Class> getJavacClass(CompilerConfiguration compilerConfiguration) throws CompilerException {
Class> c;
switch (compilerConfiguration.getCompilerReuseStrategy()) {
case AlwaysNew:
return createJavacClass();
case ReuseCreated:
c = javacClasses.poll();
if (c == null) {
c = createJavacClass();
}
return c;
case ReuseSame:
default:
c = javacClass;
if (c == null) {
synchronized (this) {
c = javacClass;
if (c == null) {
javacClass = c = createJavacClass();
}
}
}
return c;
}
}
/**
* Helper method for create Javac class
*/
protected Class> createJavacClass() throws CompilerException {
try {
// look whether JavaC is on Maven's classpath
// return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, JavacCompiler.class.getClassLoader() );
return JavacCompiler.class.getClassLoader().loadClass(JavacCompiler.JAVAC_CLASSNAME);
} catch (ClassNotFoundException ex) {
// ok
}
final File toolsJar = new File(System.getProperty("java.home"), "../lib/tools.jar");
if (!toolsJar.exists()) {
throw new CompilerException("tools.jar not found: " + toolsJar);
}
try {
// Combined classloader with no parent/child relationship, so classes in our classloader
// can reference classes in tools.jar
URL[] originalUrls = ((URLClassLoader) JavacCompiler.class.getClassLoader()).getURLs();
URL[] urls = new URL[originalUrls.length + 1];
urls[0] = toolsJar.toURI().toURL();
System.arraycopy(originalUrls, 0, urls, 1, originalUrls.length);
ClassLoader javacClassLoader = new URLClassLoader(urls);
final Thread thread = Thread.currentThread();
final ClassLoader contextClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(javacClassLoader);
try {
// return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, javacClassLoader );
return javacClassLoader.loadClass(JavacCompiler.JAVAC_CLASSNAME);
} finally {
thread.setContextClassLoader(contextClassLoader);
}
} catch (MalformedURLException ex) {
throw new CompilerException(
"Could not convert the file reference to tools.jar to a URL, path to tools.jar: '"
+ toolsJar.getAbsolutePath() + "'.",
ex);
} catch (ClassNotFoundException ex) {
throw new CompilerException(
"Unable to locate the Javac Compiler in:" + EOL + " " + toolsJar + EOL
+ "Please ensure you are using JDK 1.4 or above and" + EOL
+ "not a JRE (the com.sun.tools.javac.Main class is required)." + EOL
+ "In most cases you can change the location of your Java" + EOL
+ "installation by setting the JAVA_HOME environment variable.",
ex);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy