/*******************************************************************************
* Copyright (c) 2010 Codehaus.org, SpringSource, 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:
* Andrew Eisenberg - Initial API and implementation
* Carlos Fernandez - fix for nowarn
* Travis Schneeberger - ensure that all options are supported
*******************************************************************************/
package org.codehaus.groovy.eclipse.compiler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import org.codehaus.groovy.eclipse.compiler.InternalCompiler.Result;
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.CompilerResult;
import org.codehaus.plexus.compiler.CompilerMessage.Kind;
import org.codehaus.plexus.compiler.CompilerOutputStyle;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
import org.codehaus.plexus.logging.Logger;
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;
/**
* Allows the use of the Groovy-Eclipse compiler through maven.
*
* @plexus.component role="org.codehaus.plexus.compiler.Compiler"
* role-hint="groovy-eclipse-compiler"
*
*
* @author <a href="mailto:andrew@eisenberg.as">Andrew Eisenberg</a>
* @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a>
* @author <a href="mailto:matthew.pocock@ncl.ac.uk">Matthew Pocock</a>
*/
public class GroovyEclipseCompiler extends AbstractCompiler {
// IMPORTANT!!! this class must not reference any JDT classes directly. Must be loadable even if batch compiler not around
private static final String PROB_SEPARATOR = "----------\n";
private static final String JAVA_AGENT_CLASS_PARAM_NAME = "-javaAgentClass";
private String javaAgentClass = "";
boolean verbose;
public GroovyEclipseCompiler() {
// here is a bit of a hack. maven only wants a single file extension
// for sources, so we pass it "". Later, we must recalculate for real.
super(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, "", ".class", null);
}
@Override
public CompilerResult performCompile(CompilerConfiguration configuration) throws CompilerException {
checkForGroovyEclipseBatch();
List<CompilerMessage> messages = new ArrayList<CompilerMessage>();
boolean result = internalCompile(configuration, messages);
return new CompilerResult(result, messages);
}
/**
* groovy-eclipse-batch must be depended upon explicitly.if it is not there, then raise a nice, readable error
* @throws CompilerException
*/
private void checkForGroovyEclipseBatch() throws CompilerException {
try {
Class.forName("org.eclipse.jdt.core.compiler.CompilationProgress");
} catch (Exception e) {
throw new CompilerException("Could not find groovy-eclipse-batch artifact. "
+ "Must add this artifact as an explicit dependency the pom.");
}
}
private boolean internalCompile(CompilerConfiguration config, List<CompilerMessage> messages) throws CompilerException {
String[] args = createCommandLine(config);
if (args.length == 0) {
getLogger().info("Nothing to compile - all classes are up to date");
return true;
}
boolean success;
if (config.isFork()) {
String executable = config.getExecutable();
if (StringUtils.isEmpty(executable)) {
try {
executable = getJavaExecutable();
} catch (IOException e) {
getLogger().warn("Unable to autodetect 'java' path, using 'java' from the environment.");
executable = "java";
}
}
String groovyEclipseLocation = getGroovyEclipseBatchLocation();
success = compileOutOfProcess(config, executable, groovyEclipseLocation, args, messages);
} else {
StringWriter out = new StringWriter();
Result result = InternalCompiler.doCompile(args, out, getLogger(), verbose);
success = result.success;
try {
messages.addAll(parseMessages(success ? 0 : 1, out.getBuffer().toString(), config.isShowWarnings()));
} catch (IOException e) {
messages = new ArrayList<CompilerMessage>(1);
}
if (!success) {
messages.add(formatResult(success, result.globalErrorsCount, result.globalWarningsCount));
}
}
return success;
}
private File[] recalculateStaleFiles(CompilerConfiguration config) throws CompilerException {
config.setSourceFiles(null);
long staleMillis = 0; // can we do better than using 0?
Set<String> includes = config.getIncludes();
if (includes == null || includes.isEmpty()) {
includes = Collections.singleton("**/*");
}
StaleSourceScanner scanner = new StaleSourceScanner(staleMillis, includes, config.getExcludes());
Set<File> staleSources = computeStaleSources(config, scanner);
config.setSourceFiles(staleSources);
File[] sourceFiles = staleSources.toArray(new File[0]);
return sourceFiles;
}
private boolean startsWithHyphen(Object key) {
return null != key && String.class.isInstance(key) && ((String) key).startsWith("-");
}
private CompilerMessage formatResult(boolean result, int globalErrorsCount, int globalWarningsCount) {
if (result) {
return new CompilerMessage("Success!", Kind.NOTE);
} else {
Kind kind;
if (globalErrorsCount > 0) {
kind = Kind.ERROR;
} else if (globalWarningsCount > 0) {
kind = Kind.WARNING;
} else {
kind = Kind.NOTE;
}
String error = globalErrorsCount == 1 ? "error" : "errors";
String warning = globalWarningsCount == 1 ? "warning" : "warnings";
return new CompilerMessage("Found " + globalErrorsCount + " " + error + " and "
+ globalWarningsCount + " " + warning + ".", kind);
}
}
private Map<String,String> composeSourceFiles(File[] sourceFiles) {
Map<String,String> sources = new DeduplicatingHashMap<String,String>(getLogger(), sourceFiles.length);
for (int i = 0; i < sourceFiles.length; i++) {
sources.put(sourceFiles[i].getPath(), null);
}
return sources;
}
public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
File destinationDir = new File(config.getOutputLocation());
if (!destinationDir.exists()) {
destinationDir.mkdirs();
}
// adds src/main/groovy and src/test/groovy if exksts not already added
File workingDirectory = config.getWorkingDirectory();
// assume dest dir for main is in target/classes and for test is in
// target/test-classes
// There must be a more robust way of doing this.
if (destinationDir.getName().equals("classes")) {
File srcMainGroovy = new File(workingDirectory, "src/main/groovy");
if (srcMainGroovy.exists() && !config.getSourceLocations().contains(srcMainGroovy.getAbsolutePath())) {
config.addSourceLocation(srcMainGroovy.getAbsolutePath());
}
}
if (destinationDir.getName().equals("test-classes")) {
File srcTestGroovy = new File(workingDirectory, "src/test/groovy");
if (srcTestGroovy.exists() && !config.getSourceLocations().contains(srcTestGroovy.getAbsolutePath())) {
config.addSourceLocation(srcTestGroovy.getAbsolutePath());
}
}
// recalculate stale files since they were not properly calculated in
// super
File[] sourceFiles = recalculateStaleFiles(config);
if (sourceFiles.length == 0) {
return new String[0];
}
getLogger().info("Using Groovy-Eclipse compiler to compile both Java and Groovy files");
getLogger().debug(
"Compiling " + sourceFiles.length + " " + "source file" + (sourceFiles.length == 1 ? "" : "s") + " to "
+ destinationDir.getAbsolutePath());
// intentionally using DeduplicatingHashMap to preserve order and Map to deduplicate values
// See https://jira.codehaus.org/browse/GRECLIPSE-1659
Map<String,String> args = new DeduplicatingHashMap<String,String>(getLogger());
String cp = super.getPathString(config.getClasspathEntries());
verbose = config.isVerbose();
if (verbose) {
getLogger().info("Classpath: " + cp);
}
if (cp.length() > 0) {
args.put("-cp", cp);
}
if (config.getOutputLocation() != null && config.getOutputLocation().length() > 0) {
args.put("-d", config.getOutputLocation());
}
if (config.isDebug()) {
if (config.getDebugLevel() != null && config.getDebugLevel().trim().length() > 0) {
args.put("-g:" + config.getDebugLevel(), null);
} else {
args.put("-g", null);
}
}
if ("none".equals(config.getProc())) {
args.put("-proc:none", null);
} else if ("only".equals(config.getProc())) {
args.put("-proc:only", null);
}
if (config.getGeneratedSourcesDirectory() != null) {
args.put("-s", config.getGeneratedSourcesDirectory().getAbsolutePath());
}
// change default to 1.5
String source = config.getSourceVersion();
if (source != null && source.length() > 0) {
args.put("-source", source);
} else {
args.put("-source", "1.5");
}
String target = config.getTargetVersion();
if (target != null && target.length() > 0) {
args.put("-target", target);
} else {
args.put("-target", "1.5");
}
if (config.isShowDeprecation()) {
args.put("-deprecation", null);
}
if (!config.isShowWarnings()) {
args.put("-nowarn", null);
}
if (config.getAnnotationProcessors() != null) {
StringBuilder procArg = new StringBuilder();
for (String proc : config.getAnnotationProcessors()) {
if (proc != null && proc.trim().length() > 0) {
procArg.append(proc);
procArg.append(",");
}
}
if (procArg.length() > 0) {
procArg.replace(procArg.length() - 1, procArg.length(), "");
args.put("-processor ", "\"" + procArg.toString() + "\"");
}
}
if (verbose) {
args.put("-verbose", null);
}
if (config.getSourceEncoding() != null) {
args.put("-encoding", config.getSourceEncoding());
}
for (Entry<String, String> entry : config.getCustomCompilerArgumentsAsMap().entrySet()) {
String key = entry.getKey();
if (startsWithHyphen(key)) {
if (JAVA_AGENT_CLASS_PARAM_NAME.equals(key)) {
setJavaAgentClass(entry.getValue());
// do not add the custom java agent arg because it is not
// expected by groovy-eclipse compiler
continue;
} else {
// don't add a "-" if the arg
// already has one
args.put(key, entry.getValue());
}
} else if (key != null && !key.equals("org.osgi.framework.system.packages")) {
// See https://jira.codehaus.org/browse/GRECLIPSE-1418 ignore
// the system packages option
/*
* Not sure what the possible range of usage looks like but I
* don't think this should allow for null keys? "-null" probably
* isn't going to play nicely with any compiler?
*/
args.put("-" + key, entry.getValue());
}
}
args.putAll(composeSourceFiles(sourceFiles));
String[] argsList = flattenArgumentsMap(args);
if (verbose) {
getLogger().info("All args: " + Arrays.toString(argsList));
}
return argsList;
}
private Set<File> computeStaleSources(CompilerConfiguration compilerConfiguration, SourceInclusionScanner scanner)
throws CompilerException {
SourceMapping mappingGroovy = new SuffixMapping(".groovy", ".class");
SourceMapping mappingJava = new SuffixMapping(".java", ".class");
scanner.addSourceMapping(mappingGroovy);
scanner.addSourceMapping(mappingJava);
File outputDirectory = new File(compilerConfiguration.getOutputLocation());
Set<File> staleSources = new HashSet<File>();
for (String sourceRoot : compilerConfiguration.getSourceLocations()) {
if (verbose) {
getLogger().info("Looking for sources in source root: " + sourceRoot);
}
File rootFile = new File(sourceRoot);
if (!rootFile.isDirectory()) {
continue;
}
try {
staleSources.addAll(scanner.getIncludedSources(rootFile, outputDirectory));
} catch (InclusionScanException e) {
throw new CompilerException(
"Error scanning source root: \'" + sourceRoot + "\' " + "for stale files to recompile.", e);
}
}
return staleSources;
}
/**
* 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
* @param messages2
* @return List of CompilerError objects with the errors encountered.
* @throws CompilerException
*/
private boolean compileOutOfProcess(CompilerConfiguration config, String executable, String groovyEclipseLocation,
String[] args, List<CompilerMessage> messages) throws CompilerException {
Commandline cli = new Commandline();
cli.setWorkingDirectory(config.getWorkingDirectory().getAbsolutePath());
cli.setExecutable(executable);
try {
// we need to setup any javaagent before the -jar flag
if (!StringUtils.isEmpty(javaAgentClass)) {
cli.addArguments(new String[] { "-javaagent:" + getAdditionnalJavaAgentLocation() });
} else {
getLogger().info("no javaAgentClass seems to be set");
}
cli.addArguments(new String[] { "-jar", groovyEclipseLocation });
File argumentsFile = createFileWithArguments(args, config.getOutputLocation());
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() });
}
} catch (IOException e) {
throw new CompilerException("Error creating file with javac arguments", e);
}
CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
int returnCode;
if ((getLogger() != null) && getLogger().isDebugEnabled()) {
File commandLineFile = new File(config.getOutputLocation(), "greclipse."
+ (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 ((getLogger() != null) && getLogger().isWarnEnabled()) {
getLogger().warn("Unable to write '" + commandLineFile.getName() + "' debug script file", e);
}
}
}
try {
getLogger().info("Compiling in a forked process using " + groovyEclipseLocation);
returnCode = CommandLineUtils.executeCommandLine(cli, out, err);
messages.addAll(parseMessages(returnCode, out.getOutput(), config.isShowWarnings()));
} catch (CommandLineException e) {
throw new CompilerException("Error while executing the external compiler.", e);
} catch (IOException e) {
throw new CompilerException("Error while executing the external compiler.", e);
}
if ((returnCode != 0) && messages.isEmpty()) {
if (err.getOutput().length() == 0) {
throw new CompilerException("Unknown error trying to execute the external compiler: " + EOL + cli.toString());
} else {
messages.add(new CompilerMessage("Failure executing groovy-eclipse compiler:" + EOL + err.getOutput(), Kind.ERROR));
}
}
return returnCode == 0;
}
/**
* Parse the output from the compiler into a list of CompilerError objects
*
* @param exitCode
* The exit code of javac.
* @param input
* The output of the compiler
* @return List of CompilerError objects
* @throws IOException
*/
private List<CompilerMessage> parseMessages(int exitCode, String input, boolean showWarnings) throws IOException {
List<CompilerMessage> parsedMessages = new ArrayList<CompilerMessage>();
String[] msgs = input.split(PROB_SEPARATOR);
for (String msg : msgs) {
if (msg.length() > 1) {
// add the error bean
CompilerMessage message = parseMessage(msg, showWarnings, false);
if (message != null) {
if (showWarnings || message.getKind() == Kind.ERROR) {
parsedMessages.add(message);
}
} else {
// assume that there are one or more non-normal messages here
// All messages start with <num>. ERROR or <num>. WARNING
String[] extraMsgs = msg.split("\n");
StringBuilder sb = new StringBuilder();
for (String extraMsg : extraMsgs) {
if (extraMsg.indexOf(". WARNING") > 0 || extraMsg.indexOf(". ERROR") > 0) {
handleCurrentMessage(showWarnings, parsedMessages, sb);
sb = new StringBuilder("\n").append(extraMsg).append("\n");
} else {
if (!PROB_SEPARATOR.equals(extraMsg)) {
sb.append(extraMsg).append("\n");
}
}
}
handleCurrentMessage(showWarnings, parsedMessages, sb);
}
}
}
return parsedMessages;
}
private void handleCurrentMessage(final boolean showWarnings, final List<CompilerMessage> parsedMessages, final StringBuilder sb)
{
final CompilerMessage message;
if (sb.length() > 0) {
message = parseMessage(sb.toString(), showWarnings, true);
if (showWarnings || message.getKind() == Kind.ERROR) {
parsedMessages.add(message);
}
}
}
/**
* Construct a CompilerError object from a line of the compiler output
*
* @param msgText
* output line from the compiler
* @return the CompilerError object
*/
private CompilerMessage parseMessage(String msgText, boolean showWarning, boolean force) {
// message should look like this:
// 1. WARNING in /Users/andrew/git-repos/foo/src/main/java/packAction.java (at line 47)
// public abstract class AbstractScmTagAction extends TaskAction implements BuildBadgeAction {
// ^^^^^^^^^^^^^^^^^^^^
// But there will also be messages contributed from annotation processors that will look non-normal
int dotIndex = msgText.indexOf('.');
Kind kind;
boolean isNormal = false;
if (dotIndex > 0) {
if (msgText.substring(dotIndex, dotIndex + ". WARNING".length()).equals(". WARNING")) {
kind = Kind.WARNING;
isNormal = true;
dotIndex += ". WARNING in ".length();
} else if (msgText.substring(dotIndex, dotIndex + ". ERROR".length()).equals(". ERROR")) {
kind = Kind.ERROR;
isNormal = true;
dotIndex += ". ERROR in ".length();
} else {
kind = Kind.NOTE;
}
} else {
kind = Kind.NOTE;
}
int firstNewline = msgText.indexOf('\n');
String firstLine = firstNewline > 0 ? msgText.substring(0, firstNewline) : msgText;
String rest = firstNewline > 0 ? msgText.substring(firstNewline+1) : "";
if (isNormal) {
try {
int parenIndex = firstLine.indexOf(" (");
String file = firstLine.substring(dotIndex, parenIndex);
int line = Integer.parseInt(firstLine.substring(parenIndex + " (at line ".length(), firstLine.indexOf(')')));
int lastLineIndex = rest.lastIndexOf("\n\t");
int startColumn = rest.indexOf('^', lastLineIndex) -1 - lastLineIndex; // -1 because starts with tab
int endColumn = rest.lastIndexOf('^') -1 - lastLineIndex;
return new CompilerMessage(file, kind, line, startColumn, line, endColumn, msgText);
} catch (RuntimeException e) {
// lots of things could go wrong
if (force) {
return new CompilerMessage(msgText, kind);
} else {
return null;
}
}
} else {
if (force) {
return new CompilerMessage(msgText, kind);
} else {
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 ((getLogger() != null) && getLogger().isDebugEnabled()) {
tempFile = File.createTempFile(GroovyEclipseCompiler.class.getName(), "arguments", new File(outputDirectory));
} else {
tempFile = File.createTempFile(GroovyEclipseCompiler.class.getName(), "arguments");
tempFile.deleteOnExit();
}
writer = new PrintWriter(new FileWriter(tempFile));
for (int i = 0; i < args.length; i++) {
String argValue = args[i].replace(File.separatorChar, '/');
writer.write("\"" + argValue + "\"");
writer.println();
}
writer.flush();
return tempFile;
} finally {
if (writer != null) {
writer.close();
}
}
}
private String getAdditionnalJavaAgentLocation() throws CompilerException {
return getClassLocation(getJavaAgentClass());
}
private String getGroovyEclipseBatchLocation() throws CompilerException {
// can't reference JDT directly in this class
return getClassLocation("org.eclipse.jdt.internal.compiler.batch.Main");
}
private String getClassLocation(String className) throws CompilerException {
Class<?> cls;
try {
cls = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new CompilerException("Cannot find the requested className <" + className + "> in classpath");
}
ProtectionDomain pDomain = cls.getProtectionDomain();
CodeSource cSource = pDomain.getCodeSource();
if (cSource != null) {
URL loc = cSource.getLocation();
File file;
try {
file = new File(URLDecoder.decode(loc.getPath(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
getLogger().warn("Unsupported Encoding for URL: " + loc, e);
file = new File(loc.getPath());
}
getLogger().info("Found location <" + file.getPath() + "> for className <" + className + ">");
return file.getPath();
} else {
throw new CompilerException("Cannot find the location of the requested className <" + className + "> in classpath");
}
}
/**
* Get the path of the javac tool executable: try to find it depending the
* OS or the <code>java.home</code> system property or the
* <code>JAVA_HOME</code> environment variable.
*
* @return the path of the Javadoc tool
* @throws IOException
* if not found
*/
private String getJavaExecutable() throws IOException {
String javaCommand = "java" + (Os.isFamily(Os.FAMILY_WINDOWS) ? ".exe" : "");
String javaHome = System.getProperty("java.home");
File javaExe;
if (Os.isName("AIX")) {
javaExe = new File(javaHome + File.separator + ".." + File.separator + "sh", javaCommand);
} else if (Os.isName("Mac OS X")) {
javaExe = new File(javaHome + File.separator + "bin", javaCommand);
} else {
javaExe = new File(javaHome + File.separator + ".." + File.separator + "bin", javaCommand);
}
// ----------------------------------------------------------------------
// Try to find javacExe from JAVA_HOME environment variable
// ----------------------------------------------------------------------
if (!javaExe.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.");
}
javaExe = new File(env.getProperty("JAVA_HOME") + File.separator + "bin", javaCommand);
}
if (!javaExe.isFile()) {
throw new IOException("The javadoc executable '" + javaExe
+ "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable.");
}
return javaExe.getAbsolutePath();
}
/**
* Returns content of the Map as an array of Strings. Ignores {@code null} and empty Strings.
* Implementation note {@link LinkedHashMap} is preferred Map implementation as it preserves order
* @param args Map to be converted
* @return Array with {@code args} converted to an array
*/
private String[] flattenArgumentsMap(Map<String,String> args) {
List<String> argsList = new ArrayList<String>(args.size()*2);
for(Map.Entry<String, String> entry: args.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(key!=null && key.length()>0) {
argsList.add(key);
// adds value only if key is actually defined
if(value!=null && value.length()>0) {
argsList.add(value);
}
}
}
return argsList.toArray(new String[0]);
}
public String getJavaAgentClass() {
return javaAgentClass;
}
public void setJavaAgentClass(String className) {
this.javaAgentClass = className;
}
/**
* Linked Hash Map implementation that logs replaced entries
*
* @author <a href="kpiwko@redhat.com">Karel Piwko</a>
*
*/
private static class DeduplicatingHashMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = -589299605523895999L;
private Logger logger;
public DeduplicatingHashMap(Logger logger, int initialCapacity) {
super(initialCapacity);
this.logger = logger;
}
public DeduplicatingHashMap(Logger logger) {
super();
this.logger = logger;
}
@Override
public V put(K key, V value) {
if(this.containsKey(key) && logger.isDebugEnabled()) {
logger.debug("Replacing compiler argument \"" +key + "\" old value: " + this.get(key) + " with: " + value);
}
return super.put(key, value);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for(Map.Entry<? extends K, ? extends V> entry: m.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
}
}