Pregunta

I am trying to write an annotation Procssor to detect the methods that are annotated with the @PrintMethod annotation. For example in the test Class below, i want to print the codes within the test Method. Is there a way to do it?

From the AnnotationProcessor class stated below, i am only able get the method name but not the details of the method.

Test Class

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; 
    }
}

Annotation Processor Class

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);
        }
    }
}
¿Fue útil?

Solución

I was curious about this too so I decided to try and figure it out. Turns out to be easier than I expected. All you need to do is leverage the Trees api out of the proprietary tools.jar library. I've made a quick annotation processor along these lines here: https://github.com/johncarl81/printMethod

Here's the meat of it:

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

And the MethodPrintScanner:

public class MethodPrintScanner extends TreePathScanner {

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

You can see that we are able to visit the TreePath associated with the given annotated Element. For each method, we simply println() the methodTree which gives us the contents of the method.

Using your example, here's the output of the program during compilation:

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

Otros consejos

It's one thing to make it work in your IDE. But it's another to detect them once your code is packed inside jar files. The following code can manage both.

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;
  }
}

It requires the following helper methods:

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;
  }
}

This code has been tested with .jar files on both windows and unix systems. It has also been tested with .java files in IntelliJ on windows.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top