1

I want to get class level annotation from Java class:

    class FixAnnotation {

        public String[] author;

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Fix {

        public String[] author() default "";

    }

I tried this example of compiled java class:

@Component("test")
@Fix(
    author = {"Example author 1", "Example author 2"}
)
public class Order implements Action {
..
}

But when I try:

public List listLocalFilesAndDirsAllLevels(File baseDir) {

        List<File>  collectedFilesAndDirs = new ArrayList<>();
        Deque<File> remainingDirs = new ArrayDeque<>();

        if(baseDir.exists()) {
            remainingDirs.add(baseDir);

            while(!remainingDirs.isEmpty()) {
                File dir = remainingDirs.removeLast();
                List<File> filesInDir = Arrays.asList(dir.listFiles());
                for(File fileOrDir : filesInDir)  {
                    // We need to process only .class files
                    if(fileOrDir.getName().endsWith(".class")){
                        collectedFilesAndDirs.add(fileOrDir);
                        if(fileOrDir.isDirectory()) {
                            remainingDirs.add(fileOrDir);
                        }
                    }
                }
            }
        }

        return collectedFilesAndDirs;
    }

      List<File> list;

      for(int i=0; i<list.size(); i++) {
            File item = list.get(i);
            System.out.println(item.getName());

            Fix name = item.getClass().getAnnotation(Fix.class);

            out.println("author: " + name.author());
       }

I get NPE. Do you know how I can get the annotation content?

EDIT: I tried this:

public static void main() throws Exception
        {    
            final File folder = new File("/opt/test");
            processAnnotatedFiles(listLocalFilesAndDirsAllLevels(folder));

        }

    public void processAnnotatedFiles(List<File> list) throws IOException, ClassNotFoundException {
        out.println("Directory files size " + list.size());

        for(int i=0; i<list.size(); i++) {
            out.println("File " + list.get(i).getName());

            File file = list.get(i);

            String path = file.getPath();

            String[] authors = getFixFromClassFile(Paths.get(path));
            System.out.println(Arrays.toString(authors));
        }

    }

    public List<File> listLocalFilesAndDirsAllLevels(File baseDir) {

        List<File>  collectedFilesAndDirs = new ArrayList<>();
        Deque<File> remainingDirs = new ArrayDeque<>();

        if(baseDir.exists()) {
            remainingDirs.add(baseDir);

            while(!remainingDirs.isEmpty()) {
                File dir = remainingDirs.removeLast();
                List<File> filesInDir = Arrays.asList(dir.listFiles());
                for(File fileOrDir : filesInDir)  {
                    // We need to process only .class files
                    if(fileOrDir.getName().endsWith(".class")){
                        collectedFilesAndDirs.add(fileOrDir);
                        if(fileOrDir.isDirectory()) {
                            remainingDirs.add(fileOrDir);
                        }
                    }
                }
            }
        }

        return collectedFilesAndDirs;
    }

    private String[] getFixFromClassFile(Path pathToClass) throws MalformedURLException, ClassNotFoundException {
        // Create class loader based on path
        URLClassLoader loader = new URLClassLoader(new URL[]{pathToClass.toUri().toURL()});

        // convert path to class with package
        String classWithPackage = getClassWithPackageFromPath(pathToClass);

        // Load class dynamically
        Class<?> clazz = loader.loadClass(classWithPackage);
        Fix fix = clazz.getAnnotation(Fix.class);
        if (fix == null) {
            return new String[0];
        }

        return fix.author();
    }

    private String getClassWithPackageFromPath(Path pathToClass) {
        final String packageStartsFrom = "com.";
        final String classFileExtension = ".class";
        final String pathWithDots = pathToClass.toString().replace(File.separator, ".");
        return pathWithDots.substring(pathWithDots.indexOf(packageStartsFrom)).replace(classFileExtension, "");
    }

I get java.lang.StringIndexOutOfBoundsException: String index out of range: -1 at java.lang.String.substring(String.java:1927)

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808

1 Answers1

1

When you invoke getClass method on File object it will return java.io.File Class instance. This method does not load class from given file.

If you want to load a class from given *.class file you need to use java.lang.ClassLoader implementation. For example, java.net.URLClassLoader. Below you can find example how to load class and check annotation:

import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;

@Fix(author = "Test author")
public class ReflectionApp {

    public static void main(String[] args) throws Exception {
        String path = "path/to/com/so/ReflectionApp.class";
        String[] authors = getFixFromClassFile(Paths.get(path));
        System.out.println(Arrays.toString(authors));
    }

    private static String[] getFixFromClassFile(Path pathToClass) throws MalformedURLException, ClassNotFoundException {
        // Create class loader based on path
        URLClassLoader loader = new URLClassLoader(new URL[]{pathToClass.toUri().toURL()});

        // convert path to class with package
        String classWithPackage = getClassWithPackageFromPath(pathToClass);

        // Load class dynamically
        Class<?> clazz = loader.loadClass(classWithPackage);
        Fix fix = clazz.getAnnotation(Fix.class);
        if (fix == null) {
            return new String[0];
        }

        return fix.author();
    }

    private static String getClassWithPackageFromPath(Path pathToClass) {
        final String packageStartsFrom = "com.";
        final String classFileExtension = ".class";
        final String pathWithDots = pathToClass.toString().replace(File.separator, ".");
        return pathWithDots.substring(pathWithDots.indexOf(packageStartsFrom)).replace(classFileExtension, "");
    }
}

Above code prints:

[Test author]

See also:

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • For some reason I get `java.lang.StringIndexOutOfBoundsException: String index out of range: -1 at java.lang.String.substring(String.java:1927)` Any idea can we skip this part? – Peter Penzov Apr 29 '20 at 12:38
  • I found the issue. I want to .class files placed randomly into file system. For example the class with package `com.solutions.Actions` is placed under `/opt/lib` Can you modify our code for that case? – Peter Penzov Apr 29 '20 at 12:47
  • @PeterPenzov, it's probably because classes you want to load are not in a package `com.*`. I assumed it will be a `com.company.project.feature.Person.class`. If your classes are in package `ru.*` or `org.` just change this prefix in the code. – Michał Ziober Apr 29 '20 at 12:47
  • Is there any solution for random dir? – Peter Penzov Apr 29 '20 at 12:48
  • @PeterPenzov, But you already implemented this part which needs to find all classes, so you can merge it with my example and it should work. It is really hard to create a valid package from path because there is no any rule package names should follow. If you want to scan folders and find all classes you need to learn how to create packages from random path. It could be a really tricky problem (good one to open new question). You can simplify it and load only classes from packages which starts from `com.solutions`, for example. – Michał Ziober Apr 29 '20 at 12:56
  • I updated the post with the test but again I get error. – Peter Penzov Apr 29 '20 at 13:32
  • Ref: https://stackoverflow.com/questions/61503095/java-lang-stringindexoutofboundsexception-string-index-out-of-range-when-packag – Peter Penzov Apr 29 '20 at 13:36