July 31, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Dynamic Loading/Reloading of Classes and Dynamic Method Invocation, Part 1

  • May 16, 2006
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

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:

Complete Program Listing

Complete listings of the programs discussed in this lesson are shown in Listing 35 and Listing 36 below.
 
/*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
//=======================================================//

Listing 35

Listing 36

/**********************************************************
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
//=======================================================//

Listing 36


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.

Baldwin@DickBaldwin.com





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel