10

I want to find whether the classes inside the jar has implemented a particular interface or not. I have implemented below code, but it iterates over all the classes inside jar file and finds on each class whether it has implemented this particular interface or not.

    public static synchronized boolean findClassesInJar(final Class<?> baseInterface, final String jarName){
        final List<String> classesTobeReturned = new ArrayList<String>();
        if (!StringUtils.isBlank(jarName)) {
            //jarName is relative location of jar wrt. 
            final String jarFullPath = File.separator + jarName;
            final ClassLoader classLoader = this.getClassLoader();
            JarInputStream jarFile = null;
            URLClassLoader ucl = null;
            final URL url = new URL("jar:file:" + jarFullPath + "!/");
            ucl = new URLClassLoader(new URL[] { url }, classLoader);
            jarFile = new JarInputStream(new FileInputStream(jarFullPath));
            JarEntry jarEntry;
            while (true) {
                jarEntry = jarFile.getNextJarEntry();
                if (jarEntry == null)
                    break;
                if (jarEntry.getName().endsWith(".class")) {
                    String classname = jarEntry.getName().replaceAll("/", "\\.");
                    classname = classname.substring(0, classname.length() - 6);
                    if (!classname.contains("$")) {
                        try {
                            final Class<?> myLoadedClass = Class.forName(classname, true, ucl);
                            if (baseInterface.isAssignableFrom(myLoadedClass)) {
                                return true;
                            }
                        } catch (final ClassNotFoundException e) {

                        } 
                    }
                }
            }
        return false;
    }

Is there any simple way to do this ? Because If have a jar with 100 class files and 100th class has implemented this interface, through the above code I need to iterate all the 100 class files and find whether it has implemented the interface or not. Is there any efficient way of doing it ?

Lolly
  • 34,250
  • 42
  • 115
  • 150

5 Answers5

11

The Reflections library can do that:

Reflections reflections = new Reflections(
    ClasspathHelper.forPackage("your.root.package"), new SubTypesScanner());
Set<Class<? extends YourInterface>> implementingTypes =
     reflections.getSubTypesOf(YourInterface.class);
Aleksandr Dubinsky
  • 22,436
  • 15
  • 82
  • 99
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • My understanding of the question is that OP is looking for a method that runs in better than O(n). I'm not sure exactly how the `Reflections` library is implemented, but from just the look of this code, I'd assume it's less efficient than OP's current code. – FThompson May 14 '13 at 15:07
  • @Vulcan - the OP asks 2 questions - a simpler solution and a more efficient solution. This is definitely simpler. – Stephen C May 14 '13 at 15:16
  • @Vulcan a) I can't find that assumption anywhere in the question b) reflections is a stable library that is used in production a lot. It's as efficient as such a library can be – Sean Patrick Floyd May 14 '13 at 15:17
  • 1
    @Vulcan for one thing, the OP actually loads all classes, whereas any sensible library (including reflections) would only inspect class metadata – Sean Patrick Floyd May 14 '13 at 15:18
  • Still, this seems like the kind of task you would do exactly once, at app startup time, and even the worst implementations should not take longer than a second. – Sean Patrick Floyd May 14 '13 at 15:20
  • That Reflections library must do exactly the same (maybe more elaborate) than the OP does manually. It can not magically enumerate all the resources on the classpath as there is no way to do so by design. – Durandal May 14 '13 at 15:46
  • 1
    @Durandal a class is a binary resource with a well-defined format, there is no need to actually load a class to read that format, in fact, it is a lot faster not to load all classes. – Sean Patrick Floyd May 14 '13 at 18:29
  • @SeanPatrickFloyd You overlook a problem there - a class can be provided *only* through a classloader to the VM. And a classloader can do anything to create that class, including generating it, so there may *be no resource* for a class. You just think of the usual case where there are jar files *and* its a known classloader (e.g. URLClassLoader) which that exposes its classpath. And if you want to know which classes implement X you *must* look at all of them. Your approach is not generic. – Durandal May 15 '13 at 12:15
  • @Durandal true, it doesn't work for classes that are generated dynamically on the fly, but neither does the OP's code. In fact: if classes are not represented by physical resources on the classpath, we have no way of finding them unless we search for them by name (which I'd call a very exotic use case and well outside this question's scope) – Sean Patrick Floyd May 15 '13 at 12:29
3

Spring has some helper code for this in ClassPathScanningCandidateComponentProvider but it basically does what you did.

You might also want to look at Freud which is a comfortable toolkit to write static analysis tests like making sure that all classes in a certain package or which implement a certain interface also implement equals() and hashCode()

The nice thing about Freud is that it can analyze Java source and class files (i.e. source code and compiled byte code), it can look into properties or text files, it can read CSS and Spring configurations (so it can make sure that all important methods of a DAO bean have @Transactional).

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • "Spring has some helper code for this in ClassPathScanningCandidateComponentProvider but it basically does what you did" no it doesn't. Spring uses ASM to read the class metadata instead of actually loading the classes. (Which is why I +1'd this for the suggestion) – Sean Patrick Floyd May 15 '13 at 12:38
  • 1
    I meant that Spring also reads all the entries from the JAR files on the classpath; the main difference is that it uses `ClassLoader.getResources()` to do so instead of opening the JAR and iterating over the entries. – Aaron Digulla May 15 '13 at 13:44
0

Any modern IDE (eclipse, IntelliJ, ...) should be able to find the usages of a particular interface (or class or method).

If you have the source of the jar file, you can add it as a dependency to your project. Alternatively you can simply unzip the contents of the jar and open that as a project in your IDE and find the usages of the interface you're interested in.

ApproachingDarknessFish
  • 14,133
  • 7
  • 40
  • 79
ceasaro
  • 595
  • 6
  • 11
0

In general, no there is no easy way. There isn't even a generic way of obtaining that information.

The problem here lies with Java's classloading mechanism. You can't tell if a class implements an interface before you actually loaded it. And you can't tell if a class exists without attempting to load it (note that you can't even tell which classes are available on the class path).

The reason here is simply that class loaders do not provide any enumrate classes functionality, and the reason for that is that the classpath can be potentially infinitely large/deep and the cost (in time) to retrieve that enumeration is potentially infinite (think of an URL class loader that connects you to a large remote code repository).

So your method is already as good as it can be.

Durandal
  • 19,919
  • 4
  • 36
  • 70
  • "You can't tell if a class implements an interface before you actually loaded it." That is simply not correct. Many modern frameworks like Spring could not work properly if that were the case. Spring uses [ASM](http://asm.ow2.org/) to read class metadata in it's [MetadataReaderFactory](http://static.springsource.org/spring-framework/docs/current/javadoc-api/org/springframework/core/type/classreading/MetadataReaderFactory.html), and it doesn't load the classes in that step. The reflections library I linked to in my answer also works that way. – Sean Patrick Floyd May 15 '13 at 12:36
  • While it may not need to *load* them into the VM, it *still* needs to load the class file. Spring can't magically tell if a class implements an interface without actually analyzing the class structure, agreed?. So *load* here is ambigous. True, I did mean it as "load through classloader"; but *strictly* speaking a class resource only becomes a class when its loaded by a class loader, so it can be interpreted either way. We could now endlessly nitpick about if *load* == *read* or not. Your comment can be interpreted in that it contradicts itself. – Durandal May 15 '13 at 14:32
  • The sensible approach is to read the metadata first, decide if the class needs loading and then load it, so there are in fact two "loading" steps involved: analyzing the class and loading it through the ClassLoader. The difference is important for several reasons: a) resources inspected through libraries like asm can be garbage collected, classes, once loaded, can't. b) classes may reference optional dependencies, i.e. classes that may or may not be on the classpath. loading such a class will trigger an exception and c) the class may contain code in static initializers – Sean Patrick Floyd May 15 '13 at 14:43
  • Classes loaded through a class loader can be garbage collected as well, I'm surprised you claim they cant be (see: http://stackoverflow.com/questions/2433261/when-and-how-are-classes-garbage-collected-in-java). And if you get any exception while loading the class, you also will get that exception when you later try to actually *use* it (and why do you imply thats a bad thing anyway?). Loading as "meta data" first is a waste of time and resources if you actually want to *do* something with the classes you find (you'd be loading them anyway). – Durandal May 15 '13 at 14:57
  • re garbage collecting classes I stand corrected, thanks. re everything else I still disagree. there is no way I'll be loading every class on the classpath (and running all their potentially evil initialization routines) just to find a handful of classes. You may see it differently (and it is additional effort, of course, although I wouldn't call it a waste), but the creators of the major libraries I have come across do it that way. – Sean Patrick Floyd May 15 '13 at 15:27
  • Yes, its a matter of opinion and which approach is better is completely subjective. For the OP's scenario I personally rate adding a library as overkill, but you can always argue for its with "don't reinvent the wheel". What bugged me was the claim "this library is more efficient" which it (in terms of computational complexity) can't really be, because the available JRE API's simply limit you to iteration, which make it an O(N) problem. – Durandal May 15 '13 at 15:45
0

You can use IntelliJ for this. Create a dummy project, and add your jars to classpath. Then open your interface, and click the (I) icon left of the interface name. Not sure about Eclilse, but pretty sure you can do the same.

Same goes for classes.

enter image description here

ipolevoy
  • 5,432
  • 2
  • 31
  • 46