Page 2 of 2
Here is the code:
public static void find(String pckgname) {
// Code from JWhich
// ======
// Translate the package name into an absolute path
String name = new String(pckgname);
if (!name.startsWith("/")) {
name = "/" + name;
}
name = name.replace('.','/');
// Get a File object for the package
URL url = Launcher.class.getResource(name);
File directory = new File(url.getFile());
// New code
// ======
if (directory.exists()) {
// Get the list of the files contained in the package
String [] files = directory.list();
for (int i=0;I<files.length;i++) {
// we are only interested in .class files
if (files[i].endsWith(".class")) {
// removes the .class extension
String classname = files[i].substring(0,files[i].length()-6);
try {
// Try to create an instance of the object
Object o = Class.forName(pckgname+"."+classname).newInstance();
if (o instanceof Command) {
System.out.println(classname);
}
} catch (ClassNotFoundException cnfex) {
System.err.println(cnfex);
} catch (InstantiationException iex) {
// We try to instantiate an interface
// or an object that does not have a
// default constructor
} catch (IllegalAccessException iaex) {
// The class is not public
}
}
}
}
}
To perform the task at hand, you just need to modify the original launcher a bit. You can assume now that the interface and
its implementations are in the package commands:
public static void main(String[] args){
if (args.length>0) {
try {
Command command = (Command)Class.forName("commands."+args[0]).newInstance();
command.process();
} catch (Exception ex) {
System.out.println("Invalid command");
System.out.println("Available commands:");
find("commands");
}
} else {
System.out.println("Usage: Launcher <command>");
}
}
Here is the new result of a wrong command:
%java Launcher OpenDoor Invalid command Available commands: LightOn LightOff DoorOpen DoorClose DoorLock
You can refine the find() method to find any subclass of a given class. To do so, you use the dynamic version of instanceof, isInstance(). You replace the (o instanceof Command) with (tosubclass.isInstance(o)), where tosubclass is the class given in the parameter of the find() method.
Now you have a method that can find any subclass of a given class in a given package. You can improve the method by letting
it look for the subclasses in the currently loaded packages. To do that, you use the Package.getPackages() method, which returns the exact packages loaded by the current class loader. Then you just need to call the find() method for each package:
public static void find(String tosubclassname) {
try {
Class tosubclass = Class.forName(tosubclassname);
Package [] pcks = Package.getPackages();
for (int i=0;I<pcks.length;i++) {
find(pcks[i].getName(),tosubclass);
}
} catch (ClassNotFoundException ex) {
System.err.println("Class "+tosubclassname+" not found!");
}
}
The result of this method mainly depends on when it is called. In the case of the generic command launcher, few packages will
be loaded when the find() method is called. For instance, here are the packages loaded on my NT box before calling the find() method:
package java.util.zip, Java Platform API Specification, version 1.3 package java.security, Java Platform API Specification, version 1.3 package java.io, Java Platform API Specification, version 1.3 package sun.net.www.protocol.file, Java Platform API Specification, version 1.3 package sun.net.www.protocol.jar, Java Platform API Specification, version 1.3 package sun.net.www, Java Platform API Specification, version 1.3 package java.util.jar, Java Platform API Specification, version 1.3 package sun.security.action, Java Platform API Specification, version 1.3 package java.lang, Java Platform API Specification, version 1.3 package sun.io, Java Platform API Specification, version 1.3 package java.util, Java Platform API Specification, version 1.3 package sun.misc, Java Platform API Specification, version 1.3 package java.security.cert, Java Platform API Specification, version 1.3 package java.lang.reflect, Java Platform API Specification, version 1.3 package java.net, Java Platform API Specification, version 1.3 package sun.security.util, Java Platform API Specification, version 1.3 package java.lang.ref, Java Platform API Specification, version 1.3 package sun.security.provider, Java Platform API Specification, version 1.3 package com.sun.rsajca
Since, in the command launcher, the interface and all its implementations are in the same package, you get the loaded packages
after loading the class, thus allowing the search for subclasses in that package. This is the only way to find a relevant
package. The complete code for the RTSI class can be found in Resources. Once you unpack the zip file, you can test the code with the following:
% java -cp classes RTSI commands.Command
The code I have described works fine when the class files are present on the file system, but no longer works when the class files are in a (or several) jar file(s). In the source code, you will find a solution to that problem. You can test that capability with the following:
% java -jar RTSI.jar commands.Command
You have seen how to dynamically retrieve all the subclasses of a given class contained in a given package, or available on the loaded packages. This feature is useful for the design of generic programs. As shown by the command launcher example, it also helps the user.
Note from author: Some readers pointed out that this tip only detects subclasses that have a default constructor. They proposed that I use
the isAssignableFrom() method from Class instead of the isInstance(). You can obtain an updated version of RTSI.java here or in the updated source code below.
culorotoBy Anonymous on November 12, 2009, 2:31 pmculoroto
Reply | Read entire comment
View all comments