aboutsummaryrefslogtreecommitdiff
path: root/src/lombok/apt/AnnotationTransponder.java
blob: 01a79c12def79a8e56619bb2c9a387d6bb4f03d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package lombok.apt;

import static lombok.apt.PKG.CURRENT_SUPPORT;
import static lombok.apt.PKG.isInstanceOf;
import static lombok.apt.PKG.readResource;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;

/**
 * Responsible for redirecting the need to handle an annotation to a class that knows how to handle a given annotation type in a given compiler environment.
 * Will dynamically locate a class in this package using the naming pattern: "HandleFoo_compilerType", e.g. "HandleGetter_ecj".
 * Responsible for injecting the proper class into the right classloader so that it has open access to the classes required to inspect the live AST and
 * modify it so that annotations can cause changes to the live AST.
 * 
 * @author rzwitserloot
 * 
 * @param <T> The annotation class that this transponder should handle (example: Getter.class).
 */
public class AnnotationTransponder<T extends Annotation> {
	private HandlerForCompiler<T> impl;
	private final ProcessingEnvironment processEnv;
	private final RoundEnvironment roundEnv;
	private String error;
	
	@SuppressWarnings("unchecked")
	private void createInstance(Class<T> annotation, ClassLoader loader, String compilerType) {
		try {
			if ( loader == null ) loader = AnnotationTransponder.class.getClassLoader();
			Class<?> implClass;
			try {
				implClass = loader.loadClass(String.format(
						"org.javanext.apt.Handle%s_%s", annotation.getSimpleName(), compilerType));
			} catch ( ClassNotFoundException e ) {
				implClass = loader.loadClass(String.format("lombok.apt.HandleANY_%s", compilerType));
			}
			
			Constructor<?> constructor;
			
			constructor = implClass.getDeclaredConstructor();
			constructor.setAccessible(true);
			impl = (HandlerForCompiler<T>)constructor.newInstance();
			impl.processEnv = processEnv;
			impl.roundEnv = roundEnv;
			try {
				impl.init();
			} catch ( Exception e ) {
				error = "Exception initializing handler: " + e;
				impl = null;
			}
		} catch ( Exception e ) {
			e.printStackTrace();
			error = "You are using " + compilerType + " but a version that's changed the compiler internals. I can't work with it.";
		}
	}
	
	public AnnotationTransponder(Class<T> annotation, RoundEnvironment roundEnv, ProcessingEnvironment processEnv) {
		this.processEnv = processEnv;
		this.roundEnv = roundEnv;
		if ( isInstanceOf(processEnv, "com.sun.tools.javac.processing.JavacProcessingEnvironment") ) {
			createInstance(annotation, null, "javac");
		} else if ( isInstanceOf(processEnv, "org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeBuildProcessingEnvImpl") ) {
			final ClassLoader[] parentLoaders =
				new ClassLoader[] { processEnv.getClass().getClassLoader(), AnnotationTransponder.class.getClassLoader() };
			
			ClassLoader loader = new ClassLoader() {
				@Override public Class<?> findClass(String name) throws ClassNotFoundException {
					if ( name.equals(HandlerForCompiler.class.getName()) ) return HandlerForCompiler.class;
					if ( name.startsWith(AnnotationTransponder.class.getPackage().getName()) ) {
						byte[] data = readResource(name.replace(".", "/") + ".class");
						return defineClass(name, data, 0, data.length);
					}
					for ( int i = 0 ; i < parentLoaders.length ; i++ ) {
						try {
							return parentLoaders[i].loadClass(name);
						} catch ( ClassNotFoundException e ) {
							if ( i == parentLoaders.length -1 ) throw e;
						}
					}
					
					return null;
				}
			};
			
			createInstance(annotation, loader, "ecj");
		} else {
			impl = null;
			this.error = "I cannot work with your compiler. I currently only support " + CURRENT_SUPPORT + ".\n" +
					"This is a: " + processEnv.getClass();
		}
	}
	
	
	public void handle(Element element, T annotation) {
		if ( impl == null ) {
			processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, error, element);
		} else {
			try {
				impl.handle(element, annotation);
			} catch ( Exception e ) {
				e.printStackTrace();
				processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Exception in JavaNext: " + e, element);
			}
		}
	}
}