Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.takari.maven.plugins.compile.jdt.CompilerJdt Maven / Gradle / Ivy
package io.takari.maven.plugins.compile.jdt;
import io.takari.incrementalbuild.BuildContext;
import io.takari.incrementalbuild.BuildContext.InputMetadata;
import io.takari.incrementalbuild.spi.DefaultBuildContext;
import io.takari.incrementalbuild.spi.DefaultInput;
import io.takari.incrementalbuild.spi.DefaultInputMetadata;
import io.takari.incrementalbuild.spi.DefaultOutput;
import io.takari.incrementalbuild.spi.DefaultOutputMetadata;
import io.takari.maven.plugins.compile.AbstractCompiler;
import io.takari.maven.plugins.compile.jdt.classpath.Classpath;
import io.takari.maven.plugins.compile.jdt.classpath.ClasspathEntry;
import io.takari.maven.plugins.compile.jdt.classpath.JavaInstallation;
import io.takari.maven.plugins.compile.jdt.classpath.MutableClasspathEntry;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.maven.plugin.MojoExecutionException;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.builder.ProblemFactory;
import com.google.common.base.Stopwatch;
/**
* @TODO test classpath order changes triggers rebuild of affected sources (same type name,
* different classes)
* @TODO figure out why JDT needs to worry about duplicate types (maybe related to classpath order
* above)
* @TODO test affected sources are recompiled after source gets compile error
* @TODO test nested types because addDependentsOf has some special handling
*/
@Named(CompilerJdt.ID)
public class CompilerJdt extends AbstractCompiler implements ICompilerRequestor {
public static final String ID = "jdt";
/**
* Output .class file structure hash
*/
private static final String ATTR_CLASS_DIGEST = "jdt.class.digest";
/**
* Classpath digest, map of accessible types to their .class structure hashes.
*/
private static final String ATTR_CLASSPATH_DIGEST = "jdt.classpath.digest";
/**
* Java source {@link ReferenceCollection}
*/
private static final String ATTR_REFERENCES = "jdt.references";
private Classpath dependencypath;
/**
* Set of ICompilationUnit to be compiled.
*/
private final Set compileQueue = new LinkedHashSet();
/**
* Set of File that have already been added to the compile queue.
*/
private final Set processedSources = new LinkedHashSet();
private final Set rootNames = new LinkedHashSet();
private final Set qualifiedNames = new LinkedHashSet();
private final Set simpleNames = new LinkedHashSet();
private final ClassfileDigester digester = new ClassfileDigester();
private final ClasspathEntryCache classpathCache;
private final ClasspathDigester classpathDigester;
@Inject
public CompilerJdt(DefaultBuildContext> context, ClasspathEntryCache classpathCache,
ClasspathDigester classpathDigester) {
super(context);
this.classpathCache = classpathCache;
this.classpathDigester = classpathDigester;
}
@Override
public void compile() throws MojoExecutionException, IOException {
IErrorHandlingPolicy errorHandlingPolicy = DefaultErrorHandlingPolicies.exitAfterAllProblems();
Map args = new HashMap();
// XXX figure out how to reuse source/target check from jdt
// org.eclipse.jdt.internal.compiler.batch.Main.validateOptions(boolean)
args.put(CompilerOptions.OPTION_TargetPlatform, getTarget()); // support 5/6/7 aliases
args.put(CompilerOptions.OPTION_Compliance, getTarget()); // support 5/6/7 aliases
args.put(CompilerOptions.OPTION_Source, getSource()); // support 5/6/7 aliases
CompilerOptions compilerOptions = new CompilerOptions(args);
compilerOptions.performMethodsFullRecovery = false;
compilerOptions.performStatementsRecovery = false;
compilerOptions.verbose = isVerbose();
compilerOptions.suppressWarnings = true;
IProblemFactory problemFactory = ProblemFactory.getProblemFactory(Locale.getDefault());
Classpath namingEnvironment = createClasspath();
Compiler compiler =
new Compiler(namingEnvironment, errorHandlingPolicy, compilerOptions, this, problemFactory);
compiler.options.produceReferenceInfo = true;
// TODO optimize full build.
// there is no need to track processed inputs during full build,
// which saves memory and GC cycles
// also, if number of sources in the previous build is known, it may be more efficient to
// rebuild everything after certain % of sources is modified
// keep calling the compiler while there are sources in the queue
while (!compileQueue.isEmpty()) {
ICompilationUnit[] sourceFiles =
compileQueue.toArray(new ICompilationUnit[compileQueue.size()]);
compileQueue.clear();
compiler.compile(sourceFiles);
namingEnvironment.reset();
enqueueAffectedSources();
}
}
@Override
public boolean setSources(List sources) throws IOException {
enqueue(context.registerAndProcessInputs(sources));
// remove stale outputs and rebuild all sources that reference them
for (DefaultOutputMetadata output : context.deleteStaleOutputs(false)) {
addDependentsOf(getJavaType(output));
}
enqueueAffectedSources();
return !compileQueue.isEmpty();
}
private String getJavaType(DefaultOutputMetadata output) {
String outputDirectory = getOutputDirectory().getAbsolutePath();
String path = output.getResource().getAbsolutePath();
if (!path.startsWith(outputDirectory) || !path.endsWith(".class")) {
return null;
}
path = path.substring(outputDirectory.length(), path.length() - ".class".length());
if (path.startsWith(File.separator)) {
path = path.substring(1);
}
return path.replace(File.separatorChar, '.');
}
private void enqueue(Iterable> sources) {
for (DefaultInput source : sources) {
enqueue(source.getResource());
}
}
private void enqueueAffectedSources() throws IOException {
for (InputMetadata input : context.getRegisteredInputs(File.class)) {
final File resource = input.getResource();
if (!processedSources.contains(resource) && resource.canRead()) {
ReferenceCollection references = input.getValue(ATTR_REFERENCES, ReferenceCollection.class);
if (references != null && references.includes(qualifiedNames, simpleNames, rootNames)) {
enqueue(resource);
}
}
}
qualifiedNames.clear();
simpleNames.clear();
rootNames.clear();
}
private void enqueue(File sourceFile) {
if (processedSources.add(sourceFile)) {
compileQueue.add(newSourceFile(sourceFile));
}
}
private CompilationUnit newSourceFile(File source) {
final String fileName = source.getAbsolutePath();
final String encoding = getSourceEncoding() != null ? getSourceEncoding().name() : null;
return new CompilationUnit(null, fileName, encoding, getOutputDirectory().getAbsolutePath(),
false);
}
private Classpath createClasspath() throws IOException {
final List entries = new ArrayList();
final List mutableentries = new ArrayList();
// XXX detect change!
for (File file : JavaInstallation.getDefault().getClasspath()) {
ClasspathEntry entry = classpathCache.get(file);
if (entry != null) {
entries.add(entry);
}
}
CompileQueueClasspathEntry queueEntry = new CompileQueueClasspathEntry(compileQueue);
entries.add(queueEntry);
mutableentries.add(queueEntry);
OutputDirectoryClasspathEntry output = new OutputDirectoryClasspathEntry(getOutputDirectory());
entries.add(output);
mutableentries.add(output);
entries.addAll(dependencypath.getEntries());
return new Classpath(entries, mutableentries);
}
@Override
public boolean setClasspath(List dependencies) throws IOException {
final List dependencypath = new ArrayList();
final List files = new ArrayList();
for (File dependency : dependencies) {
ClasspathEntry entry = classpathCache.get(dependency);
if (entry != null) {
dependencypath.add(entry);
files.add(dependency);
}
}
this.dependencypath = new Classpath(dependencypath, null);
Stopwatch stopwatch = new Stopwatch().start();
long typecount = 0, packagecount = 0;
HashMap digest = classpathDigester.digestDependencies(files);
DefaultInputMetadata metadata = context.registerInput(getPom());
@SuppressWarnings("unchecked")
Map oldDigest =
(Map) metadata.getValue(ATTR_CLASSPATH_DIGEST, Serializable.class);
boolean changed = false;
if (oldDigest != null) {
Set changedPackages = new HashSet();
for (Map.Entry entry : digest.entrySet()) {
String type = entry.getKey();
byte[] hash = entry.getValue();
if (!Arrays.equals(hash, oldDigest.get(type))) {
changed = true;
addDependentsOf(type);
}
changedPackages.add(getPackage(type));
}
for (String oldType : oldDigest.keySet()) {
if (!digest.containsKey(oldType)) {
changed = true;
addDependentsOf(oldType);
}
changedPackages.remove(getPackage(oldType));
}
for (String changedPackage : changedPackages) {
addDependentsOf(changedPackage);
}
} else {
changed = true;
}
if (changed) {
metadata.process().setValue(ATTR_CLASSPATH_DIGEST, digest);
}
log.debug("Verified {} types and {} packages in {} ms", typecount, packagecount,
stopwatch.elapsed(TimeUnit.MILLISECONDS));
enqueueAffectedSources();
return !compileQueue.isEmpty();
}
private String getPackage(String type) {
int idx = type.lastIndexOf('.');
return idx > 0 ? type.substring(0, idx) : null;
}
@Override
public void acceptResult(CompilationResult result) {
if (result == null) {
return; // ah?
}
final String sourceName = new String(result.getFileName());
final File sourceFile = new File(sourceName);
processedSources.add(sourceFile);
// JDT may decide to compile more sources than it was asked to in some cases
// always register and process sources with build context
DefaultInput input = context.registerInput(sourceFile).process();
// track type references
input.setValue(ATTR_REFERENCES, new ReferenceCollection(result.rootReferences,
result.qualifiedReferences, result.simpleNameReferences));
if (result.hasProblems()) {
for (CategorizedProblem problem : result.getProblems()) {
input.addMessage(problem.getSourceLineNumber(), ((DefaultProblem) problem).column, problem
.getMessage(), problem.isError()
? BuildContext.Severity.ERROR
: BuildContext.Severity.WARNING, null);
}
}
if (!result.hasErrors()) {
for (ClassFile classFile : result.getClassFiles()) {
try {
char[] filename = classFile.fileName();
int length = filename.length;
char[] relativeName = new char[length + 6];
System.arraycopy(filename, 0, relativeName, 0, length);
System.arraycopy(SuffixConstants.SUFFIX_class, 0, relativeName, length, 6);
CharOperation.replace(relativeName, '/', File.separatorChar);
String relativeStringName = new String(relativeName);
writeClassFile(input, relativeStringName, classFile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// XXX double check affected sources are recompiled when this source has errors
}
private void writeClassFile(DefaultInput input, String relativeStringName,
ClassFile classFile) throws IOException {
final byte[] bytes = classFile.getBytes();
final File outputFile = new File(getOutputDirectory(), relativeStringName);
final DefaultOutput output = input.associateOutput(outputFile);
boolean significantChange = digestClassFile(output, bytes);
if (significantChange) {
// find all sources that reference this type and put them into work queue
addDependentsOf(CharOperation.toString(classFile.getCompoundName()));
}
final BufferedOutputStream os = new BufferedOutputStream(output.newOutputStream());
try {
os.write(bytes);
os.flush();
} finally {
os.close();
}
}
private boolean digestClassFile(DefaultOutput output, byte[] definition) {
boolean significantChange = true;
try {
ClassFileReader reader =
new ClassFileReader(definition, output.getResource().getAbsolutePath().toCharArray());
byte[] hash = digester.digest(reader);
if (hash != null) {
byte[] oldHash = (byte[]) output.setValue(ATTR_CLASS_DIGEST, hash);
significantChange = oldHash == null || !Arrays.equals(hash, oldHash);
}
} catch (ClassFormatException e) {
// ignore this class
}
return significantChange;
}
private void addDependentsOf(String typeOrPackage) {
if (typeOrPackage != null) {
// adopted from org.eclipse.jdt.internal.core.builder.IncrementalImageBuilder.addDependentsOf
// TODO deal with package-info
int idx = typeOrPackage.indexOf('.');
if (idx > 0) {
rootNames.add(typeOrPackage.substring(0, idx));
idx = typeOrPackage.lastIndexOf('.');
qualifiedNames.add(typeOrPackage.substring(0, idx));
simpleNames.add(typeOrPackage.substring(idx + 1));
} else {
rootNames.add(typeOrPackage);
simpleNames.add(typeOrPackage);
}
}
}
@Override
public void skipCompilation() {
// unlike javac, jdt compiler tracks input-output association
// this allows BuildContext to automatically carry-over output metadata
}
}