Building a Java class list by reflection

Discussion in 'Mac Programming' started by wrldwzrd89, Jan 3, 2009.

  1. wrldwzrd89 macrumors G5

    wrldwzrd89

    Joined:
    Jun 6, 2003
    Location:
    Solon, OH
    #1
    Is it possible to dynamically grab all the classes in a particular package (within the current project, not in a JAR file or elsewhere), attempt to create new instances of them (assume they have a no-argument constructor), and store all these instances in an array for later processing? Assume that they are all derived from a common base class other than Object.

    For example, here's a possible class tree:
    package main:
    * Main.java
    package data:
    * DataOne.java
    * DataTwo.java
    * DataThree.java
    package list:
    * DataBaseClass.java
    * DataList.java

    The DataList class needs to contain an array of new instances of all the classes in the data package, and should update itself if classes are added/removed. Right now, I handle this the old-fashioned way, in DataList:
    Code:
    DataBaseClass[] arr = { new DataOne(), new DataTwo(), new DataThree() };
    What I'd like to do is make that arr variable have automatically generated contents. In this particular case, DataBaseClass serves as the common base class for DataOne, DataTwo, and DataThree.
     
  2. macsmurf macrumors 65816

    macsmurf

    Joined:
    Aug 3, 2007
    #2
    I think that it is possible, although I'm at a loss to think why you would want to. As far as I'm aware, the Java class loader doesn't know anything about classes that has not been used in the code. Also, on-demand import (wildcard import) does not actually load the classes (which is a good thing).

    Creating instances of known classes with default constructors is trivial using reflection so the problem is basically a lookup problem. You should be able to find the classes by traversing the class files in the classpath.

    The other approach would be to maintain a list of the classes in some sort of data file.

    An alternative to reflection would be to use code generation to generate the instance creating java code, but you'd still have the lookup problem.

    I think the only way to avoid traversing the class path or having a list of classes would be to implement the classes as inner classes i. e.

    Code:
    public class Foo {
    
        public class One {}
    
        public class Two {}
    
        public class Three {}
    
        public class Four {}
        
    }
    
    Code:
    public class Bar {
    
        public static void main(String[] args) {
            Foo foo = new Foo();
            
            for (Class clazz : foo.getClass().getClasses()) {
                System.out.println(clazz.getName());
            }
        }
    }
    
    However, all of this seems not to be very nice. There might be a better way to do whatever it is you want to do.
     
  3. wrldwzrd89 thread starter macrumors G5

    wrldwzrd89

    Joined:
    Jun 6, 2003
    Location:
    Solon, OH
    #3
    The reason is actually quite simple: I'm writing a game, and for various reasons I need a list of all the valid objects (for example, to place in the editor). I already have such a thing but I'd rather not have to keep updating it every time I add new objects to the game.
     
  4. macsmurf macrumors 65816

    macsmurf

    Joined:
    Aug 3, 2007
    #4
    ...So a working example using static inner classes would be:

    Code:
    public class DataBaseClass {
    
        public String getName() {
            return "DataBaseClass";
        }
    
        public static class DataOne extends DataBaseClass {
    
            @Override
            public String getName() {
                return "DataOne";
            }
        }
    
        public static class DataTwo extends DataBaseClass {
    
            @Override
            public String getName() {
                return "DataTwo";
            }
    
        }
    
        public static class DataThree extends DataBaseClass {
    
            @Override
            public String getName() {
                return "DataThree";
            }
    
        }
    }
    
    Code:
    import java.util.List;
    import java.util.ArrayList;
    
    public class DataList {
    
        private List<DataBaseClass> dataBaseClasses = new ArrayList<DataBaseClass>();
    
        public List<DataBaseClass> getDataBaseClasses() {
            return dataBaseClasses;
        }
    
        public static void main(String[] args) throws IllegalAccessException, InstantiationException {
            DataList dataList = new DataList();
    
            DataBaseClass dataBaseClass = new DataBaseClass();
    
            for (Class clazz : dataBaseClass.getClass().getClasses()) {
                dataList.getDataBaseClasses().add((DataBaseClass)clazz.newInstance());
            }
    
            for (DataBaseClass dbc : dataList.getDataBaseClasses()) {
                System.out.println(dbc.getName());
            }
        }
    }
    
    But again, there is probably a better way as the above is somewhat esoteric.
     
  5. wrldwzrd89 thread starter macrumors G5

    wrldwzrd89

    Joined:
    Jun 6, 2003
    Location:
    Solon, OH
    #5
  6. macsmurf macrumors 65816

    macsmurf

    Joined:
    Aug 3, 2007
    #6
    The link does not seem to work for me
     
  7. wrldwzrd89 thread starter macrumors G5

    wrldwzrd89

    Joined:
    Jun 6, 2003
    Location:
    Solon, OH
    #7
    Works fine here... *shrug*

    Here's the solution code.
    Code:
    /**
         * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
         *
         * @param packageName The base package
         * @return The classes
         * @throws ClassNotFoundException
         * @throws IOException
         */
        private static Class[] getClasses(String packageName)
                throws ClassNotFoundException, IOException {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            assert classLoader != null;
            String path = packageName.replace('.', '/');
            Enumeration<URL> resources = classLoader.getResources(path);
            List<File> dirs = new ArrayList<File>();
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                dirs.add(new File(resource.getFile()));
            }
            ArrayList<Class> classes = new ArrayList<Class>();
            for (File directory : dirs) {
                classes.addAll(findClasses(directory, packageName));
            }
            return classes.toArray(new Class[classes.size()]);
        }
    
        /**
         * Recursive method used to find all classes in a given directory and subdirs.
         *
         * @param directory   The base directory
         * @param packageName The package name for classes found inside the base directory
         * @return The classes
         * @throws ClassNotFoundException
         */
        private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
            List<Class> classes = new ArrayList<Class>();
            if (!directory.exists()) {
                return classes;
            }
            File[] files = directory.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    assert !file.getName().contains(".");
                    classes.addAll(findClasses(file, packageName + "." + file.getName()));
                } else if (file.getName().endsWith(".class")) {
                    classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
                }
            }
            return classes;
        }
    
     
  8. macsmurf macrumors 65816

    macsmurf

    Joined:
    Aug 3, 2007
    #8
    OK, so that would be a solution by looking up the class in the class path. Of course it would break if you need to package it into a Jar or use it on a non-unix system but that might not be a problem. :)
     

Share This Page