Dynamic Loading/Reloading of Classes and Dynamic Method Invocation, Part 1, Page 2
Run the Program
I encourage you to copy the code from Listing 35 and Listing 36 into your text editor, compile it, and execute it. Experiment with it, making changes, and observing the results of your changes.
Summary
In this lesson, I taught you how to write a program that modifies its fundamental behavior at runtime by dynamically modifying, compiling, loading, and reloading classes.
This involves writing code, which in turn writes new code in the form of source code for new class definitions.
It also involves writing code that causes the source code for the new classes to be compiled and loaded.
Finally, it involves writing code that uses reflection to invoke instance methods belonging to objects instantiated from the newly-loaded classes.
What's Next?
In Part 2 of this lesson, I will show you two alternative ways to accomplish the same thing using an approach that avoids the complexity of reflection.
References
The following online documents contain information that is relevant to the material covered in this tutorial lesson:
- Lesson 58 Threads of Control
- Lesson 60 Core Java Classes, Input and Output Streams
- Lesson 1062 Swing from A to Z: Analyzing Swing Components, Part 2, GUI Setup
- Lesson 1468 Plotting Engineering and Scientific Data using Java
- Lesson 1472 Introduction to the Java Robot Class
- Lesson 1640 The Essence of OOP using Java, Anonymous Classes
- Lesson 2300 Generics in J2SE 5.0, Getting Started
- The Class Loader Architecture by Gary McGraw and Ed Felten
Complete Program Listing
/*File Reload01.java
Copyright 2006, R.G.Baldwin
Revised 03/02/04
Illustrates writing, compiling, loading, reloading, and
instantiating an object of a class, and invoking a method
on that object totally under program control.
The program begins by automatically writing the following
simple class into a source code file in a subdirectory (of
the current directory) named temp:
public class Reload01a{
public String theMethod(String str,double val){
System.out.println("Executing theMethod");
return str + " " + "Tom" + " " + val/3;
}
}
The program displays a GUI having a single button.
Each time you click the button, the program compiles and
reloads the class named Reload01a. Then it instantiates
an object of the newly-loaded class and invokes the method
named theMethod on the object.
If you modify the source code file for the class, the next
time you click the button to cause the file to be compiled
and reloaded, the new behavior will be evident.
If the compilation process fails to return within five
seconds, the compilation process is aborted and a
"Compilation timeout error" is declared. It is also
possible that the compilation process could return within
five seconds with a compilation error. In either case, a
"Probable compile error" will be declared. The program is
not terminated as a result of a probable compile error, so
you can fix the problem in the source code and try again
by clicking the button.
The location of the class file is hard-coded as the
subdirectory named temp, but could be anywhere on the disk
that is accessible to the program if the program were
modified accordingly.
You need to make certain that there is not a copy of
a compiled version of the class named Reload01a on the
classpath. If the program finds a copy on the classpath,
the class will not be reloaded when a reload is requested.
Once this program is running, use an editor to modify the
source code for class and then click the button.
Here is a typical output produced by the program by
modifying the source code file for the class between each
click of the button. In one case, a syntax error was
purposely created so as to produce a compiler error.
Writing the class file.
In actionPerformed
Compiling tempReload01a.java
Waiting for completion
Compile complete
Executing theMethod
Hello Tom 3.3666666666666667
In actionPerformed
Compiling tempReload01a.java
Waiting for completion
Compilation timeout error.
Probable compiler error.
In actionPerformed
Compiling tempReload01a.java
Waiting for completion
Compile complete
Executing theMethod
Hello Bill 3.3666666666666667
In actionPerformed
Compiling tempReload01a.java
Waiting for completion
Compile complete
Executing theMethod
Hello Bill 2.525
Tested using J2SE 5.0 under Windows XP.
**********************************************************/
import java.net.*;
import java.io.*;
import java.lang.reflect.*;
import java.awt.event.*;
import javax.swing.*;
class Reload01 extends JFrame{
File targetDir;//location of the file named Reload01a
public static void main(String[] args){
new Reload01().writeTheClassFile();
}//end main
//-----------------------------------------------------//
Reload01(){//constructor
setTitle("Copyright 2006, R.G.Baldwin");
JButton button = new JButton("Recompile and Reload");
getContentPane().add(button);
button.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("nIn actionPerformed");
try{
//Now compile the new class file. The compile
// method returns true on a successful compile,
// and false otherwise.
boolean compileStatus = compile(
"temp" + File.separator + "Reload01a.java");
if(compileStatus){//If compile was successful.
System.out.println("Compile complete");
//The class has been defined and compiled.
//Force the class named Reload01a to be
// reloaded and get a Class object that
// represents the reloaded version of the
// class in the process.
//Specify the name of the class and the
// directory containing the class file that
// is to be reloaded.
Class loadedClass =
reloadTheClass("Reload01a",
targetDir);
//Instantiate a new object of the class.
Object obj = loadedClass.newInstance();
//Get a reference to an object of type Method
// that represents the instance method
// belonging to the object. The method
// belonging to the object will be invoked
// later by invoking the method named invoke
// on the object of type Method.
//The first parameter specifies the name of
// the target method that will be invoked
// later. In this case, the name is
// theMethod.
//The second parameter specifies that the
// method named theMethod requires two
// parameters, the first of type String and
// the second of type double.
Method methodObj =
loadedClass.getDeclaredMethod(
"theMethod",
new Class[]{String.class,double.class});
//Invoke the method named theMethod by
// invoking the method named invoke on the
// Method object, passing the reference to
// the object and a reference to a parameter
// array as parameters to the invoke method.
//Cast the return value from Object to
// String. Then display the return values.
//When the target method requires primitive
// parameters, they must be wrapped in an
// object of the corresponding wrapper class
// for passing to the invoke method. They
// are automatically unwrapped by the system
// for passing to the target method.
//Similarly, when the target method returns a
// value of a primitive type, the invoke
// method will return that value wrapped in
// an object of the corresponding wrapper
// class.
String returnVal = (String)methodObj.invoke(
obj,
new Object[]{"Hello",new Double(10.1)});
System.out.println(returnVal);
}//end if on compileStatus
else{
System.out.println(
"Probable compiler error.");
}//end else
}catch(Exception ex){
ex.printStackTrace();
}//end catch
}//end actionPerformed
}//end new ActionListener
);//end addActionListener
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(250,100);
setVisible(true);
}//end constructor
//-----------------------------------------------------//
//Write the source code for the file named Reload01a
// into a subdirectory named temp. If the file
// exists, it will be overwritten.
void writeTheClassFile(){
try{
//Create a File object that points to a directory
// where the class file will reside.
targetDir = new File(System.getProperty("user.dir")
+ File.separator + "temp" + File.separator);
//If the directory doesn't exist, make it.
if(!targetDir.exists()){
targetDir.mkdir();
}//end if
//Get an output stream for writing the source code
// for the new class file.
DataOutputStream dataOut = new DataOutputStream(
new FileOutputStream("temp" + File.separator +
"Reload01a.java"));
//Create the source code for the new class file.
System.out.println("Writing the class file.");
dataOut.writeBytes(
"public class Reload01a{" +
"public String theMethod(String str,double val){" +
"System.out.println("Executing theMethod");" +
"return str + " " + "Tom" + " " + val/3;" +
"}" +
"}"
);//end writeBytes method
dataOut.close();//Close the output file.
}catch(Exception e){
e.printStackTrace();
}//end catch
}//end writeTheClassFile
//-----------------------------------------------------//
//The purpose of this method is to re-load a class that
// is specified by name as a String from a subdirectory
// specified by a File parameter named dir.
static Class reloadTheClass(String reloadableClass,
File dir){
//Get a URL object that points to the directory
// containing the class file for the class that is
// to be loaded.
URL[] theUrl = null;
try{
//The compiled class file for the reloadable class is
// stored in a directory that was specified by an
// incoming parameter named dir of type File.
//Following the recommendations in the Sun docs,
// convert the File object to a URI and convert the
// URI to a URL. Deposit the reference to the URL
// object into an one-element array of type URL.
URI uri = dir.toURI();
URL url = uri.toURL();
theUrl = new URL[]{url};
}catch(Exception e){
e.printStackTrace();
}//end catch
Class theClass = null;
try {
//Create a new class loader associated with the
// directory containing the reloadable class.
ClassLoader classLoader = new URLClassLoader(theUrl);
// Load the specified class, creating a Class object
// that represents the class in the process.
theClass = classLoader.loadClass(reloadableClass);
}catch (Exception e){
e.printStackTrace();
}//end catch
//Return a reference to the Class object that
// represents the newly-loaded class.
return theClass;
}//end reloadTheClass
//-----------------------------------------------------//
//Method to compile the java source code file. Returns
// true if successful, false otherwise.
private static boolean compile(String file)
throws IOException{
System.out.println("Compiling " + file);
Process p = Runtime.getRuntime().exec("javac " + file);
//Note: Sometimes the method named waitFor hangs up
// and fails to return when there is a compiler error.
// The following code is designed to deal with that
// problem. This code allows five seconds for the
// compilation to complete successfully and the
// waitFor method to return. If the waitFor method
// fails to return within five seconds, the code
// declares a "Compilation timeout error" and
// terminates the compilation process, returning false
// from the method to indicate a compiler error.
// However, it doesn't terminate the program and the
// user may correct the program and try again.
Thread myTimer = new Thread(
new Timer(Thread.currentThread(),5000));
//Start Timer thread
myTimer.start();
System.out.println("Waiting for completion");
try{
p.waitFor();//wait for completion
//The waitFor method has returned,
if(myTimer.isAlive()){
//Stop the timer.
myTimer.interrupt();
}//end if
}catch(InterruptedException e){
//The timer expired before the waitFor method
// returned and interrupted the waitFor method.
System.out.println("Compilation timeout error.");
p.destroy();
return false;
}//end catch
//The waitFor method returned in five seconds or less.
//p.exitValue() other than 0 indicates a compiler
// error.
return p.exitValue() == 0;
}//end method compile
//-----------------------------------------------------//
}//end class Reload01
//=======================================================//
class Timer implements Runnable{
Thread theCompilingThread;
int delay;//time interval to sleep
//-----------------------------------------------------//
Timer(Thread theCompilingThread, int delay){//constructor
this.theCompilingThread = theCompilingThread;
this.delay = delay;
}//end constructor
//-----------------------------------------------------//
//The significant functionality of all thread objects is
// written into run() method for the object.
public void run(){
try{
Thread.currentThread().sleep(delay);
}catch(InterruptedException e){
//No action is required when this sleeping thread is
// interrupted.
return;
}//end catch
//Control is transferred to here when the sleeping
// thread awakens naturally after the specified delay
// period. This means that the compilation process is
// probably hung up. Interrupt the compilation
// process and terminate the run method.
theCompilingThread.interrupt();
}//end run method
}//end Timer class
//=======================================================//
|
/**********************************************************
File Reload02.java
Copyright 2006, R.G.Baldwin
Revised 03/02/06
This program demonstrates the use of dynamic class loading.
This version uses the invoke method of the Method class to
access and execute the methods belonging to an object of
a newly-loaded class. See Reload03 for a version that
uses an interface to access and reload those methods.
When the program executes, it writes, compiles, loads,
instantiates, and invokes methods on an object of a new
class that is designed automatically according to input
from the user.
This is a plotting program. It is designed to create and
to access a new class file named Reload02a and to plot
three methods that are defined in that class. The
methods are named f1, f2, and f3.
The behavior of each of the three methods is specified in
text form by the user who enters equations in three
corresponding text fields.
By modifying the equations, checking a check box, and
clicking a button, the user can cause a new class to be
created.
The new class contains three methods with behavior
matching the equations entered by the user. The three
methods are then plotted on a common set of Cartesian
coordinates. Once the methods are defined and plotted,
the user can modify many plotting parameters and cause the
methods to be plotted over and over with different
plotting parameters.
Once the user is satisfied with the plots of those three
methods, the user can modify the equations and start
over with three new equations without terminating the
program or manually defining any new code.
The three methods named f1, f2, and f3 are plotted in
black, red, and blue respectively.
A coordinate system with axes, tic marks, and labels is
plotted in green. The labels on the axes correspond to
the values at the extreme edges of the plotting surface.
The initial plotting range extends from -151 to +151 on
each dimension. This causes points plotted within the
range from -150 to +150 inclusive to be visible on the
plotting surface.
The plotting range on the positive or negative side of
either dimension can easily be changed by the user.
The default size of the GUI is 475 pixels wide by 430
pixels tall. This size was chosen to satisfy the size
requirements of my publisher. You may want to expand the
size in order to be able to view more detail.
The new class file named Reload02a.class is created in a
subdirectory of the current directory named temp. If the
subdirectory doesn't exist, it will be created.
CAUTION: If a class file having the same name already
exists in that subdirectory, it will be overwritten.
The program provides three text fields into which the user
enters equations expressed in java syntax. For example,
the following equation causes the value of y to be plotted
on the vertical as a function of x on the horizontal axis.
(Note that the semicolon is required.)
y = 100.0*(cos(x/10.0)*sin(x/11.0));
In this case, the cos and sin methods of the Math class are
used to evaluate the equation. All computations are
performed as type double. Then the results are converted
to integer values for plotting.
For the student with more advanced Java programming
knowledge, the text entered into the text field can be
more advanced. For example, the following will plot as
a series of spikes.
if(x%10 == 0){y = 50;}else{y = -50;}
The program uses a static import directive for the Math
class. Therefore, it is not necessary to qualify methods
from that class with the name of the class as in
Math.cos(...). All that is required is cos(...). Thus,
all of the methods of the Math class are directly available
to be included in the equations.
If it is desired to make use of other classes, it is
necessary to fully qualify the references to those classes
using their package name. Here is an example of such
qualification, which has no purpose other than to
illustrate the syntax involved:
y = x * ((new java.util.Date().getTime())/
(new java.util.Date().getTime()));
(Note that line break was manually inserted in the middle
of the above equation.)
As it turns out, this is a very complicated way to express
the equation for a straight line, so the plot is a straight
line.
The three text fields described above are located in the
NORTH portion of the GUI. In addition, a check box is
also located in the NORTH of the GUI. There is a Graph
button at the bottom of the GUI. If the check box is
checked when the Graph button is clicked, a new class that
matches the equations that have been entered into the text
fields described above will be created, compiled, and
loaded before the equations are plotted. If the check box
has not been checked, the equations in the existing class
will be replotted, potentially with different plotting
parameters. (You can change the plotting parameters by
changing the values in text fields at the bottom of the
GUI.)
The program plots the following three equations by default
at startup to provide a visual confirmation that the
program is working, and also to illustrate the syntax that
should be used to enter equations into the text fields:
y = (x*x*x + 40*x*x + 100*x -6000)/100;
y = x * ((new java.util.Date().getTime())/
(new java.util.Date().getTime()));
y = 100.0*(cos(x/10.0)*sin(x/11.0));
(Note that a line break was manually entered into the
middle equation in the above list.)
The first equation shown above is a cubic function with
roots at values for x of -20, -30, and +10.
The second equation illustrates how classes other than the
Math class can be incorporated into equations.
The third equation illustrates how methods of the Math
class can be incorporated into equations.
In addition to the text fields at the top of the GUI as
described above, this program also provides the following
text fields at the bottom of the GUI for user input. The
program also provides a button labeled Graph at the bottom
of the GUI. The combination of these text fields and the
button make it possible for the user to adjust the
plotting parameters and to replot the graph as many times
with as many plotting-parameter settings as may be needed.
xMin = minimum x-axis value
xMax = maximum x-axis value
yMin = minimum y-axis value
yMax = maximum y-axis value
xTicInt = tic interval on x-axis
yTicInt = tic interval on y-axis
xCalcInc = calculation interval (should normally be set
to 1.0)
The user can modify any of these parameters and then click
the Graph button to cause the three methods to be
re-plotted according to the new parameters.
In order to plot new and different equations, it is only
necessary to enter the new equations into the text fields
at the top of the GUI, check the check box at the top of
the GUI, and click the Graph button.
If the compilation process fails to return within five
seconds, the compilation process is aborted and a
"Compilation timeout error" is declared. It is also
possible that the compilation process could return within
five seconds with a compilation error. In either case, a
"Probable compile error" will be declared. The program is
not terminated as a result of a probable compile error, so
you can fix the problem in the source code and try again
by clicking the button.
The program was tested using J2SE 5.0 under Windows XP.
This program uses static import directives. Therefore, it
requires J2SE 5.0 or later to compile and run successfully.
**********************************************************/
import java.net.*;
import java.io.*;
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
class Reload02{
public static void main(String[] args){
//Instantiate a GUI object.
new ScienceGraph();
}//end main
}//end class Reload02
//=======================================================//
class ScienceGraph extends JFrame
implements ActionListener{
//Define plotting parameters and their default values.
double xMin = -151.0;
double xMax = 151.0;
double yMin = -151.0;
double yMax = 151.0;
double xTicInt = 10.0;//Tic interval
double yTicInt = 10.0;//Tic interval
//Calculation interval along x-axis. This should
// normally be left at 1.0 unless you have a special need
// to use more coarse sampling.
double xCalcInc = 1.0;
//Tic lengths
double xTicLen = (yMax-yMin)/50;
double yTicLen = (xMax-xMin)/50;
//Text fields for plotting parameters
JTextField xMinTxt = new JTextField("" + xMin,6);
JTextField xMaxTxt = new JTextField("" + xMax,6);
JTextField yMinTxt = new JTextField("" + yMin,6);
JTextField yMaxTxt = new JTextField("" + yMax,6);
JTextField xTicIntTxt = new JTextField("" + xTicInt,5);
JTextField yTicIntTxt = new JTextField("" + yTicInt,5);
JTextField xCalcIncTxt = new JTextField("" + xCalcInc,4);
//Text fields for methods with default equations
// inserted.
JTextField f1Txt = new JTextField(
"y = (x*x*x + 40*x*x + 100*x -6000)/100;",40);
JTextField f2Txt = new JTextField(
"y = (x * ((new java.util.Date().getTime())/" +
"(new java.util.Date().getTime())));",40);
JTextField f3Txt = new JTextField(
"y = 100.0*(cos(x/10.0)*sin(x/11.0));",40);
//Check box used to force rewrite, recompile, and reload
// of the class.
JCheckBox reloadCkBox = new JCheckBox(
"Rewrite, Recompile, and Reload the Class " +
"using the Above Equations",false);
//Panels to contain a label and a text field
JPanel pan0 = new JPanel();
JPanel pan1 = new JPanel();
JPanel pan2 = new JPanel();
JPanel pan3 = new JPanel();
JPanel pan4 = new JPanel();
JPanel pan5 = new JPanel();
JPanel pan6 = new JPanel();
JPanel pan7 = new JPanel();
JPanel pan8 = new JPanel();
JPanel pan9 = new JPanel();
//Misc instance variables
int frmWidth = 475;
int frmHeight = 430;
int width;
int height;
//A reference to a newly-loaded class.
Class loadedClass = null;
//Plot is drawn on this canvas
MyCanvas canvas;
//-----------------------------------------------------//
ScienceGraph(){//constructor
System.out.println(
"Write, compile, and load initial class file.");
//Write, compile, and load the new class based on the
// default equations in the text fields. Returns
// true on success, false on failure to compile.
boolean compileStatus = updateClass();
if(!compileStatus){
System.out.println(
"Unable to compile initial class");
}//end if
//Now build the GUI.
canvas = new MyCanvas();
JPanel southPanel = new JPanel();
//Set the layout manager to GridLayout with
// 2 rows x 4 cols
southPanel.setLayout(new GridLayout(2,4));
JPanel northPanel = new JPanel();
northPanel.setLayout(new GridLayout(4,1));
//Button for replotting the graph
JButton graphBtn = new JButton("Graph");
graphBtn.addActionListener(this);
//Populate each small panel with a label
// and a text field
pan0.add(new JLabel("xMin"));
pan0.add(xMinTxt);
pan1.add(new JLabel("xMax"));
pan1.add(xMaxTxt);
pan2.add(new JLabel("yMin"));
pan2.add(yMinTxt);
pan3.add(new JLabel("yMax"));
pan3.add(yMaxTxt);
pan4.add(new JLabel("xTicInt"));
pan4.add(xTicIntTxt);
pan5.add(new JLabel("yTicInt"));
pan5.add(yTicIntTxt);
pan6.add(new JLabel("xCalcInc"));
pan6.add(xCalcIncTxt);
pan7.add(new JLabel("f1"));
pan7.add(f1Txt);
//Make the color of the labels that identify the
// equations for f2 and f3 match the color that will
// be used to plot those two equations. f1 is
// already black so I didn't need to change its color.
JLabel f2Label = new JLabel("f2");
f2Label.setForeground(Color.RED);
pan8.add(f2Label);
pan8.add(f2Txt);
JLabel f3Label = new JLabel("f3");
f3Label.setForeground(Color.BLUE);
pan9.add(f3Label);
pan9.add(f3Txt);
//Add the populated panels, the button, and the check
// box to the control panels for the South and North
// locations.
southPanel.add(pan0);
southPanel.add(pan1);
southPanel.add(pan2);
southPanel.add(pan3);
southPanel.add(pan4);
southPanel.add(pan5);
southPanel.add(pan6);
southPanel.add(graphBtn);
northPanel.add(pan7);
northPanel.add(pan8);
northPanel.add(pan9);
northPanel.add(reloadCkBox);
//Add the sub-assemblies to the frame. Set the frame's
// location, size, and title, and make it visible.
getContentPane().add(northPanel,BorderLayout.NORTH);
getContentPane().add(southPanel,BorderLayout.SOUTH);
getContentPane().add(canvas,BorderLayout.CENTER);
canvas.setBackground(Color.WHITE);
setBounds(0,0,frmWidth,frmHeight);
setTitle(
"ScienceGraph, Copyright 2006, Richard G. Baldwin");
setVisible(true);//Make the GUI visible
//Set to exit on X-button
setDefaultCloseOperation(EXIT_ON_CLOSE);
//Get and save the size of the plotting surface
width = canvas.getWidth();
height = canvas.getHeight();
//Cycle visibility once to force the initial methods
// to be displayed. There must be a better way to
// accomplish this. Without this, the initial
// methods are not displayed at startup. This
// appears to be the result of a timing problem
// involving compilation, etc.
setVisible(false);
setVisible(true);
}//end constructor
//-----------------------------------------------------//
//Method to compile the java source code file. Returns
// true if successful, false otherwise.
private static boolean compile(String file)
throws IOException{
System.out.println("Compiling " + file);
Process p = Runtime.getRuntime().exec("javac " + file);
//Note: Sometimes the method named waitFor hangs up
// and fails to return when there is a compiler error.
// The following code is designed to deal with that
// problem. This code allows five seconds for the
// compilation to complete successfully and the
// waitFor method to return. If the waitFor method
// fails to return within five seconds, the code
// declares a "Compilation timeout error" and
// terminates the compilation process, returning false
// from the method to indicate a compiler error.
// However, it doesn't terminate the program and the
// user may correct the program and try again.
Thread myTimer = new Thread(
new Timer(Thread.currentThread(),5000));
//Start Timer thread
myTimer.start();
System.out.println("Waiting for completion");
try{
p.waitFor();//wait for completion
//The waitFor method has returned,
if(myTimer.isAlive()){
//Stop the timer.
myTimer.interrupt();
}//end if
}catch(InterruptedException e){
//The timer expired before the waitFor method
// returned and interrupted the waitFor method.
System.out.println("Compilation timeout error.");
p.destroy();
return false;
}//end catch
//The waitFor method returned in five seconds or less.
//p.exitValue() other than 0 indicates a compiler
// error.
return p.exitValue() == 0;
}//end method compile
//-----------------------------------------------------//
//Method to write, compile, and load the new class.
boolean updateClass(){
boolean compileStatus = false;
try{
//Create a File object that points to a directory
// where the class file will reside.
File targetDir = new File(
System.getProperty("user.dir")
+ File.separator +"temp" + File.separator);
//If the directory doesn't exist, make it.
if(!targetDir.exists()){
targetDir.mkdir();
}//end if
//Get an output stream for writing the source code
// for the new class file.
DataOutputStream dataOut = new DataOutputStream(
new FileOutputStream("temp" + File.separator +
"Reload02a.java"));
//Get the equations from the text fields.
String eq1 = f1Txt.getText();
String eq2 = f2Txt.getText();
String eq3 = f3Txt.getText();
//Create the source code for the new class file.
dataOut.writeBytes(
"import static java.lang.Math.*;" +
"public class Reload02a{" +
"public double f1(double x)"+
"{" +
"double y = 0.0;" +
eq1 +
"return y;" +
"}" +
"public double f2(double x)"+
"{" +
"double y = 0.0;" +
eq2 +
"return y;" +
"}" +
"public double f3(double x)"+
"{" +
"double y = 0.0;" +
eq3 +
"return y;" +
"}" +
"}"
);//end writeBytes method
dataOut.close();//Close the output file.
//Now compile the new class file
compileStatus = compile(
"temp" + File.separator + "Reload02a.java");
if(compileStatus){
System.out.println("Compile complete");
//The class has been defined and compiled. Now
// force it to be loaded.
//Get a URL object that points to the directory
// containing the class file. A File object that
// points to that directory was created earlier.
//The compiled class file for the reloadable class
// is stored in a directory that is pointed to by a
// reference variable of type File named targetDir.
//Following the recommendations in the Sun docs,
// convert the File object to a URI and convert the
// URI to a URL. Deposit the reference to the URL
// object into an one-element array of type URL.
URI uri = targetDir.toURI();
URL url = uri.toURL();
URL[] theUrl = new URL[]{url};
//Create a new class loader associated with the
// directory (URL) containing the reloadable class.
ClassLoader classLoader =
new URLClassLoader(theUrl);
// Load the specified class, creating a Class
// object that represents the class in the process.
loadedClass = classLoader.loadClass("Reload02a");
}else{
System.out.println("Probable compile error");
}//end else
}catch (Exception e){
e.printStackTrace();
System.exit(0);
}//end catch
return compileStatus;
}//end updateClass method
//-----------------------------------------------------//
//This event handler is registered on the JButton to
// cause the methods to be replotted.
public void actionPerformed(ActionEvent evt){
System.out.println("nExecuting actionPerformed");
//Set plotting parameters using data from the text
// fields.
xMin = Double.parseDouble(xMinTxt.getText());
xMax = Double.parseDouble(xMaxTxt.getText());
yMin = Double.parseDouble(yMinTxt.getText());
yMax = Double.parseDouble(yMaxTxt.getText());
xTicInt = Double.parseDouble(xTicIntTxt.getText());
yTicInt = Double.parseDouble(yTicIntTxt.getText());
xCalcInc = Double.parseDouble(xCalcIncTxt.getText());
//Calculate new values for the length of the tic marks
// on the axes.
xTicLen = (yMax-yMin)/50;
yTicLen = (xMax-xMin)/50;
boolean compileStatus = true;
if(reloadCkBox.isSelected()){
//Clear the checkbox, recompile, and reload.
reloadCkBox.setSelected(false);
compileStatus = updateClass();
}//end if on reloadCkBox.isSelected()
if(compileStatus){
//Repaint the plotting surface
canvas.repaint();
}else{
System.out.println("Unable to compile new class");
}//end else
}//end actionPerformed
//-----------------------------------------------------//
//This is an inner class, which is used to override the
// paint method on the plotting surface.
class MyCanvas extends Canvas{
//Factors to convert from double values to integer
// pixel locations.
double xScale;
double yScale;
//---------------------------------------------------//
//Override the paint method
public void paint(Graphics g){
//Calculate the scale factors
xScale = width/(xMax-xMin);
yScale = height/(yMax-yMin);
//Set the origin based on the minimum values in
// x and y
g.translate((int)((0-xMin)*xScale),
(int)((0-yMin)*yScale));
drawAxes(g);//Draw the axes
//Don't try to plot if the class has not been
// successfully loaded.
if(loadedClass != null){
try{
//Instantiate a new object of the class
Object obj = loadedClass.newInstance();
//Get a reference to an object of type Method
// that represents the instance method belonging
// to the object. The method belonging to the
// object will be invoked later by invoking the
// method named invoke on the object of type
// Method.
//The first parameter specifies the name of the
// method that will be invoked. In this case,
// the name is f1.
//The second parameter specifies that the method
// named f1 requires one parameter of type
// double.
Method methodObj = loadedClass.getDeclaredMethod(
"f1",
new Class[]{double.class});
//Invoke the method named f1 by invoking the
// method named invoke on the Method object
// passing the object's reference and a
// reference to a parameter array as parameters.
// Cast the return value from Object to Double.
// Extract the double value from the returned
// value and save it in tempY.
//Note that the double parameter value is wrapped
// in an object of type Double. It will be
// unwrapped automatically and delivered to the
// f1 method as type double.
//Similarly, when the method named f1 returns a
// value of type double, it is automatically
// wrapped in an object of type Double and
// returned by the invoke method as type Object.
// Thus, it is necessary to cast the return
// value to type Double and extrace the double
// value that it encapsulates.
//Plot the first method in black.
g.setColor(Color.BLACK);
double xVal = xMin;
int oldX = getX(xVal);
double tempY = ((Double)methodObj.invoke(obj,new
Object[]{new Double(xVal)})).doubleValue();
int oldY = getY(tempY);
//Enter a while loop using the same process to
// invoke the method named f1 several times in
// succession and plotting returned value.
while(xVal < xMax){
tempY = ((Double)methodObj.invoke(obj,new
Object[]{new Double(xVal)})).doubleValue();
int yVal = getY(tempY);
int x = getX(xVal);
g.drawLine(oldX,oldY,x,yVal);
xVal += xCalcInc;
oldX = x;
oldY = yVal;
}//end while loop
//Now do the same thing for the other two
// methods named f2 and f3.
//Get a Method object that represents the
// method named f2.
methodObj = loadedClass.getDeclaredMethod(
"f2",
new Class[]{double.class});
//Plot the second method in red
g.setColor(Color.RED);
xVal = xMin;
oldX = getX(xVal);
tempY = ((Double)methodObj.invoke(obj,new
Object[]{new Double(xVal)})).doubleValue();
oldY = getY(tempY);
while(xVal < xMax){
tempY = ((Double)methodObj.invoke(obj,new
Object[]{new Double(xVal)})).doubleValue();
int yVal = getY(tempY);
int x = getX(xVal);
g.drawLine(oldX,oldY,x,yVal);
xVal += xCalcInc;
oldX = x;
oldY = yVal;
}//end while loop
//Get a Method object that represents f3.
methodObj = loadedClass.getDeclaredMethod(
"f3",
new Class[]{double.class});
//Plot the third method in BLUE
g.setColor(Color.BLUE);
xVal = xMin;
oldX = getX(xVal);
tempY = ((Double)methodObj.invoke(obj,new
Object[]{new Double(xVal)})).doubleValue();
oldY = getY(tempY);
while(xVal < xMax){
tempY = ((Double)methodObj.invoke(obj,new
Object[]{new Double(xVal)})).doubleValue();
int yVal = getY(tempY);
int x = getX(xVal);
g.drawLine(oldX,oldY,x,yVal);
xVal += xCalcInc;
oldX = x;
oldY = yVal;
}//end while loop
}catch(Exception e){
e.printStackTrace();
System.exit(0);
}//end catch
}else{
System.out.println("Class was not loaded");
}//end if-else on loadedClass
}//end overridden paint method
//---------------------------------------------------//
//Method to draw axes with tic marks and labels in the
// color GREEN
void drawAxes(Graphics g){
g.setColor(Color.GREEN);
//Lable left x-axis and bottom y-axis. These are
// the easy ones. Separate the labels from the ends
// of the tic marks by two pixels.
g.drawString(
"" + (int)xMin,getX(xMin),getY(xTicLen/2)-2);
g.drawString(
"" + (int)yMin,getX(yTicLen/2)+2,getY(yMin));
//Label the right x-axis and the top y-axis. These
// are the hard ones because the position must be
// adjusted by the font size and the number of
// characters.
//Get the width of the string for right end of x-axis
// and the height of the string for top of y-axis.
//Create a string that is an integer representation
// of the label for the plus end of the x-axis. Then
// get a character array that represents the string.
int xMaxInt = (int)xMax;
String xMaxStr = "" + xMaxInt;
char[] array = xMaxStr.toCharArray();
//Get a FontMetrics object that can be used to get
// the size of the string in pixels.
FontMetrics fontMetrics = g.getFontMetrics();
//Get a bounding rectangle for the string
Rectangle2D r2d = fontMetrics.getStringBounds(
array,0,array.length,g);
//Get the width and the height of the bounding
// rectangle. The width is the width of the label on
// the positive x-axis. The height applies to all
// the labels, but is needed specifically for the
// label at the positive end of the y-axis.
int labWidth = (int)(r2d.getWidth());
int labHeight = (int)(r2d.getHeight());
//Label the positive x-axis and the positive y-axis
// using the width and height from above to position
// the labels. These labels apply to the very ends
// of the axes at the edge of the plotting surface.
g.drawString("" + (int)xMax,getX(xMax)-labWidth,
getY(xTicLen/2)-2);
g.drawString("" + (int)yMax,getX(yTicLen/2)+2,
getY(yMax)+labHeight);
//Draw the axes
g.drawLine(getX(xMin),getY(0.0),
getX(xMax),getY(0.0));
g.drawLine(getX(0.0),getY(yMin),
getX(0.0),getY(yMax));
//Draw the tic marks on axes
xTics(g);
yTics(g);
}//end drawAxes
//---------------------------------------------------//
//Method to draw tic marks on x-axis
void xTics(Graphics g){
double xDoub = 0;
int x = 0;
int topEnd = getY(xTicLen/2);
int bottomEnd = getY(-xTicLen/2);
//Loop and draw a series of short lines to serve as
// tic marks.
//Begin with the positive x-axis moving to the right
// from zero.
while(xDoub < xMax){
x = getX(xDoub);
g.drawLine(x,topEnd,x,bottomEnd);
xDoub += xTicInt;
}//end while
//Now do the negative x-axis moving to the left from
// zero
xDoub = 0;
while(xDoub > xMin){
x = getX(xDoub);
g.drawLine(x,topEnd,x,bottomEnd);
xDoub -= xTicInt;
}//end while
}//end xTics
//---------------------------------------------------//
//Method to draw tic marks on y-axis
void yTics(Graphics g){
double yDoub = 0;
int y = 0;
int rightEnd = getX(yTicLen/2);
int leftEnd = getX(-yTicLen/2);
//Loop and draw a series of short lines to serve as
// tic marks. Begin with the positive y-axis moving
// up from zero.
while(yDoub < yMax){
y = getY(yDoub);
g.drawLine(rightEnd,y,leftEnd,y);
yDoub += yTicInt;
}//end while
//Now do the negative y-axis moving down from zero.
yDoub = 0;
while(yDoub > yMin){
y = getY(yDoub);
g.drawLine(rightEnd,y,leftEnd,y);
yDoub -= yTicInt;
}//end while
}//end yTics
//---------------------------------------------------//
//This method translates and scales a double y value
// to plot properly in the integer coordinate system.
// In addition to scaling, it causes the positive
// direction of the y-axis to be from bottom to top.
int getY(double y){
double yDoub = (yMax+yMin)-y;
int yInt = (int)(yDoub*yScale);
return yInt;
}//end getY
//---------------------------------------------------//
//This method scales a double x value to plot properly
// in the integer coordinate system.
int getX(double x){
return (int)(x*xScale);
}//end getX
//---------------------------------------------------//
}//end inner class MyCanvas
}//end class ScienceGraph
//=======================================================//
class Timer implements Runnable{
Thread theCompilingThread;
int delay;//time interval to sleep
//-----------------------------------------------------//
Timer(Thread theCompilingThread, int delay){//constructor
this.theCompilingThread = theCompilingThread;
this.delay = delay;
}//end constructor
//-----------------------------------------------------//
//The significant functionality of all thread objects is
// written into run() method for the object.
public void run(){
try{
Thread.currentThread().sleep(delay);
}catch(InterruptedException e){
//No action is required when this sleeping thread is
// interrupted.
return;
}//end catch
//Control is transferred to here when the sleeping
// thread awakens naturally after the specified delay
// period. This means that the compilation process is
// probably hung up. Interrupt the compilation
// process and terminate the run method.
theCompilingThread.interrupt();
}//end run method
}//end Timer class
//=======================================================//
|
Copyright 2006, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
About the author
Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
