2013-08-03 203 views
1

我正在嘗試編寫註釋Procssor來檢測使用@PrintMethod註釋進行註釋的方法。例如在下面的測試類中,我想打印測試方法中的代碼。有沒有辦法做到這一點?自定義註釋處理器 - 使用註釋檢測方法

從下面所述的AnnotationProcessor類中,我只能獲取方法名稱,而不能獲取方法的細節。

測試類

public class test { 

    public static void main(String[] args) { 
     System.out.println("Args"); 
    } 

    @PrintMethod 
    private boolean testMethod(String input) { 
     if(input!=null) { 
      return true; 
     } 
     return false; 
    } 
} 

註解處理器類

public class AnnotationProcessor extends AbstractProcessor { 
//...... 
    @Override 
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 
     //retrieve test Anntoation 
     Set<? extends Element> ann =roundEnv.getElementsAnnotatedWith(PrintMethod.class); 

     //Print the Method Name 
     for(Element e: ann) { 
      String msg="Element ee :"+ee.getSimpleName().toString(); 
      processingEnv.getMessager().printMessage(javax.tools.Diagnostic.Kind.ERROR, msg, e); 
     } 
    } 
} 

回答

1

我很好奇,這也讓我決定嘗試弄明白。結果比我預想的要容易。您所需要做的就是利用專有tools.jar庫中的Trees api。我做了沿着這裏這些線快速標註處理器:https://github.com/johncarl81/printMethod

下面是它的肉:

@SupportedSourceVersion(SourceVersion.RELEASE_6) 
@SupportedAnnotationTypes("org.printMethod.PrintMethod") 
public class PrintMethodAnnotationProcessor extends AbstractProcessor { 

    private Trees trees; 

    @Override 
    public synchronized void init(ProcessingEnvironment processingEnv) { 
     super.init(processingEnv); 
     trees = Trees.instance(processingEnv); //initialize the Trees api. 
    } 

    @Override 
    public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) { 

     MethodPrintScanner visitor = new MethodPrintScanner(); 

     for (Element e : roundEnvironment.getElementsAnnotatedWith(PrintMethod.class)) { 
      TreePath tp = trees.getPath(e); 
      // visit the annotated methods 
      visitor.scan(tp, trees); 
     } 
     return true; 
    } 

    @Override 
    public SourceVersion getSupportedSourceVersion() { 
     return SourceVersion.latestSupported(); 
    } 
} 

而且MethodPrintScanner

public class MethodPrintScanner extends TreePathScanner { 

    @Override 
    public Object visitMethod(MethodTree methodTree, Object o) { 
     System.out.println(methodTree); 
     return null; 
    } 
} 

你可以看到,我們能夠請訪問與給定註釋元素關聯的TreePath。對於每種方法,我們只需println()methodTree它給我們的方法的內容。

使用你的例子,這裏的編譯過程中程序的輸出:

@PrintMethod() 
private boolean testMethod(String input) { 
    if (input != null) { 
     return true; 
    } 
    return false; 
} 
0

這是一兩件事,使之在你的IDE工作。但是,一旦你的代碼被打包到jar文件中,它就是另一種檢測。以下代碼可以同時管理。

public static List<Class> getPackageClassListHavingAnnotation(String pPackageName, 
                   Class<? extends Annotation> pAnnotation) throws Exception 
{ 
    try 
    { 
    List<Class> classList = getPackageClassList(pPackageName); 
    if ((pAnnotation == null) || (classList == null)) return classList; 

    List<Class> resultList = new ArrayList<Class>(classList.size()); 

    outerLoop: 
    for (Class clazz : classList) 
    { 
     try 
     { 
     for (Method method : clazz.getMethods()) 
     { 
      if (method.isAnnotationPresent(pAnnotation)) 
      { 
      resultList.add(clazz); 
      continue outerLoop; 
      } 
     } 
     } 
     catch (Throwable e) 
     { 
     } 
    } 
    return (resultList.isEmpty()) ? null : resultList; 
    } 
    catch (Exception e) 
    { 
    return null; 
    } 
} 

它需要以下輔助方法:

public static List<Class> getPackageClassList(String pPackageName) throws Exception 
{ 
    try 
    { 
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 
    String path = pPackageName.replace('.', '/'); 

    List<File> dirs = new ArrayList<File>(); 
    List<JarFile> jars = new ArrayList<JarFile>(); 
    Enumeration<URL> resources = classLoader.getResources(path); 
    if (resources != null) 
    { 
     String fileName; 
     URL resource; 
     File file; 
     while (resources.hasMoreElements()) 
     { 
     resource = resources.nextElement(); 
     fileName = resource.getFile(); 

     if (fileName.contains("!")) 
     { 
      // jar file 
      resource = new URL(StringUtil.getArrayFromString(fileName, "!")[0]); 
      file = urlToFile(resource); 
      if (!file.exists()) continue; 
      jars.add(new JarFile(file)); 
     } 
     else 
     { 
      // class file that is not in a jar file 
      file = urlToFile(resource); 
      if (!file.exists()) continue; 
      dirs.add(file); 
     } 
     } 
    } 

    List<Class> resultList = new ArrayList<Class>(1000); 
    List<Class> tmpClassList; 
    for (File directory : dirs) 
    { 
     tmpClassList = getPckDirClassList(directory, pPackageName); 
     if (tmpClassList != null) resultList.addAll(tmpClassList); 
    } 

    for (JarFile jar : jars) 
    { 
     tmpClassList = getPckJarClassList(jar, pPackageName); 
     if (tmpClassList != null) resultList.addAll(tmpClassList); 
    } 

    return (resultList.isEmpty()) ? null : resultList; 
    } 
    catch (Exception e) 
    { 
    return null; 
    } 
} 

private static List<Class> getPckJarClassList(JarFile pJar, String pPackageName) 
{ 
    if ((pJar == null) || (pPackageName == null)) return null; 

    List<Class> resultList = new ArrayList<Class>(100); 

    Enumeration<JarEntry> jarEntries = (pJar.entries()); 
    JarEntry jarEntry; 
    String fullClassName; 
    while (jarEntries.hasMoreElements()) 
    { 
    jarEntry = jarEntries.nextElement(); 
    fullClassName = jarEntry.getName().replaceAll("/", "."); 
    if (!fullClassName.startsWith(pPackageName)) continue; 
    if (!fullClassName.endsWith(".class")) continue; 

    // do not do a Class.forName for the following path, this can crash the server 
    try 
    { 
     resultList.add(Class.forName(fullClassName.substring(0, fullClassName.length() - 6))); 
    } 
    catch (Throwable e) 
    { 
    } 
    } 

    return (resultList.isEmpty()) ? null : resultList; 
} 

/** 
* Recursive method to find all classes in a package directory tree. 
*/ 
private static List<Class> getPckDirClassList(File pDirectory, String pPackageName) throws ClassNotFoundException 
{ 
    try 
    { 
    if ((pDirectory == null) || (pPackageName == null)) return null; 

    if (!pDirectory.exists()) return null; 
    File[] files = pDirectory.listFiles(); 
    if ((files == null) || (files.length == 0)) return null; 

    List<Class> resultList = new ArrayList<Class>(100); 
    List<Class> tmpClassList; 
    for (File file : files) 
    { 
     if (file.isDirectory()) 
     { 
     tmpClassList = getPckDirClassList(file, pPackageName + "." + file.getName()); 
     if (tmpClassList != null) resultList.addAll(tmpClassList); 
     } 
     else if (file.getName().endsWith(".class")) 
     { 
     try 
     { 
      resultList.add(Class.forName(pPackageName + '.' + file.getName().substring(0, file.getName().length() - 6))); 
     } 
     catch (Throwable e) 
     { 
     } 
     } 
    } 
    return (resultList.isEmpty()) ? null : resultList; 
    } 
    catch (Exception e) 
    { 
    return null; 
    } 
} 

此代碼已經對Windows和UNIX系統.jar文件進行測試。它也已經在Windows上使用IntelliJ中的.java文件進行了測試。