package restx.common.processor;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.samskivert.mustache.Template;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Set;
/**
* Date: 1/3/14
* Time: 07:50
*/
public abstract class RestxAbstractProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
return processImpl(annotations, roundEnv);
} catch (Exception e) {
// We don't allow exceptions of any kind to propagate to the compiler
fatalError("", e);
return true;
}
}
protected abstract boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) throws Exception;
protected void log(String msg) {
if (processingEnv.getOptions().containsKey("debug")) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
}
}
protected void error(String msg, Element element) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element);
}
protected void warn(String msg, Element element) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, msg, element);
}
protected void error(String msg, Element element, AnnotationMirror annotation) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element, annotation);
}
protected void fatalError(String msg) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR: " + msg);
}
protected void fatalError(String msg, Exception e) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR: " + msg + " " + writer);
}
protected void fatalError(String msg, Exception e, Element element) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR: " + msg + " " + writer, element);
}
protected PackageElement getPackage(TypeElement typeElement) {
Element el = typeElement.getEnclosingElement();
while (el != null) {
if (el instanceof PackageElement) {
return (PackageElement) el;
}
el = el.getEnclosingElement();
}
throw new IllegalStateException("no package for " + typeElement);
}
protected TypeElement asTypeElement(TypeMirror typeMirror) {
Types TypeUtils = this.processingEnv.getTypeUtils();
return (TypeElement)TypeUtils.asElement(typeMirror);
}
protected void generateJavaClass(String className, Template mustache, ImmutableMap<String, Object> ctx,
Element originatingElements) throws IOException {
generateJavaClass(className, mustache, ctx, ImmutableSet.of(originatingElements));
}
protected void generateJavaClass(String className, Template mustache, ImmutableMap<String, ? extends Object> ctx,
Set<Element> originatingElements) throws IOException {
JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(className,
Iterables.toArray(originatingElements, Element.class));
try (Writer writer = fileObject.openWriter()) {
mustache.execute(ctx, writer);
}
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
/**
* Abstract class to manage resource file generation, it permits to read existing content using the
* {@link #processing()} method, which will call the abstract {@link #readContent(java.io.Reader)} method.
* And the method {@link #generate()} need to be called once we want to generate the resource, during this process
* the {@link #writeContent(java.io.Writer)} method will be called.
*/
protected abstract class ResourceDeclaration {
private final String targetFilePath;
private FileObject fileObject;
/**
* @param targetFilePath path of the resource to write
*/
protected ResourceDeclaration(String targetFilePath) {
this.targetFilePath = targetFilePath;
}
/**
* @return true if the resource need to be generated (for example, it permits to skip empty contents)
*/
protected abstract boolean requireGeneration();
/**
* called once the file has been written
*/
protected abstract void clearContent();
/**
* Writes the resource content.
*
* @param writer the writer to use
* @throws IOException if an I/O error occurs
*/
protected abstract void writeContent(Writer writer) throws IOException;
/**
* Reads the resource content.
*
* @param reader the reader to use
* @throws IOException if an I/O error occurs
*/
protected abstract void readContent(Reader reader) throws IOException;
public void generate() throws IOException {
if (!requireGeneration()) {
return;
}
writeResourceFile(targetFilePath);
clearContent();
fileObject = null;
}
public void processing() throws IOException {
readExistingResourceIfExists(targetFilePath);
}
private void writeResourceFile(String targetFile) throws IOException {
if (fileObject != null
&& fileObject.getClass().getSimpleName().equals("EclipseFileObject")
) {
// eclipse does not allow to do a createResource for a fileobject already obtained via getResource
// but the file object can be used for writing, so it's ok to reuse it in this case
// see source code at:
// https://github.com/eclipse/eclipse.jdt.core/blob/master/org.eclipse.jdt.compiler.apt/src/org/eclipse/jdt/internal/compiler/apt/dispatch/BatchProcessingEnvImpl.java
// https://github.com/eclipse/eclipse.jdt.core/blob/master/org.eclipse.jdt.compiler.tool/src/org/eclipse/jdt/internal/compiler/tool/EclipseFileObject.java
} else {
try {
fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", targetFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
try (Writer writer = fileObject.openWriter()) {
writeContent(writer);
}
}
private void readExistingResourceIfExists(String targetFile) throws IOException {
try {
if (fileObject == null) {
fileObject = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", targetFile);
}
try (Reader r = fileObject.openReader(true)) {
readContent(r);
}
} catch (FileNotFoundException ex) {
/*
This is a very strange behaviour of javac during incremantal compilation (at least experienced
with Intellij make process): a FileNotFoundException is raised while the file actually exist.
"Fortunately" the exception message is the path of the file, so we can try to load it using
plain java.io
*/
try {
File file = new File(ex.getMessage());
if (file.exists()) {
try (Reader r = new FileReader(file)) {
readContent(r);
} catch (IOException e) {
// ignore
}
}
} catch (Exception e) {
// ignore
}
} catch (IOException | IllegalArgumentException ex) {
// ignore
}
}
}
}