You could put all the classes in a specific package, then scan that directory for class files, load them, and track the ones that extend Monster
. You could even define some custom annotations to help manage this, e.g. @IgnoreMonster
to temporarily disable some without having to change the location of the file. This is similar to the way e.g. Hibernate scans source to find entity mappings.
Here is an example. All the Monster classes are placed in package dload.monsters
. First, here's the base class I'm using for this example:
package dload.monsters;
public abstract class Monster {
public abstract String getName ();
}
Then, a MonsterFactory
which scans for all classes in the dload.monsters
package (sorry its a little sloppy, and I skimped out on exception handling):
package dload.monsters;
import java.io.*;
import java.net.*;
import java.util.*;
public class MonsterFactory {
private static final List<Class<? extends Monster>> monsterClasses = new ArrayList<Class<? extends Monster>>();
private static final Random random = new Random();
@SuppressWarnings("unchecked") // <- for monsterClasses.add((Class<? extends Monster>)cls);
public static void loadMonsters () throws Exception {
// in this example, Monster is in the same package as the monsters. if
// that is not the case, replace "." with path relative to Monster.
File folder = new File(Monster.class.getResource(".").toURI());
for (File f : folder.listFiles()) {
if (f.getName().endsWith(".class")) {
String name = f.getName().split("\\.")[0];
// replace "dload.monsters." below with package monsters are in
Class<?> cls = ClassLoader.getSystemClassLoader().loadClass("dload.monsters." + name);
// if Monster is not in same package as monsters, you can remove
// cls.equals(Monster.class) check. this check makes sure the loaded
// class extends Monster, but is not the Monster class itself (since
// its also in that package).
if (Monster.class.isAssignableFrom(cls) && !cls.equals(Monster.class)) {
System.out.println("Found " + cls.getSimpleName());
monsterClasses.add((Class<? extends Monster>)cls);
}
}
}
// at this point all Class's for monsters are in monsterClasses list.
}
public static Monster randomMonster () throws Exception {
// choose a class at random
int n = random.nextInt(monsterClasses.size());
Class<? extends Monster> cls = monsterClasses.get(n);
// instantiate it
return cls.newInstance();
}
}
Then, when you want to use it:
public static void main (String[] args) throws Exception {
// load monsters; only need to do this once at startup
MonsterFactory.loadMonsters();
// create 10 random monsters
for (int n = 0; n < 10; ++ n) {
Monster m = MonsterFactory.randomMonster();
System.out.println("name is " + m.getName());
}
}
Note that at any time you can check the monster's Class
for relevant annotations.
Another option, if the classes are already loaded (which they won't be if they've never been used or explicitly loaded) is to use Instrumentation.getAllLoadedClasses()
to get a list of all classes currently loaded, then scan through all classes looking for ones that are assignable to a Monster
.
Note: I do feel like there is a cleaner way to do the actual scan, and I haven't tested this in a JAR. Suggestions welcome.
All that being said, if a Monster's behavior could be entirely defined by data, I also support and recommend the data driven approach described above.