package io.takari.maven.plugins.compile.jdt;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Filer;
import javax.annotation.processing.FilerException;
import javax.lang.model.element.Element;
import javax.tools.FileObject;
import javax.tools.ForwardingFileObject;
import javax.tools.ForwardingJavaFileObject;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import org.eclipse.jdt.internal.compiler.apt.model.ElementImpl;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import io.takari.incrementalbuild.Output;
import io.takari.incrementalbuild.Resource;
import io.takari.maven.plugins.compile.AbstractCompileMojo.Proc;
import io.takari.maven.plugins.compile.CompilerBuildContext;
class FilerImpl implements Filer {
private final CompilerBuildContext context;
private final StandardJavaFileManager fileManager;
private final ProcessingEnvImpl processingEnv;
private final CompilerJdt incrementalCompiler;
private final boolean incremental;
private final Set<URI> createdResources = new HashSet<>();
private class FileObjectDelegate {
private final Collection<Resource<File>> inputs;
public FileObjectDelegate(Collection<Resource<File>> inputs) {
this.inputs = inputs;
}
public OutputStream openOutputStream(URI uri) throws IOException {
final Output<File> output = context.processOutput(new File(uri));
for (Resource<File> input : inputs) {
input.associateOutput(output);
}
return new FilterOutputStream(output.newOutputStream()) {
@Override
public void close() throws IOException {
super.close();
onClose(output);
}
};
}
public Writer openWriter(URI uri) throws IOException {
return new OutputStreamWriter(openOutputStream(uri)); // XXX encoding
}
protected void onClose(Output<File> output) {}
}
// TODO EclipseFileObject implementation is quite inappropriate for our needs, consider rewrite
private static class JavaFileObjectImpl extends ForwardingJavaFileObject<JavaFileObject> {
private final FileObjectDelegate delegate;
public JavaFileObjectImpl(JavaFileObject fileObject, FileObjectDelegate delegate) {
super(fileObject);
this.delegate = delegate;
}
@Override
public OutputStream openOutputStream() throws IOException {
return delegate.openOutputStream(toUri());
}
@Override
public Writer openWriter() throws IOException {
return delegate.openWriter(toUri());
}
}
private static class FileObjectImpl extends ForwardingFileObject<FileObject> {
private final FileObjectDelegate delegate;
protected FileObjectImpl(FileObject fileObject, FileObjectDelegate delegate) {
super(fileObject);
this.delegate = delegate;
}
@Override
public OutputStream openOutputStream() throws IOException {
return delegate.openOutputStream(toUri());
}
@Override
public Writer openWriter() throws IOException {
return delegate.openWriter(toUri());
}
}
public FilerImpl(CompilerBuildContext context, StandardJavaFileManager fileManager, CompilerJdt incrementalCompiler, ProcessingEnvImpl processingEnv, Proc proc) {
this.context = context;
this.fileManager = fileManager;
this.incrementalCompiler = incrementalCompiler;
this.processingEnv = processingEnv;
this.incremental = proc == Proc.proc || proc == Proc.only;
}
@Override
public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements) throws IOException {
JavaFileObject sourceFile = fileManager.getJavaFileForOutput(StandardLocation.SOURCE_OUTPUT, name.toString(), JavaFileObject.Kind.SOURCE, null);
if (!createdResources.add(sourceFile.toUri())) {
throw new FilerException("Attempt to recreate file for type " + name);
}
return new JavaFileObjectImpl(sourceFile, new FileObjectDelegate(getInputs(originatingElements)) {
@Override
protected void onClose(Output<File> generatedSource) {
// TODO optimize if the regenerated sources didn't change compared the previous build
CompilationUnit unit = new CompilationUnit(null, generatedSource.getResource().getAbsolutePath(), null /* encoding */);
processingEnv.addNewUnit(unit);
incrementalCompiler.addGeneratedSource(generatedSource);
}
});
}
private Collection<Resource<File>> getInputs(Element[] elements) {
if (incremental && elements.length == 0) {
throw new IllegalArgumentException("originatingElements must be provided during incremental annotation processing.\n " //
+ "fix the annotation processor or use procEX/onlyEX as a workaround");
}
Map<File, Resource<File>> inputs = new HashMap<>();
for (Element element : elements) {
if (!(element instanceof ElementImpl)) {
throw new IllegalArgumentException();
}
Binding binding = ((ElementImpl) element)._binding;
if (binding instanceof SourceTypeBinding) {
File file = new File(new String(((SourceTypeBinding) binding).getFileName()));
inputs.put(file, context.getProcessedSource(file));
}
}
return inputs.values();
}
@Override
public JavaFileObject createClassFile(CharSequence name, Element... originatingElements) throws IOException {
JavaFileObject classFile = fileManager.getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, name.toString(), JavaFileObject.Kind.CLASS, null);
if (!createdResources.add(classFile.toUri())) {
throw new FilerException("Attempt to recreate file for class " + name);
}
return new JavaFileObjectImpl(classFile, new FileObjectDelegate(getInputs(originatingElements)) {
@Override
protected void onClose(Output<File> generatedClass) {
// TODO processingEnv.addNewClassFile
throw new UnsupportedOperationException();
}
});
}
@Override
public FileObject createResource(Location location, CharSequence pkg, CharSequence relativeName, Element... originatingElements) throws IOException {
FileObject file = fileManager.getFileForOutput(location, pkg.toString(), relativeName.toString(), null);
if (!createdResources.add(file.toUri())) {
throw new FilerException("Attempt to recreate file for resource " + pkg + "." + relativeName);
}
return new FileObjectImpl(file, new FileObjectDelegate(getInputs(originatingElements)));
}
@Override
public FileObject getResource(Location location, CharSequence pkg, CharSequence relativeName) throws IOException {
FileObject file = fileManager.getFileForInput(location, pkg.toString(), relativeName.toString());
if (file == null) {
throw new FileNotFoundException("Resource does not exist " + location + '/' + pkg + '/' + relativeName);
}
if (createdResources.contains(file.toUri())) {
throw new FilerException("Resource already created " + pkg + "." + relativeName);
}
return file;
}
public void hardReset() {
this.createdResources.clear();
}
}