package xapi.dev.processor;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.FilerException;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import xapi.util.api.Pair;
import xapi.util.impl.AbstractPair;
import xapi.util.impl.PairBuilder;
/**
* This is the annotation processor for our injection library.
*
* It scans for our injection annotations and writes manifests to speed up injection.
* default implementations go in META-INF/instances or META-INF/singletons.
*
* Platform specific injection types go into META-INF/$type/instances, META-INF/$type/singletons.
*
* It is included in the core api because it is run on the core api;
* every jar built will include these manifests, whether they are used at runtime or not.
*
* Gwt injection does not look at the manifests, and a bytecode enhancer is
* in the works to further process jre environments, by replacing calls to X_Inject
* with direct access of the injected type.
*
* Final builds (bound to pre-deploy) can also be further enhanced,
* by replacing all references to an injected interface with it's replacement type
* (changing all class references, and changing invokeinterface to invokespecial).
* This may be unnecessary once javac optimize=true after replacing the X_Inject call site.
*
*
* @author "James X. Nelson (james@wetheinter.net)"
*
*/
@SupportedAnnotationTypes({"xapi.annotation.inject.*"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class InjectionAnnotationProcessor extends AbstractProcessor{
protected static class PlatformPair extends AbstractPair<String, Class<?>> {}
protected static class ManifestWriter {
HashMap<String, Pair<Integer, String>> singletons = new HashMap<String, Pair<Integer, String>>();
HashMap<String, Pair<Integer, String>> instances = new HashMap<String, Pair<Integer, String>>();
void writeSingleton(final String iface, final String platform, final Integer priority, final String element) {
if (priority == null) {
if (!singletons.containsKey(iface)) {
singletons.put(iface, PairBuilder.pairOf(priority, element));
}
} else {
final Pair<Integer, String> existing = singletons.get(iface);
if (
existing == null ||
existing.get0() == null ||
existing.get0() < priority
) {
singletons.put(iface, PairBuilder.pairOf(priority, element));
}
}
}
void writeInstance(final String iface, final String platform, final Integer priority, final String element) {
if (priority == null) {
if (!instances.containsKey(iface)) {
instances.put(iface, PairBuilder.pairOf(priority, element));
}
} else {
final Pair<Integer, String> existing = instances.get(iface);
if (
existing == null ||
existing.get0() == null ||
existing.get0() < priority
) {
instances.put(iface, PairBuilder.pairOf(priority, element));
}
}
}
void commit(final Filer filer) throws IOException {
for (final String iface : singletons.keySet()) {
final String impl = singletons.get(iface).get1();
writeTo("singletons",iface, impl, filer);
}
for (final String iface : instances.keySet()) {
final String impl = instances.get(iface).get1();
writeTo("instances",iface, impl, filer);
}
}
protected void writeTo(final String location, final String iface, final String impl, final Filer filer) throws IOException {
CharSequence existing;
final String manifest = "META-INF/" + location+ "/" + iface;
try {
existing = filer.getResource(StandardLocation.CLASS_OUTPUT, "", manifest).getCharContent(true);
if (!impl.contentEquals(existing)) {
System.out.println("Cannot overwrite existing " +location+" injection target.\n" +
"Tried: "+iface+" -> "+impl+"\n" +
"but existing manifest has: "+existing);
}
} catch (final FilerException ignored) {
// Re-opening the file for reading or after writing is ignorable
} catch (final IOException e) {
// File does not exist, just create one.
final FileObject res = filer.createResource(StandardLocation.CLASS_OUTPUT, "",manifest);
final OutputStream out = res.openOutputStream();
out.write(impl.getBytes());
out.close();
}
}
}
public InjectionAnnotationProcessor() {}
protected Filer filer;
protected ManifestWriter writer;
@Override
public final synchronized void init(final ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
writer = initWriter(processingEnv);
}
protected ManifestWriter initWriter(final ProcessingEnvironment processingEnv) {
return new ManifestWriter();
}
@Override
public boolean process(
final Set<? extends TypeElement> annotations,
final RoundEnvironment roundEnv
) {
final Elements elements = processingEnv.getElementUtils();
for (final TypeElement anno : annotations) {
final ExecutableElement implFor = extractImplFor(anno);
final ExecutableElement priorityFor = extractPriorityFor(anno);
for (final Element element : roundEnv.getElementsAnnotatedWith(anno)) {
final AnnotationMirror mirror = getMirror(element, anno);
final AnnotationValue t =
elements.getElementValuesWithDefaults(mirror)
.get(implFor);
if (!(t.getValue() instanceof DeclaredType)) {
// TODO investigate why the value is sometimes a string and react accordingly
continue;
}
final DeclaredType cls = (DeclaredType) t.getValue();
final Integer priority = getPriority(elements, mirror, priorityFor);
if (anno.getSimpleName().contentEquals("SingletonDefault")) {
for (final String platform : getPlatforms(element)) {
writer.writeSingleton(cls.toString(), platform, null, element.toString());
}
} else if (anno.getSimpleName().contentEquals("SingletonOverride")) {
for (final String platform : getPlatforms(element)) {
writer.writeSingleton(cls.toString(), platform, priority, element.toString());
}
} else if (anno.getSimpleName().contentEquals("InstanceDefault")) {
for (final String platform : getPlatforms(element)) {
writer.writeInstance(cls.toString(), platform, null, element.toString());
}
} else if (anno.getSimpleName().contentEquals("InstanceOverride")) {
for (final String platform : getPlatforms(element)) {
writer.writeInstance(cls.toString(), platform, priority, element.toString());
}
}
}
}
if (roundEnv.processingOver()) {
try {
writer.commit(filer);
} catch (final Exception e) {
e.printStackTrace();
System.err.println("Unable to write injection metadata.");
return false;
}
}
return true;
}
private Integer getPriority(final Elements elements, final AnnotationMirror mirror,
final ExecutableElement priorityFor) {
if (priorityFor == null)
{
return null; // *Default annos
}
return (Integer)elements.getElementValuesWithDefaults(mirror)
.get(priorityFor)
.getValue();
}
private AnnotationMirror getMirror(final Element element, final TypeElement type) {
for (final AnnotationMirror mirror : element.getAnnotationMirrors()) {
if (mirror.getAnnotationType().toString().equals(type.toString())) {
return mirror;
}
}
throw new RuntimeException("Element "+element+" did not contain annotation "+type);
}
private ExecutableElement extractImplFor(final TypeElement anno) {
for (final ExecutableElement e : ElementFilter.methodsIn(anno.getEnclosedElements())) {
if (e.getSimpleName().toString().equals("implFor")){
return e;
}
}
throw new RuntimeException("Annotation "+anno+" does not contain an implFor() method.\n" +
"Available methods: "+anno.getEnclosedElements());
}
private ExecutableElement extractPriorityFor(final TypeElement anno) {
for (final ExecutableElement e : ElementFilter.methodsIn(anno.getEnclosedElements())) {
if (e.getSimpleName().toString().equals("priority")){
return e;
}
}
return null; // Default annos don't have priority
}
protected Iterable<String> getPlatforms(final Element element) {
return Arrays.asList("");
}
void dumpType(final Element anno) {
processingEnv.getElementUtils().printElements(new PrintWriter(System.out), anno);
}
}