ソースを参照

eksperymenty z APT

Ranides Atterwim 13 年 前
コミット
3f135afd2a

+ 2 - 1
pom.xml

@@ -4,7 +4,7 @@
 
     <groupId>net.ranides</groupId>
     <artifactId>assira</artifactId>
-    <version>0.50</version>
+    <version>0.51</version>
     <packaging>jar</packaging>
 
     <name>assira</name>
@@ -25,6 +25,7 @@
                     <source>1.6</source>
                     <target>1.6</target>
                     <compilerArgument>-Xlint:unchecked</compilerArgument>
+                    <compilerArgument>-proc:none</compilerArgument>
                 </configuration>
             </plugin>
         </plugins>

+ 7 - 0
src/main/java/net/ranides/assira/annotations/Meta.java

@@ -205,4 +205,11 @@ public final class Meta {
     public @interface Unsafe {
     }
 
+    @Retention(SOURCE)
+    @Documented
+    @Target(TYPE)
+    public @interface ExportService {
+        Class value() default void.class;
+    }
+
 }

+ 166 - 0
src/main/java/net/ranides/assira/annotations/ProcessorDef.java

@@ -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);
+    }
+    
+}

+ 0 - 4
src/main/java/net/ranides/assira/collection/CollectionUtils.java

@@ -8,13 +8,9 @@
 package net.ranides.assira.collection;
 
 import java.util.AbstractCollection;
-import java.util.AbstractSet;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Iterator;
-import java.util.Set;
 import net.ranides.assira.generic.Function;
 
 /**

+ 20 - 0
src/main/java/net/ranides/assira/collection/ListUtils.java

@@ -80,6 +80,15 @@ public final class ListUtils {
         if(null == values) { return null; }
         return values.isEmpty() ? null : values.get(0);
     }
+    
+    public static Object first(Object values) {
+        if(values instanceof List) {
+            List list = (List)values;
+            return list.isEmpty() ? null : list.get(0);
+        } else {
+            return null;
+        }
+    }
 
 
     /**
@@ -111,6 +120,17 @@ public final class ListUtils {
         if(null == values || values.isEmpty()) { return null; }
         return values.get(values.size()-1);
     }
+    
+    public static Object last(Object values) {
+        if(values instanceof List) {
+            List list = (List)values;
+            return list.isEmpty() ? null : list.get(list.size()-1);
+        } else {
+            return null;
+        }
+    }
+    
+        
 
     /**
      * Zwraca rozmiar listy.

+ 7 - 1
src/main/java/net/ranides/assira/collection/map/MultiMap.java

@@ -15,8 +15,14 @@ import java.util.*;
  * @author ranides
  * @param <K>
  * @param <V> 
+ * 
+ * @todo (ranides # 1) multimap nie powinien się opierać na List<V>, ale na Collection<V>
+ * implementacje na 
+ *      - HashMap<Set<V>>
+ *      - HashMap<List<V>>
+ *      - HashMap<LinkedHashSet<V>>
  */
-public interface MultiMap<K,V> extends Map<K,List<V>> {
+public interface MultiMap<K,V> extends Map<K, List<V>> {
         
     /**
      * Sprawdza, czy podana wartość została conajmniej raz przypisana 

+ 1 - 0
src/main/resources/META-INF/services/javax.annotation.processing.Processor

@@ -0,0 +1 @@
+net.ranides.assira.annotations.ProcessorDef