2

I have a task to check a set of conditions for unknown set of classes from classpath. I want to scan it for classes, load each of them and perform my checks. Now I have a set of urls to class files and I try to use URLClassLoader. But to load a class I need to specify a fully qualified class name, but I don't have them (I have only file path). I don't think that building class name from class file path is relieble, is it a better way to do it?

Thanks!

dbf
  • 6,399
  • 2
  • 38
  • 65
  • Why building class name from class file path isn't reliable? I think it is. The only problem I see is for inner classes (multiple classes in one class file). That is the problem. – Aleksandar Jul 02 '15 at 08:46
  • I have something like C:\Projects\my_project_trunk\module\target\classes\com\a\b\c\d\e\f\Smth.class at my Win desktop and something like /home/user/teamcity/agent1/.../a/b/c/d/e/f/Smth.class at build server so my parser should find 'classes' part, build package name etc. Interesting why we can't fetch class name from class file, it should be there... – dbf Jul 02 '15 at 08:58
  • 1
    I'd just parse the beginning of the class file, looking for "package" keyword and first occurrence of "class" keyword. Then, when you combine those two (packageName + "." + className), it should result in a proper class name. – psliwa Jul 02 '15 at 09:02
  • @PiotrSliwa Make this as answer (for vote up). You are right. – Aleksandar Jul 02 '15 at 09:05

2 Answers2

3

I'd just parse the beginning of the class file, looking for "package" keyword and first occurrence of "class" keyword. Then, when you combine those two (packageName + "." + className), it should result in a proper class name.

psliwa
  • 1,094
  • 5
  • 9
  • PiotrSliwa, thanks for idea. Finally I used commons-bcel library to parse .class file that is a bit simplier. – dbf Jul 03 '15 at 06:08
1

I started a project once to automatically test classes found on the class path for run time exceptions, by calling constructors and methods reflectively with dodgy arguments like null, 0, 1, -1, "" etc.

That project has a class called Finder wich does roughly what you need:

    static List<Class<?>> findClassesForPackage(String packagename, Report report) throws ClassNotFoundException {
            // 'classes' will hold a list of directories matching the package name.
            // There may be more than one if a package is split over multiple
            // jars/paths
            List<Class<?>> classes = new ArrayList<Class<?>>();
            List<File> directories = new ArrayList<File>();
            try {
                    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                    if (classLoader == null) {
                            throw new ClassNotFoundException("Can't get class loader.");
                    }
                    // Ask for all resources for the path
                    String path = packagename.replace('.', '/');
                    Enumeration<URL> resources = classLoader.getResources(path);
                    while (resources.hasMoreElements()) {
                            URL res = resources.nextElement();
                            if (res.getProtocol().equalsIgnoreCase("jar")) {
                                    JarURLConnection conn = (JarURLConnection) res.openConnection();
                                    JarFile jar = conn.getJarFile();
                                    for (JarEntry entry : Collections.list(jar.entries())) {
                                            if (entry.getName().startsWith(path) && entry.getName().endsWith(".class")
                                                            && !entry.getName().contains("$")) {
                                                    String className = entry.getName().replace("/", ".").substring(0,
                                                                    entry.getName().length() - 6);
                                                    LOG.debug("Adding JAR className " + className);
                                                    try {
                                                            Class<?> clazz = Class.forName(className);
                                                            classes.add(clazz);
                                                            report.addClass(className);
                                                    } catch (Throwable throwable) {
                                                            ParamSet params = new ParamSet();
                                                            params.addParamValue(new ParamValue(className, "fully qualified classname"));
                                                            report.addError(className, new Error("Class.forName()", params, throwable));
                                                    }
                                            }
                                    }
                            } else
                                    directories.add(new File(URLDecoder.decode(res.getPath(), "UTF-8")));
                    }
            } catch (NullPointerException e) {
                    throw new ClassNotFoundException(String.format("%s does not appear to be a valid package", packagename), e);
            } catch (UnsupportedEncodingException e) {
                    throw new ClassNotFoundException(String.format("%s does not appear to be a valid package", packagename), e);
            } catch (IOException e) {
                    throw new ClassNotFoundException(String.format("Could not get all resources for %s", packagename), e);
            }
            List<String> subPackages = new ArrayList<String>();
            // For every directory identified capture all the .class files
            for (File directory : directories) {
                    if (directory.exists()) {
                            // Get the list of the files contained in the package
                            File[] files = directory.listFiles();
                            for (File file : files) {
                                    // add .class files to results
                                    String fileName = file.getName();
                                    if (file.isFile() && fileName.endsWith(".class")) {
                                            // removes the .class extension
                                            String className = packagename + '.' + fileName.substring(0, fileName.length() - 6);
                                            LOG.debug("Adding FILE className " + className);
                                            try {
                                                    Class<?> clazz = Class.forName(className);
                                                    classes.add(clazz);
                                                    report.addClass(className);
                                            } catch (Throwable throwable) {
                                                    ParamSet params = new ParamSet();
                                                    params.addParamValue(new ParamValue(className, "fully qualified classname"));
                                                    report.addError(className, new Error("Class.forName()", params, throwable));
                                            }
                                    }
                                    // keep track of subdirectories
                                    if (file.isDirectory()) {
                                            subPackages.add(packagename + "." + fileName);
                                    }
                            }
                    } else {
                            throw new ClassNotFoundException(String.format("%s (%s) does not appear to be a valid package",
                                            packagename, directory.getPath()));
                    }
            }
            // check all potential subpackages
            for (String subPackage : subPackages) {
                    classes.addAll(findClassesForPackage(subPackage, report));
            }
            return classes;
    }

You probably have to strip some code that does reporting etc.

Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60