|
|
@@ -0,0 +1,166 @@
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+package net.ranides.assira.annotations;
|
|
|
+
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.FileNotFoundException;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.io.OutputStreamWriter;
|
|
|
+import java.io.PrintWriter;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import javax.annotation.processing.AbstractProcessor;
|
|
|
+import javax.annotation.processing.Filer;
|
|
|
+import javax.annotation.processing.RoundEnvironment;
|
|
|
+import javax.lang.model.element.Element;
|
|
|
+import javax.lang.model.element.TypeElement;
|
|
|
+import javax.lang.model.type.DeclaredType;
|
|
|
+import javax.lang.model.type.MirroredTypeException;
|
|
|
+import javax.lang.model.type.TypeKind;
|
|
|
+import javax.lang.model.type.TypeMirror;
|
|
|
+import javax.lang.model.util.Elements;
|
|
|
+import javax.tools.Diagnostic.Kind;
|
|
|
+import javax.tools.FileObject;
|
|
|
+import javax.tools.StandardLocation;
|
|
|
+import net.ranides.assira.collection.SingleCollection;
|
|
|
+import net.ranides.assira.collection.map.MultiHashMap;
|
|
|
+import net.ranides.assira.collection.map.MultiMap;
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @author ranides
|
|
|
+ */
|
|
|
+public class ProcessorDef extends AbstractProcessor {
|
|
|
+
|
|
|
+ private static final Set<String> SUPPORTED = new SingleCollection<String>(Meta.ExportService.class.getName());
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<String> getSupportedAnnotationTypes() {
|
|
|
+ return SUPPORTED;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
|
|
+ if ( roundEnv.processingOver() ) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ MultiMap<String, String> services = new MultiHashMap<String, String>();
|
|
|
+
|
|
|
+ Elements elements = processingEnv.getElementUtils();
|
|
|
+
|
|
|
+ // discover services from the current compilation sources
|
|
|
+ for (Element element : roundEnv.getElementsAnnotatedWith(Meta.ExportService.class)) {
|
|
|
+ Meta.ExportService info = element.getAnnotation(Meta.ExportService.class);
|
|
|
+ if(info==null) {
|
|
|
+ continue; // shouldn't happen - ignore
|
|
|
+ }
|
|
|
+ if ( !element.getKind().isClass() && !element.getKind().isInterface()) {
|
|
|
+ continue; // shouldn't happen - ignore
|
|
|
+ }
|
|
|
+
|
|
|
+ TypeElement type = (TypeElement)element;
|
|
|
+ TypeElement contract = getContract(type, info);
|
|
|
+ if(contract==null) {
|
|
|
+ continue; // error should have already been reported
|
|
|
+ }
|
|
|
+
|
|
|
+ String cn = elements.getBinaryName(contract).toString();
|
|
|
+ String tn = elements.getBinaryName(type).toString();
|
|
|
+ services.putItem(cn, tn);
|
|
|
+ }
|
|
|
+
|
|
|
+ // also load up any existing values, since this compilation may be partial
|
|
|
+ Filer filer = processingEnv.getFiler();
|
|
|
+ for (Map.Entry<String,List<String>> e : services.entrySet()) {
|
|
|
+ try {
|
|
|
+ String contract = e.getKey();
|
|
|
+ List<String> target = e.getValue();
|
|
|
+
|
|
|
+ FileObject file = filer.getResource(StandardLocation.CLASS_OUTPUT, "", "META-INF/services/" +contract);
|
|
|
+ BufferedReader reader = new BufferedReader(new InputStreamReader(file.openInputStream(), "UTF-8"));
|
|
|
+ String line;
|
|
|
+ while( null!=(line=reader.readLine()) ) { target.add(line); }
|
|
|
+ reader.close();
|
|
|
+
|
|
|
+ } catch (FileNotFoundException x) {
|
|
|
+ // doesn't exist
|
|
|
+ } catch (IOException x) {
|
|
|
+ processingEnv.getMessager().printMessage(Kind.ERROR,"Failed to load existing service definition files: "+x);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // now write them back out
|
|
|
+ for (Map.Entry<String,List<String>> e : services.entrySet()) {
|
|
|
+ try {
|
|
|
+ String contract = e.getKey();
|
|
|
+ List<String> classes = e.getValue();
|
|
|
+ processingEnv.getMessager().printMessage(Kind.NOTE,"Writing META-INF/services/"+contract);
|
|
|
+
|
|
|
+ FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", "META-INF/services/" +contract);
|
|
|
+ PrintWriter writer = new PrintWriter(new OutputStreamWriter(file.openOutputStream(), "UTF-8"));
|
|
|
+ for (String value : classes) {
|
|
|
+ writer.println(value);
|
|
|
+ }
|
|
|
+ writer.close();
|
|
|
+
|
|
|
+ } catch (IOException x) {
|
|
|
+ processingEnv.getMessager().printMessage(Kind.ERROR,"Failed to write service definition files: "+x);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private TypeElement getContract(TypeElement type, Meta.ExportService a) {
|
|
|
+ // explicitly specified?
|
|
|
+ try {
|
|
|
+ a.value();
|
|
|
+ throw new AssertionError();
|
|
|
+ } catch (MirroredTypeException e) {
|
|
|
+ TypeMirror m = e.getTypeMirror();
|
|
|
+ if (m.getKind()== TypeKind.VOID) {
|
|
|
+ // contract inferred from the signature
|
|
|
+ boolean hasBaseClass = type.getSuperclass().getKind()!=TypeKind.NONE && !isObject(type.getSuperclass());
|
|
|
+ boolean hasInterfaces = !type.getInterfaces().isEmpty();
|
|
|
+ if(hasBaseClass^hasInterfaces) {
|
|
|
+ if(hasBaseClass)
|
|
|
+ return (TypeElement)((DeclaredType)type.getSuperclass()).asElement();
|
|
|
+ return (TypeElement)((DeclaredType)type.getInterfaces().get(0)).asElement();
|
|
|
+ }
|
|
|
+
|
|
|
+ error(type, "Contract type was not specified, but it couldn't be inferred.");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m instanceof DeclaredType) {
|
|
|
+ DeclaredType dt = (DeclaredType) m;
|
|
|
+ return (TypeElement)dt.asElement();
|
|
|
+ } else {
|
|
|
+ error(type, "Invalid type specified as the contract");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isObject(TypeMirror t) {
|
|
|
+ if (t instanceof DeclaredType) {
|
|
|
+ DeclaredType dt = (DeclaredType) t;
|
|
|
+ return((TypeElement)dt.asElement()).getQualifiedName().toString().equals("java.lang.Object");
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void error(Element source, String msg) {
|
|
|
+ processingEnv.getMessager().printMessage(Kind.ERROR,msg,source);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|