|
|
|||
![]() |
Department of Engineering |
| University of Cambridge > Engineering Department > computing help > Languages > Java |
This article aims to explain how to dynamically load classes at runtime with Java. In doing so it will illustrate the use ofpackage,interface, Exceptions, abstract classes, etc, with Vectors and Hashtables thrown in. Some knowledge of Java is assumed. Most of the code originates from http://www.pramodx.20m.com/dynamic_class_loading_in_java.htm for which much thanks.
Plug-in architectures offer flexibility for both the programmer and the user - extra functionality can be added to a program without the program having to be changed. Java has mechanisms to support this approach. This example is based on a situation where a text file will be read in and analysed in various ways, some of which may not be foreseeable by the programmer. When the program is run, it will load in the available plug-ins. The plug-ins need to conform to rules laid down by the main program but within those limits there's still some flexibility.
The type of arguments these detectors need may depend on what the detectors are looking for. In the example provided the plug-in is looking for a certain poetry form where the syllable-per-line information is needed. The main program can ask plug-ins what type of arguments are required.
package - Rather like a library in other languages, or
a namespace. All the
methods here are put into a TextPatterns package. This helps avoid
potential name-clashes, especially in big programs. To use a package, you
need to import it.interface - Unlike C++, Java doesn't support multiple inheritance - objects can be derived from only one parent. However, Java has interfaces so that otherwise unrelated objects can show that they support certain method calls. An interface declares a list of publically available methods and constants, but doesn't state where (in what class) the implementation of the methods are. In this example, an interface is defined in
Detector.java
package TextPatterns;
import java.util.*;
public interface Detector
{
void description();
String argtype_required();
void processing(Vector v);
}
interfaces are created and used rather in the same way as classes are except that whereas a class can only extend one class it
can implement several interfaces. Note that implementing an interface has some similarities with extending a class with abstract methods - in both situations the new class has to provide an implementation. However, extending a class implies inheriting its data structures and non-abstract methods too.
The plug-ins here all have to implement this interface.
For example, they all have a
description method which should display a description of the
plug-in.
exception - When something goes wrong, a java program
throws a bundle of information called an exception
that another piece of code should catch. There are many types
of exceptions to cope informatively with different situations. Here we'll
invent our own exception type. DetectorNotCreatedException.java
contains
package TextPatterns;
public class DetectorNotCreatedException extends Exception
{
DetectorNotCreatedException()
{
super("Detector could not be created!");
}
}
java.lang.Class the
static Class Class.forName(String name) method returns the
class object associated with the class name. If the class has not been
loaded, most implementations load the class as well. It throws the
ClassNotFoundException if the class could not be found for some
reason. This is what we'll use for dynamic loading. When a class is loaded
in this way, any static method that doesn't have a name is run. We'll make
use of this idea to add information about the loaded class to a table.
The code is divided into a number of files.
package TextPatterns;
import java.util.*;
public abstract class DetectorCreator
{
// This is the list of loaded plug-ins
public static Hashtable detectorFactories = new Hashtable();
protected static Detector createDetector(String name, String type)
throws DetectorNotCreatedException
{
DetectorFactory s = (DetectorFactory) detectorFactories.get(name);
if(s == null) // detector not found
{
try
{
Class.forName("TextPatterns.Detectors."+name);
// Loading the class should add it to the detectorFactories
// table.
s = (DetectorFactory) detectorFactories.get(name);
if (s == null)
{
throw (new DetectorNotCreatedException());
}
}
catch(ClassNotFoundException e)
{
// We'll throw an exception to indicate that
// the detector could not be created
throw(new DetectorNotCreatedException());
}
}
return(s.create(type));
}
}
package TextPatterns;
// a detector is expected to provide sub-class of this factory which
// will create the required detector.
// The static block without a function name in the the detector class
// should create a new instance of its factory and add it to the
// hash-table
public abstract class DetectorFactory
{
public abstract Detector create(String type);
}
package TextPatterns;
// To run this, set the CLASSPATH to the parent directory,
// go to that directory, and type
// java TextPatterns.PatternDetector
import java.io.*;
import java.util.*;
public class PatternDetector extends DetectorCreator
{
public static void main(String args[])
{
// Look for *.class files in a particular directory
// and load them in using createDetector
File dir = new File("TextPatterns/Detectors");
if (!dir.exists()) {
// File or directory does not exist
System.out.println("No plugin directory");
return;
}
String[] children = dir.list();
if (children == null) {
System.out.println("No plugin directory - it's a file");
return;
}
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".class");
}
};
children = dir.list(filter);
// Let's specify 5 types of plug-in
Hashtable plugin_families = new Hashtable();
plugin_families.put("syllabic", "Vector of ints (syllables/line)");
plugin_families.put("textual","Vector of strings (the lines of the poem)");
plugin_families.put("stress", "Vector of strings (stress patterns)");
plugin_families.put("phoneme", "Vector of strings (phonemes)");
plugin_families.put("rhyme", "Vector of ints (rhyming lines)");
for (int i=0; i<children.length; i++) {
// Get filename of file or directory
String filename = children[i].substring(0, children[i].length()-6);
if (filename.indexOf('$')==-1) {
try
{
Detector s = createDetector(filename,"syllabic");
System.out.println(filename + " plug-in loaded");
System.out.print(" Description: ");
s.description();
System.out.print(" Processing: ");
String plugin_type=s.argtype_required();
String argtype_description = (String) plugin_families.get(plugin_type);
if (argtype_description == null)
System.out.println(plugin_type + "isn't a recognised type");
else
if (plugin_type.equals("syllabic")){
// In the real program we'd process a text file to
// provide the arguments. Here we'll fabricate
Vector vec = new Vector();
vec.addElement(new Integer(5));
vec.addElement(new Integer(7));
vec.addElement(new Integer(5));
s.processing(vec);
}
}
catch(DetectorNotCreatedException e)
{
System.out.println(e.getMessage());
}
catch(ClassCastException e)
{
System.out.println("Tried to load a non-detector as a detector!!");
}
}
}
// Now print some info about the loaded plug-ins
System.out.println(DetectorCreator.detectorFactories.size() + " plug-in(s) loaded. Names are:");
for ( Enumeration e = DetectorCreator.detectorFactories.keys() ; e.hasMoreElements() ; )
System.out.println(e.nextElement());
}
}
package TextPatterns.Detectors;
import TextPatterns.*;
import java.util.*;
import java.io.*;
public class SyllableFormDetection implements Detector
{
String type;
public SyllableFormDetection()
{
type = new String("syllabic");
}
public SyllableFormDetection(String t)
{
type = t;
}
public void description()
{
System.out.print("This is a description of the SyllableFormDetection plug in.");
System.out.println(" It is a " + type + " detector");
}
public String argtype_required()
{
return type;
}
public void processing(Vector vec) {
// Haiku check
if (vec.size() == 3) {
System.out.print("Three lines ...");
if ( ((Integer)vec.elementAt(0)).intValue()==5 &&
((Integer)vec.elementAt(1)).intValue()==7 &&
((Integer)vec.elementAt(2)).intValue()==5)
System.out.println("... and a haiku!");
else
System.out.println("... but no haiku");
}
}
// This class has to be static (i.e. a class method rather than
// an object one) because it's called from the static block below
static class SyllableFormDetectionFactory extends DetectorFactory
{
public Detector create(String type)
{
return(new SyllableFormDetection(type));
}
}
// Note that the following routine is static and has no name, which
// means it will only be run when the class is loaded
static
{
// put factory in the hashtable for detector factories.
DetectorCreator.detectorFactories.put("SyllableFormDetection", new SyllableFormDetectionFactory());
}
}
Detector interfacetype set so that the processing method
can receive the appropriate argumentsIf you're going to try this example out, you'll have to create a directory named DetectorUser. In that create a directory named Detectors. The
DetectorUser directory will store the main sources and the Detectors directory
will store the plug-ins. Set your CLASSPATH environment variable
to the parent directory of DetectorUser. Then compile the files in the
order they're presented here. Run by going to the parent directory and typing
java TextPatterns.PatternDetector
The top-level PatternDetector extends
DetectorCreator so that it can load plug-ins.
SyllableFormDetection is the sample plug-in provided.
Loading it calls the plug-in's nameless block which in
turn calls the plug-in's factory (which extends
DetectorFactory).
| | computing help | Java | Languages | |