13

I am looking for a way to list all the subpackages of an arbitrary package in Java.

Something like this:

Package basePackage = getPackage("com.mypackage");
for(Package subPackage : basepackage.getSubPackages()){
  System.out.println(subPackage.getName());
}

Is there a way to do it? Thanks in advance.

How does IDE(let's say Netbeans) do it? enter image description here

UPDATE:

I am trying to find all the mappers package for MyBatis. In my project, all the mappers packages has to name "*.mappers". For example: "a.b.mappers" or "a.b.c.mappers". The thing is I only know the base package, and not sure how many mappers packages under it.

UPDATE: Here is my code trying to use reflection library to do it:

private Set<String> getPackagesNames() {
    Reflections reflections = new Reflections("com.mypackage");
    Set<Class<? extends Object>> allClasses = reflections.getSubTypesOf(Object.class);
    Set<String> packageNames = new HashSet<>();

    for( Iterator<Class<? extends Object>> it = allClasses.iterator(); it.hasNext(); ) {
        Class<? extends Object> subClass= it.next();
        packageNames.add(subClass.getPackage().getName());
    }

    return packageNames;
}

Not sure why this does not work. No class is found here....

UPDATE

Here is my code to do it. Kind of slow, but the performance is not that big concern in my case. I have never used Spring before, so if there are better ways to do it, let me know. Thanks.

    private static Set<String> getPackages(String basePackage) throws IOException, ClassNotFoundException {
        Set<String> packagesNames = new HashSet<>();

        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + "**/*.class";
        Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
        for( Resource resource : resources ) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);    
                Class aClass = Class.forName(metadataReader.getClassMetadata().getClassName());
                String packageName = aClass.getPackage().getName();
                packagesNames.add(packageName);
            }
        }
        return packagesNames;
    }

    private static String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
    }

Most of the code is copied from How do I read all classes from a Java package in the classpath?

Community
  • 1
  • 1
Xin
  • 737
  • 3
  • 10
  • 27
  • 1
    did you see this: http://stackoverflow.com/questions/4212088/java-programmatically-determine-all-of-the-package-names-loaded-on-the-classpa – Azodious Apr 09 '13 at 04:17

4 Answers4

13

I suppose the easiest way to get packages is to get all packages of your class loader with

Package.getPackages()

and filter it with your package name with

packageName.startsWith("com.yourcompany.yourpackage")
Dmitry Erokhin
  • 863
  • 1
  • 7
  • 14
  • Great tip! I'm working with a project that can't have more dependencies and this is a really good, lightweight solution, _although_ I haven't used it yet. – gustavohenke Oct 23 '14 at 12:29
  • 3
    As noted on http://stackoverflow.com/a/13944677/421602, this gives you all packages known (!) to the current class loader - so if you haven't accessed a class in a given package yet, it won't be part of this list. – vorburger Dec 17 '16 at 00:53
0

1) Load any class from the package and get its URL

URL u = Test.class.getResource("");

2) Determine if it is a file or jar.

2) use File.list() for directory or JarFile.getEntries for jar to find subpackages

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • ...this assumes all of the other classes in the package are in the same jar (correct 99% of thetime, but still..) – radai Apr 09 '13 at 04:30
  • That would also run differently in Maven test runs, as Maven adds the compiled test code only when running in test scope. Of course, there are no connections between packages and sub packages, so this task seems a bit odd. – Eric Jablow Apr 09 '13 at 04:43
  • @Evgeniy Dorofeev how do you determine if this URL is a file or jar. Thanks. – Xin Apr 09 '13 at 06:25
  • url.toString() returns something like file:/D:/workspace1/spring/target/test-classes/annotationconfig/ or jar:file:/D:/.repository/org/springframework/spring-core/3.2.0.RELEASE/spring-core-3.2.0.RELEASE.jar!/org/springframework/util/ – Evgeniy Dorofeev Apr 09 '13 at 06:30
0

Another possible way of solving this problem would be to use the system classloader to load all jars / zip files in the classpath and then simply inspect these.

Performance will not be great. Because we are inspecting jar / zip files, but it works.

Here is some example code:

import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.JarInputStream;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;

public class ClasspathLister {

    public static void main(String[] args) {
        listPackages("com/fasterxml").forEach(s -> {
            System.out.printf("%s%n", s.replace("/", ".").replaceAll("\\.$", ""));
        });
    }

    private static Set<String> listPackages(String prefix) {
        URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        return Arrays.stream(sysloader.getURLs())
            .filter(u -> u.toString().matches("(?i).+(\\.jar|\\.zip)$"))
            .flatMap(u -> listJar(u, prefix).stream())
            .collect(Collectors.toCollection(TreeSet::new));
    }

    private static Set<String> listJar(URL u, String prefix) {
        Set<String> packages = new LinkedHashSet<>();
        try (JarInputStream in = 
            new JarInputStream(Files.newInputStream(Paths.get(u.toURI())))) {
            ZipEntry ze;
            while ((ze = in.getNextEntry()) != null) {
                if (ze.isDirectory() && ze.getName().startsWith(prefix)) {
                    packages.add(ze.getName());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return packages;
    }
}

This will output something like:

com.fasterxml
com.fasterxml.jackson
com.fasterxml.jackson.annotation
com.fasterxml.jackson.core
com.fasterxml.jackson.core.base
com.fasterxml.jackson.core.filter
com.fasterxml.jackson.core.format
com.fasterxml.jackson.core.io
com.fasterxml.jackson.core.json
com.fasterxml.jackson.core.sym
com.fasterxml.jackson.core.type
com.fasterxml.jackson.core.util
com.fasterxml.jackson.databind
com.fasterxml.jackson.databind.annotation
com.fasterxml.jackson.databind.cfg
com.fasterxml.jackson.databind.deser
com.fasterxml.jackson.databind.deser.impl
com.fasterxml.jackson.databind.deser.std
com.fasterxml.jackson.databind.exc
...
gil.fernandes
  • 12,978
  • 5
  • 63
  • 76
0

To elaborate: IDE's actually read the JAR files of the dependencies. In the JAR file it's just a directory structure. This is different from the way the Java ClassLoader stores it.

Simon Baars
  • 1,877
  • 21
  • 38