package lombok.core;
import static lombok.core.Augments.ClassLoader_lombokAlreadyAddedTo;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import lombok.patcher.ClassRootFinder;
import lombok.permit.Permit;
@SupportedAnnotationTypes("*")
public class AnnotationProcessor extends AbstractProcessor {
private static String trace(Throwable t) {
StringWriter w = new StringWriter();
t.printStackTrace(new PrintWriter(w, true));
return w.toString();
}
static abstract class ProcessorDescriptor {
abstract boolean want(ProcessingEnvironment procEnv, List<String> delayedWarnings);
abstract String getName();
abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
}
private final List<ProcessorDescriptor> registered = Arrays.asList(new JavacDescriptor(), new EcjDescriptor());
private final List<ProcessorDescriptor> active = new ArrayList<ProcessorDescriptor>();
private final List<String> delayedWarnings = new ArrayList<String>();
public static ProcessingEnvironment getJavacProcessingEnvironment(ProcessingEnvironment procEnv, List<String> delayedWarnings) {
return tryRecursivelyObtainJavacProcessingEnvironment(procEnv);
}
private static ProcessingEnvironment tryRecursivelyObtainJavacProcessingEnvironment(ProcessingEnvironment procEnv) {
if (procEnv.getClass().getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment")) {
return procEnv;
}
for (Class<?> procEnvClass = procEnv.getClass(); procEnvClass != null; procEnvClass = procEnvClass.getSuperclass()) {
try {
Object delegate = tryGetDelegateField(procEnvClass, procEnv);
if (delegate == null) delegate = tryGetProcessingEnvField(procEnvClass, procEnv);
if (delegate == null) delegate = tryGetProxyDelegateToField(procEnvClass, procEnv);
if (delegate != null) return tryRecursivelyObtainJavacProcessingEnvironment((ProcessingEnvironment) delegate);
} catch (final Exception e) {
}
}
return null;
}
private static Object tryGetDelegateField(Class<?> delegateClass, Object instance) {
try {
return Permit.getField(delegateClass, "delegate").get(instance);
} catch (Exception e) {
return null;
}
}
private static Object tryGetProcessingEnvField(Class<?> delegateClass, Object instance) {
try {
return Permit.getField(delegateClass, "processingEnv").get(instance);
} catch (Exception e) {
return null;
}
}
private static Object tryGetProxyDelegateToField(Class<?> delegateClass, Object instance) {
try {
InvocationHandler handler = Proxy.getInvocationHandler(instance);
return Permit.getField(handler.getClass(), "val$delegateTo").get(handler);
} catch (Exception e) {
return null;
}
}
static class JavacDescriptor extends ProcessorDescriptor {
private Processor processor;
@Override String getName() {
return "OpenJDK javac";
}
@Override boolean want(ProcessingEnvironment procEnv, List<String> delayedWarnings) {
if (procEnv.getClass().getName().startsWith("org.eclipse.jdt.")) return false;
ProcessingEnvironment javacProcEnv = getJavacProcessingEnvironment(procEnv, delayedWarnings);
if (javacProcEnv == null) return false;
try {
ClassLoader classLoader = findAndPatchClassLoader(javacProcEnv);
processor = (Processor) Class.forName("lombok.javac.apt.LombokProcessor", false, classLoader).getConstructor().newInstance();
} catch (Exception e) {
delayedWarnings.add("You found a bug in lombok; lombok.javac.apt.LombokProcessor is not available. Lombok will not run during this compilation: " + trace(e));
return false;
} catch (NoClassDefFoundError e) {
delayedWarnings.add("Can't load javac processor due to (most likely) a class loader problem: " + trace(e));
return false;
}
try {
processor.init(procEnv);
} catch (Exception e) {
delayedWarnings.add("lombok.javac.apt.LombokProcessor could not be initialized. Lombok will not run during this compilation: " + trace(e));
return false;
} catch (NoClassDefFoundError e) {
delayedWarnings.add("Can't initialize javac processor due to (most likely) a class loader problem: " + trace(e));
return false;
}
return true;
}
private ClassLoader findAndPatchClassLoader(ProcessingEnvironment procEnv) throws Exception {
ClassLoader environmentClassLoader = procEnv.getClass().getClassLoader();
if (environmentClassLoader != null && environmentClassLoader.getClass().getCanonicalName().equals("org.codehaus.plexus.compiler.javac.IsolatedClassLoader")) {
if (!ClassLoader_lombokAlreadyAddedTo.getAndSet(environmentClassLoader, true)) {
Method m = Permit.getMethod(environmentClassLoader.getClass(), "addURL", URL.class);
URL selfUrl = new File(ClassRootFinder.findClassRootOfClass(AnnotationProcessor.class)).toURI().toURL();
m.invoke(environmentClassLoader, selfUrl);
}
}
ClassLoader ourClassLoader = JavacDescriptor.class.getClassLoader();
if (ourClassLoader == null) return ClassLoader.getSystemClassLoader();
return ourClassLoader;
}
@Override boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return processor.process(annotations, roundEnv);
}
}
static class EcjDescriptor extends ProcessorDescriptor {
@Override String getName() {
return "ECJ";
}
@Override boolean want(ProcessingEnvironment procEnv, List<String> delayedWarnings) {
if (!procEnv.getClass().getName().startsWith("org.eclipse.jdt.")) return false;
return true;
}
@Override boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
@Override public void init(ProcessingEnvironment procEnv) {
super.init(procEnv);
for (ProcessorDescriptor proc : registered) {
if (proc.want(procEnv, delayedWarnings)) active.add(proc);
}
if (active.isEmpty() && delayedWarnings.isEmpty()) {
StringBuilder supported = new StringBuilder();
for (ProcessorDescriptor proc : registered) {
if (supported.length() > 0) supported.append(", ");
supported.append(proc.getName());
}
procEnv.getMessager().printMessage(Kind.WARNING, String.format("You aren't using a compiler supported by lombok, so lombok will not work and has been disabled.\n" +
"Your processor is: %s\nLombok supports: %s", procEnv.getClass().getName(), supported));
}
}
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!delayedWarnings.isEmpty()) {
Set<? extends Element> rootElements = roundEnv.getRootElements();
if (!rootElements.isEmpty()) {
Element firstRoot = rootElements.iterator().next();
for (String warning : delayedWarnings) processingEnv.getMessager().printMessage(Kind.WARNING, warning, firstRoot);
delayedWarnings.clear();
}
}
for (ProcessorDescriptor proc : active) proc.process(annotations, roundEnv);
boolean onlyLombok = true;
boolean zeroElems = true;
for (TypeElement elem : annotations) {
zeroElems = false;
Name n = elem.getQualifiedName();
if (n.toString().startsWith("lombok.")) continue;
onlyLombok = false;
}
return onlyLombok && !zeroElems;
}
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.values()[SourceVersion.values().length - 1];
}
}