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 »

Java Programming Notes # 1494


Preface

In my opinion, one of the most complex topics in all of Java involves dynamic loading, unloading, and reloading of classes.  Not far behind in terms of syntactical complexity is the dynamic invocation of methods using reflection.

In this lesson, I will teach you how to write a program that modifies its fundamental behavior at runtime by dynamically modifying, compiling, loading, and reloading classes.  I will also teach you how to use reflection for dynamic method invocation.

A practical example

Whenever possible, I like to demonstrate Java programming concepts by writing a small but useful program that incorporates those concepts to advantage.  I thought long and hard before finally coming up with an idea for a small but useful program that clearly demonstrates the benefits of dynamic class loading.

I settled on a program that writes, compiles, loads and executes new Java code on the fly with no requirement to stop and restart the program to incorporate the new code.  The new code is code that was completely unknown to me when I wrote the program.  The new code may also be code that is unknown to the user when she starts the program running.

A plotting program

Stated briefly, the program allows a user to enter chunks (multiple statements) of Java code into a text field during runtime.  (The reason for doing this will be explained in detail later.)  Having entered a new chunk of code, the user clicks a button, which causes the new code to be compiled, loaded, and executed as the body of a method.  The results produced by executing the method are then plotted in a Cartesian coordinate system as shown in Figure 1.  The user can repeat this process for as long as she has new chunks of code to evaluate with no requirement to stop and restart the program along the way.

From very simple to very complex

The chunk of Java code can be as simple or as complex as may be needed to satisfy the user's needs.  For example, the chunk of code could be as simple as the following equation of a straight line.

y = 6*x+5;

On the other hand, the chunk of code could be as complex as or more complex than the following which includes the definition and use of a local inner class along with the getTime instance method from the Date class and the static sqrt method from the Math class.

final double z = 65;class LocalClass{public long p = 
new java.util.Date().getTime();public double method(
double data){return sqrt(p/(data*z*1.0E09));}}LocalClass 
q = new LocalClass();y = q.method(18.6)*x;

(Line breaks were manually inserted into the example given above to force it to fit into this narrow publication format.)

Viewing tip

You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different listings and figures while you are reading about them.

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online Java tutorials.  You will find those lessons published at Gamelan.com.  However, as of the date of this writing, Gamelan doesn't maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there.  You will find a consolidated index at www.DickBaldwin.com.

I also encourage you to review the material in the lessons listed in the References section of this document.

Preview

A useful program

I claim no special expertise in the complex areas of class loaders or dynamic method invocation.  In fact, I find much of what I read on the web about these topics to be confusing and often contradictory.  However, I have learned enough about these topics to put them to work.  As an example, I will present and explain a very useful program that might be called an equation solver at the low end, and might be called an interactive code exerciser at the high end.  I will share the knowledge that I have gained on these topics with you in this two-part lesson.

A screen shot of the program GUI

I will begin by showing you a screen shot that illustrates the program in operation.  Then I will briefly explain the operation of the program.  After that, I will explain the program code in detail.

A screen shot of the program GUI is shown in Figure 1.


Figure 1

Dynamic class loading and method invocation

This program, which is named Reload02, demonstrates the use of dynamic class loading and dynamic method invocation.  When the program executes, it defines, writes, compiles, loads, instantiates, and invokes methods on an object of a new class that is designed automatically according to input from the user.

A plotting program

As you can see from Figure 1, this is a plotting program.  It is designed to automatically create and load a new class file named Reload02a.class, to instantiate a new instance (object) of the class, and to plot the behavior of three instance methods belonging to the object.  The user can repeat this cycle indefinitely, entering specifications for the new classes into the text fields at the top of the GUI, with no requirement to stop and restart the program.

Three methods

The three 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 the specifying text into three corresponding text fields at the top of Figure 1.

Creating and loading a new class

By modifying the text, checking the check box near the top, and clicking the button near the bottom, the user can cause a new class to be created, compiled, and loaded.  The new class contains three methods with behavior matching the text entered by the user.

Plotting the behavior of the methods

The behavior of each of the three methods is then plotted on a common set of Cartesian coordinates.  Once the methods are defined and plotted for the first time, the user can modify the plotting parameters in the text fields at the bottom of Figure 1 and cause the methods to be plotted again and again with different plotting parameters.

Zooming in

For example, Figure 2 shows the result of adjusting the plotting parameters so as to zoom in on the region near the origin of the graph shown in Figure 1.


Figure 2

The user can replot the graph as many times as may be needed using different plotting parameters for each new graph.

New method specifications

When the user is satisfied with the plots of those three methods, she can modify the text in the text fields near the top and start over with three new methods.  It is important to note that, due to dynamic reloading of the class, this does not require the termination and/or restarting of the program.

Modifying the text specification for one or more of the three methods, checking the checkbox, and clicking the Graph button causes a new class to be defined, written, compiled, loaded, and instantiated, and causes the three methods belonging to the object of the new class to be executed and plotted.  The new class is designed automatically according to the input provided in the text fields by the user.

Plotting format

As you can see in Figure 1 and Figure 2, 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.  As shown in Figure 1, 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 inside the plotting surface.

As shown in Figure 2, the plotting range on the positive or negative side of either dimension can easily be changed by the user.

The overall 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 overall size of the GUI in order to be able to view more detail.

User input, graphic output

As discussed above and as shown in Figure 1 and Figure 2, the program provides three text fields near the top of the GUI into which the user can enter text expressions written in java syntax.  These expressions are evaluated to produce the plots shown in the center of the GUI.  For example, when the following equation is entered into one of the text fields, the check box is checked, and the Graph button is clicked, the value of y is plotted on the vertical axis 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.  (This produces the blue curve in Figure 1 and Figure 2.)

All computations are performed as type double.  Then the results are converted to integer values for plotting.

From simple to complex

The text entered into the text fields can be as simple or as complex as is required to satisfy the user's needs.  For example, a beginning algebra student might be interested in graphing the following three equations of a straight line and comparing the different graphs:

y = 10*x + 15;
y = -5*x - 0;

y = 3*x -50;

DSP

On the other hand, someone interested in understanding Digital Signal Processing (DSP), might be interested in the plotted form of the sinusoidal equation shown above(As mentioned earlier, this is the blue curve in Figure 1 and Figure 2.)

Java programming and DSP

Someone having Java programming knowledge can enter more advanced text into the text fields, such as the complex chunk of Java code shown earlier.

For example, the following text will plot as a series of impulses, as shown in Figure 3.

if(x%10 == 0){y = 50;}else{y = -50;}

(This is another functional form that might be of great interest to someone interested in DSP because we frequently deal with impulse trains in DSP.)


Figure 3

Eliminating two of the three graphs

Note that two of the three graphs were effectively eliminated from Figure 3 by entering equations that caused the value for y to be a constant which was off the page.

Static import directive

The program uses a static import directive for the Math class requiring the use of J2SE 5.0 or later.  Therefore, it is not necessary to qualify methods from the Math class with the name of the class as in Math.cos(...).  As shown in Figure 2, all that is required is cos(...).  Thus, all of the methods of the Math class are directly available to be included in the text specifications for the three methods.

Using other classes

If it is desired to use other classes from the Java API, it is necessary to fully qualify the references to those classes using their package name, as shown in the following text specification.  (This example has no purpose other than to illustrate the syntax involved.)

y = (x * ((new java.util.Date().getTime())/
(new java.util.Date().getTime())));
This is the text that is plotted in red in Figure 1 and Figure 2(Note that a line break was manually inserted in the middle of the above equation to force it to fit into this narrow publication format.)

Design, write, compile, load, instantiate, evaluate, and plot a new class

As shown in the above figures, the three text fields used for entry of text specifications are located near the top of the GUI.  In addition, a check box is also located near the top 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 specifications that have been entered into the text fields is automatically designed, written, compiled, loaded and instantiated before the methods are evaluated and plotted.

If the check box is not checked...

If the check box has not been checked when the Graph button is clicked, a new class is not loaded. Rather, the methods in the existing class are simply re-plotted, potentially with different plotting parameters.  (You can change the plotting parameters by changing the values in the text fields at the bottom of the GUI.)

Method specifications at startup

As shown in Figure 1, the program plots the following three text expressions by default at startup in order to provide a visual confirmation that the program is working, and also to illustrate the syntax that should be used to enter text specifications 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 second equation above to force it to fit into this narrow publication format.)

A simple cubic function

The first equation shown above is a simple cubic function with roots at x values of -20, -30, and +10.

Classes other than the Math class

The second equation illustrates how classes other than the Math class can be incorporated into the text specifications.

Methods of the Math class

The third equation illustrates how methods of the Math class can be incorporated into the text specifications.

Adjusting the plotting parameters

In addition to the text fields at the top of the GUI, this program also provides the following text fields and a button labeled Graph at the bottom of the GUI.  The combination of these text fields and the button makes 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.

Output on the command-line screen

In addition to the output shown in the GUI in Figure 1, the program also produces status output on the command-line screen as shown in Figure 4.


Figure 4

In order to plot new and different equations, it is only necessary to:

  • Enter the new specifications 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.

Possible compilation problems

This will cause a new class containing three methods that represent the text specifications in the three text fields to be compiled and loaded.  If the new class is compiled and loaded successfully, the new graphs will appear on the GUI fairly quickly.  If the compilation process fails to return within five seconds, the compilation process will be aborted and a "Compilation timeout error" will be declared on the command-line screen as shown in Figure 4.

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 on the command-line screen.

If at first you don't succeed, try, try again

However, the program does not terminate as a result of a probable compile error.  You can fix the problem in the text specification and try again by checking the checkbox and clicking the Graph button.

How do you know how to fix the problem?

The source code for the new class file named Reload02a.java is deposited in a subdirectory (of the current directory) named Temp.

In the event of a compiler error, the command-line screen shown in Figure 4 does not provide any diagnostic information.  Therefore, it isn't of much help in fixing the problem.  In that case, it might be a good idea to manually recompile the source-code file named Reload02a.java in the subdirectory named Temp using the javac.exe program from Sun before attempting to repair the text specification in the text field shown in Figure 1.

In that instance, the source code should not compile successfully, but useful diagnostic information should be produced.  My approach at that point would probably be to repair the source code file in order to get a good compilation.  Having determined the nature of the problem with the specification, I would then repair the text specification in the text field of Figure 1, check the check box, and click the Graph button again.

Two programs in Part 1

I will present and explain two programs in Part 1 of this lesson.  The first program is named Reload01.  The purpose of this program is simply to illustrate the methodology for dynamically compiling and loading a new class, instantiating an object of the new class, and invoking a method belonging to that object.  This program has no particular use other than to illustrate the concepts listed above.

The second program that I will present and explain in Part 1 of this lesson is named Reload02.  This program will incorporate the three concepts listed above into the plotting program described earlier.

Three ways to do the job

There are at least three ways to make use of a class that has been dynamically loaded.  Both of the programs presented in Part 1 of this lesson will use the invoke method of the Method class (reflection) to dynamically invoke methods belonging to objects of the newly-loaded class.

Part 2 of this lesson will demonstrate the second approach for accomplishing the same results using interfaces while avoiding the complexities of reflection.  Part 2 will also describe, but probably will not demonstrate the third approach.

General Background Information

This section will provide general background information on class loaders and dynamic method invocation.

Class Loaders

What does it mean to load a class?  I can't provide a complete technical description, but I can tell you that once a class has been loaded, a special object of the class named Class will have been automatically instantiated.  The Class object represents the newly-loaded class, and you can do lots of things with it.  (See for example the introspection example in the earlier lesson entitled Swing from A to Z: Analyzing Swing Components, Part 2, GUI Setup.)

Putting class loaders to work

As I explained earlier, I claim no special expertise in the complex area of class loaders.  However, I have learned enough about them to put them to work and I will share that knowledge with you.

Two kinds of class loaders

I am led to believe that there are two kinds of class loaders:

  • The primordial class loader
  • Class loader objects

There is only one primordial class loader in a running Java program, and it is part of the Java Virtual Machine.

There may in addition be one or more class loader objects, which are created by the program at runtime.

When can a class be reloaded?

I am led to believe that once a class is loaded, it can only be reloaded if it was loaded by a class loader object.  Classes loaded by the primordial class loader cannot be reloaded.  If a class cannot be reloaded, it also cannot be replaced by a new class having the same name.

Class loading priority

After creating a class loader object, you can ask it to load classes according to certain restrictions.  However, I believe that even if you ask a class loader object to load a class, the primordial class loader is given an opportunity to load the class first.  If the primordial class loader finds the class on the classpath, it will load it.

Avoid putting your re-loadable classes on the classpath

As mentioned above, once a class is loaded by the primordial class loader, it cannot be reloaded.  Therefore, classes that you intend to load and reload using class loader objects must not be on the class path.  If they are, they will be loaded by the primordial loader, and once loaded cannot be reloaded.

The URLClassLoader class

It is possible to define your own class loader objects to deal with special class loading requirements.  However, that is not demonstrated in this lesson.  Rather, this lesson instantiates an object of the URLClassLoader class and uses it to load classes.  The URLClassLoader class is contained in the standard Java class library.

Loading a class

Once you have a URLClassLoader object, you can ask it to load a class by invoking the loadClass method on the object passing the name of the class as a String.  If the class is successfully loaded, the loadClass method will return a reference to a Class object that represents the newly-loaded class.  (The loadClass method throws the ClassNotFoundException if it can't find the class file.)

Instantiating an object of the newly-loaded class

Once you have a Class object that represents the newly-loaded class, you can instantiate an object of the class by invoking the newInstance method on the Class object.

What is the type of the new object's reference?

However, the type of the reference returned by the newInstance method is not the name of the class represented by the Class object.  Rather, in J2SE 5.0 and later, it is the generic type <T>.

Cannot cast to the true type

Furthermore, because the newly-loaded class is not on the classpath, it is not known to the compiler at compile time, so you cannot compile a statement that will cast the object's reference to the type of the newly-loaded class.

(However, to get on more familiar ground, you can save the reference as type Object if you are uncomfortable with the concept of the generic type <T>.  Prior to the release of J2SE 5.0 and its concept of Generics, the newInstance method of the Class class actually returned a reference of type Object .)

How do you invoke a method belonging to the new object?

That brings us to the next topic.  If your reference to the new object is not of the correct type, how do you invoke methods belonging to the new object?

As I mentioned earlier, there are at least three ways to invoke the new object's methods.  Both of the programs that I will explain in Part 1 of this lesson will use the invoke method of the Method class (reflection) to dynamically invoke methods belonging to the object.

Part 2 of this lesson will demonstrate the second approach, which uses a common interface while avoiding the complexity of dynamic method invocation.  Part 2 will also describe, but probably will not demonstrate the third approach.

Dynamic Method Invocation using Reflection

The Class object that represents a class provides a large number of methods that make it possible for you to get information pertaining to the class represented by the object.  For example, the method named getDeclaredMethods (plural form):

"Returns an array of Method objects reflecting all the methods declared by the class or interface represented by this Class object."

Similarly, the method named getDeclaredMethod (singular form):

"Returns a Method object that reflects the specified declared method of the class or interface represented by this Class object."

Will use the singular form of the method

Since I already know the names, return types, and parameter types for the methods of interest in the programs in this lesson, I won't need a list of all the methods belonging to the object.  Therefore, I can use the singular form, getDeclaredMethod, described above.

The Method class

The getDeclaredMethod method returns a reference to an object of the class Method.  According to the Sun documentation:

"A Method provides information about, and access to, a single method on a class or interface. The reflected method may be a class method or an instance method (including an abstract method)."

The invoke method of the Method class

Among the many methods provided by the Method class is the method named invoke.  Again, according to the Sun documentation, this method:

"Invokes the underlying method represented by this Method object, on the specified object with the specified parameters."

Makes it possible to invoke a particular method...

Thus, once you have a Method object that represents a particular method, it is possible to invoke the method named invoke on the Method object to cause that particular method belonging to a particular object to be executed.

Does not require a reference of the true type

Furthermore, that is possible even if the type of your object's reference is not the true type of the object.  For example, your reference could be the generic type Object.

(Normally, if you have a reference to an object as type Object, you cannot directly invoke a method on the object unless the method is one of the eleven methods defined in the class named Object.  However, you can get around that restriction by using the dynamic method invocation process described above.  I will demonstrate the process in the two programs in this document.)

Someone once said, "The devil is in the details."

There are quite a few tedious details involved in the use of dynamic method invocation.  I will explain some of those details in the explanation of the code in the sections that follow.

Discussion and Sample Code

I will provide a detailed explanation of the following two programs in Part 1 of this lesson:
  • Reload01
  • Reload02

The first program listed above is designed to illustrate how to use dynamic class loading and dynamic method invocation.  This is a relatively simple, but not particularly useful program.

The second program listed above is designed to incorporate dynamic class loading and dynamic method invocation into a useful but much more complex program.

The Program Named Reload01

The program named Reload01 illustrates writing, compiling, loading, recompiling, and reloading a new class, and invoking a method on an object instantiated from that newly-loaded class totally under program control at runtime.

Write a simple source code file

The program begins by automatically writing the simple class definition shown in Listing 1 into a source code file named Reload01a.java 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;
  }
}

Listing 1

The program GUI

The program displays the simple GUI shown in Figure 5.


Figure 5

This GUI has a single button labeled Recompile and Reload.  Each time you click the button, the program compiles and reloads the class named Reload01a, which was originally written as shown in Listing 1, but which may have modified by you, external to the program after it was originally written.

Then the program instantiates an object of the newly-loaded class and invokes the method named theMethod on the object.

If you modify the source code...

If you modify the source code file for the class using a text editor outside the program, the next time you click the button to cause the file to be recompiled and reloaded, the new behavior that matches your modifications will be exhibited.

Compiler errors

When you modify the source code for the class, you may inadvertently insert a syntax error.  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 and displayed on the command-line screen.

However, the program does not terminate 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 again.

The location of the class file

The location of the class file is hard-coded as the subdirectory (of the current directory) named temp, but could be anywhere (including being located at some URL on the internet) that is accessible by the program if you were to modify the program accordingly.  In other words, classes can be dynamically loaded at runtime from a variety of locations.

Watch out for the classpath

You must 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, that version of the class will be loaded by the primordial loader, and will not be reloaded when a reload is requested.

Program output

Figure 6 shows typical output produced by the program on the command-line screen.

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
Figure 6

Modified source code

The source code for the class was modified between each click of the button in Figure 5 producing the output shown in Figure 6.   Thus, the behavior of the recompiled and reloaded class was different each time the button was clicked.  (Note in particular the names and the numeric values.)  In one case, a syntax error was purposely created in such a way as to produce a compiler error.

Program testing

This program was tested using J2SE 5.0 under Windows XP.

Will explain the code in fragments

A complete listing of the program is provided in Listing 35 near the end of the lesson.  I will explain the code in fragments.

Event-driven programming code

Much of the code in this program involves event-driven programming and the Delegation or JavaBeans event model.  If you have studied my earlier lessons on these topics, this material should be familiar to you.

(This event-driven programming code is included in the program simply as a means of controlling the process and is not an inherent part of dynamic class loading.)

I won't bore you by explaining the event-driven programming code in detail, but will skip over it fairly rapidly and mention it fairly briefly.  I will concentrate on the code required to write, compile, load, recompile, and reload the new class named Reload01a, and to invoke a method on an object instantiated from that newly-loaded class totally under program control at runtime.

The Reload01 class

The beginning of the class and the main method for the class named Reload01 is shown in Listing 2.

class Reload01 extends JFrame{
  File targetDir;//location of the file named Reload01a
  
  public static void main(String[] args){
    new Reload01().writeTheClassFile();
  }//end main

Listing 2

The code in Listing 2 instantiates a new object of the class named Reload01 and invokes the method named writeTheClassFile on that object.  The purpose of the method named writeTheClassFile is to write the source code file for the class named Reload01a in a subdirectory (of the current directory) named temp.

The method named writeTheClassFile

The method named writeTheClassFile is shown in its entirety in Listing 3.

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

Listing 3

Most of the code in Listing 3 should be familiar to you, particularly if you have studied my previous lesson entitled Core Java Classes, Input and Output Streams.  Therefore, I won't discuss the code in Listing 3 any further.

The constructor

The constructor for the class named Reload01 begins in Listing 4.

  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");

Listing 4

An anonymous inner class

The boldface code in Listing 4 shows the beginning of the definition of an anonymous listener class, and the instantiation of an anonymous listener object of the anonymous class.

This code is something less than straightforward.  If you are unfamiliar with the use of anonymous inner classes, I recommend that you go back and review my earlier lesson entitled The Essence of OOP using Java, Anonymous Classes along with other lessons that I have published on this topic.  You can access those other lessons using the links at http://www.dickbaldwin.com/tocmed.htm.  You might also want to review the lessons on member classes and local classes while you are at it.

The last two lines in Listing 4 show the beginning of the definition of the actionPerformed method, which is executed each time the user clicks the button shown in Figure 5.

Invoke the compile method

Continuing in the actionPerformed method, the code in Listing 5 invokes the method named compile to cause the new source file named Reload01a to be compiled.

          try{
            boolean compileStatus = compile(
               "temp" + File.separator + "Reload01a.java");

Listing 5

The method named compile returns true on a successful compile and false otherwise.  The return value is saved in the variable named compileStatus for later use.

(If you are unfamiliar with the use of File.separator, you can look it up in the Java documentation from Sun.)

The method named compile

The code for the method named compile begins in Listing 6.

  private static boolean compile(String file)
                                        throws IOException{
    System.out.println("Compiling " + file);

Listing 6

The code in Listing 6 simply displays a message on the command-line screen. 

Executing the Java compiler in a separate process

The really important code in the compile method is shown in the single statement in Listing 7.

    Process p = Runtime.getRuntime().exec("javac " + file);

Listing 7

In case you have never done this before, Listing 7 shows how you can cause Java programs to execute other programs written in other languages.  According to Sun, the exec method of the Runtime class:

"Executes the specified string command in a separate process."

In this case, the code in Listing 7 causes the Java compiler named javac.exe to be executed to compile the file named Reload01a in the subdirectory named temp.

(I explained this process (used for an entirely different purpose) in an earlier lesson entitled Introduction to the Java Robot Class.)

An object of the Process class

As you can see, the invocation of the exec method in Listing 7 returns a reference to an object of the class Process.

Part of what Sun has to say about a Process object is shown in Figure 7.

"The ProcessBuilder.start() and Runtime.exec methods create a native process and return an instance of a subclass of Process that can be used to control the process and obtain information about it. The class Process provides methods for performing input from the process, performing output to the process, waiting for the process to complete, checking the exit status of the process, and destroying (killing) the process."
Figure 7

In this program, I will be particularly interested in the following two capabilities listed in Figure 7:

  • Waiting for the process to complete
  • Destroying (killing) the process

Will invoke the waitFor method

Once I start the compilation process, I will invoke the waitFor method on the Process object to cause the program to block until the compilation is finished.  However, I have determined experimentally that sometimes the waitFor method hangs up and fails to return when there is a compiler error.

A five-second timer

Therefore, I designed some special code to deal with that problem.  The code that I designed allows five seconds for the compilation to complete successfully and for the waitFor method to return.  If the waitFor method fails to return within five seconds, the code

  • Declares a "Compilation timeout error"
  • Invokes the destroy method on the Process object
  • Returns false to indicate a compiler error

However, it doesn't terminate the program and the user may correct the source code for the class and try again.

Complex flow of control

I will be trying to explain the program flow of control involving two threads that are executing in parallel in the paragraphs that follow.  This can become a little difficult to explain.

The compiling thread and the timer thread

For want of a better approach, I will refer to the thread that executes the code in Listing 8 as the compiling thread.  I will refer to the other thread as the timer thread.

Two browser windows would be useful

This is a place where it would definitely be useful for you to have this lesson open in two or more browser windows so that you can conveniently view two or more code listings at the same time.

Start a five-second timer

The code in Listing 8, which is executing in the compiling thread, instantiates a new Thread object of the Timer class and starts it running as a different thread, which is the timer thread.  The timer is set to run for 5000 milliseconds, or five seconds.

(If you are unfamiliar with multithreaded programming in Java, see my earlier lesson entitled Threads of Control.)

    Thread myTimer = new Thread(
                   new Timer(Thread.currentThread(),5000));
    //Start Timer thread
    myTimer.start();

Listing 8

The Timer class

The Timer class, which is instantiated in Listing 8, is shown in its entirety in Listing 9.

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
  //-----------------------------------------------------//
  
  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 9

The constructor

The constructor for the Timer class receives and saves two incoming values:

  • A reference back to the compiling thread.
  • The amount of time to count down in milliseconds.

The run method

The essential functionality of a Thread object is written into its run method, so that is what I will concentrate on.

The run method is executed after the code in Listing 8 invokes the start method on the Timer object.

(Apparently the start method takes care of some necessary tasks, and then invokes the run method on the Thread object.)

Go to sleep

The code in the run method begins by putting the timer thread to sleep for the specified delay interval (5000 milliseconds or 5 seconds in this case).  The thread will sleep for that amount of time and then wake up and continue executing statements unless awakened prematurely.

Waking up prematurely

A sleeping thread is awakened prematurely when code that is executing in some other thread invokes the interrupt method on the sleeping thread.  You will see shortly how that can happen in this program.

An InterruptedException

When a sleeping thread is awakened prematurely, it will wake up and throw an InterruptedException.  This, in turn, causes the thread to execute the code in the matching catch block.

For now, just note that when the thread is awakened prematurely, no special action is required.  The code in the matching catch block in Listing 9 simply executes a return statement, which causes the run method to terminate and the thread to die.

When the thread wakes up naturally

The case that is of particular interest to us at this point in the program is the case where the sleeping thread wakes up naturally after having slept for five seconds.  When this happens, the code below the catch block in Listing 9 is executed.

The compiling thread must be hung up

The code in the compiling thread is responsible for completing the compilation process and invoking the interrupt method on the timer thread to awaken the timer thread before the five seconds has elapsed.

Therefore, if control reaches this point in the program, the assumption is that the waitFor method that was invoked on the Process object in the compiling thread is hung up in the wait state for some reason.  It is also assumed that this is probably due to a compiler error.

Interrupt the compiling thread

Having concluded that a compiler error has probably occurred, the last statement in Listing 9 invokes the interrupt method on the compiling thread on which the waitFor method was invoked.  This causes the waitFor method to terminate and to throw an InterruptedException, which in turn causes the code in the catch block in Listing 11 to be executed.

(I will explain the code in the catch block in Listing 11 shortly.)

The sequence of events

Returning now to a review of Listing 8, note that the code in Listing 8 instantiates the object of the Timer class and causes the run method to be invoked on that object, starting the five-second timeout interval to begin on the timer thread, which is running in parallel with the compiling thread that executes the code in Listing 8.

Then control moves to the code on the compiling thread shown in Listing 10.

    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

Listing 10

Wait for the compilation to complete

After starting the five-second timer on the timer thread, the code in Listing 10 displays a message and then immediately enters a try block where it invokes the waitFor method on the Process object.  This effectively puts the compiling thread to sleep also, awaiting completion of the compilation of the class file that was started in Listing 7.

If all goes well...

If all goes well, the compilation process will have completed (either successfully or unsuccessfully) before the five-second timer wakes up, and control will have passed to the if statement shown in Listing 10.

The if statement checks to see if the timer thread is still alive (meaning that it is still sleeping in this case) and if so, invokes the interrupt method on the timer thread to cause it to wake up prematurely.  When the timer thread is awakened prematurely, it executes the code in the catch block in Listing 9.  As mentioned earlier, this causes the timer thread to die quietly.

If things don't go well...

However, if the waitFor method fails to return within the five-second timeout interval of the timer, the timer thread in Listing 9 will awaken naturally and will invoke the interrupt method on the compiling thread running in Listing 10.

(Again recall that this is based on the assumption that the compilation should easily complete within five seconds.  If five seconds has elapsed, it is assumed that the waitFor method is hung up, and it is also assumed that this is probably the result of a compiler error.)

This causes the waitFor method to terminate and to throw an InterruptedException, transferring control to the catch block shown in Listing 11.

    }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

Listing 11

Handling the InterruptedException

The code in Listing 11:

  • Displays a Compilation timeout error message,
  • Invokes the destroy method on the Process object for the purpose of terminating the compilation operation that began in Listing 7,
  • Terminates the compile method, returning a value of false to indicate a probable compiler error.

When the waitFor method returns within five seconds

The code in Listing 12 (at the end of the compile method) is executed only if the waitFor method that was called in Listing 10 returned during the five-second timeout interval.

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

Listing 12

Not necessarily a successful compilation

The fact that the waitFor method returned within five seconds does not necessarily mean that the compilation was successful.  For the amount of code being compiled in this program, one would hope that the method would always return within five seconds whether or not the compilation was successful.

(The fact that it frequently doesn't return within five seconds indicates some sort of an inter-process communication bug to me.)

The process exit value

In any event, there is an exitValue associated with the Process object.  By convention, this value will be 0 for a successful completion of the process.  The last statement in Listing 12 gets and compares the exitValue with 0, returning true for success and false for failure.

Back to the actionPerformed method

That brings us back to the code in the actionPerformed method of Listing 5 where the compile method was called with the return value of true or false being saved in a local boolean variable named compileStatus.

When the compilation is successful

Listing 13 shows the beginning of an if-else statement in the actionPerformed method that deals with the possibility that the compilation either was, or was not successful.

            if(compileStatus){//If compile was successful.
              System.out.println("Compile complete");

Listing 13

The println statement in Listing 13 is executed when the compile process was successful.

(If you would like to do so, you can skip ahead to Listing 24 to see what happens when the compile process is not successful.)

Listing 14 continues with the behavior of the program as a result of a successful compilation.

Load/reload the class

At this point, the class has been defined (once by the program and possibly more than once by the user external to the program).  The class has also been successfully compiled.

(In fact, the file may have been compiled many times with manual changes to the source code by the user in between compilations.)

In any event, a compiled class file is available at this point for loading or reloading.

Listing 14 invokes the method named reloadTheClass to cause the class file named Reload01a.class to be loaded.  The method named reloadTheClass returns a reference to a Class object that represents the newly-loaded version of the class.

              Class loadedClass = 
                                reloadTheClass("Reload01a",
                                                targetDir);

Listing 14

The code in Listing 14 passes the name of the class along with the directory containing the class file that is to be loaded to the method named reloadTheClass.

The method named reloadTheClass

The purpose of this method is to load a class that is specified by name as a String from a subdirectory specified by a File parameter named dir.

As I mentioned earlier, the class will be loaded using a class loader object of type URLClassLoader, which is a class in the standard Java class library.

The method named reloadTheClass begins in Listing 15.

  static Class reloadTheClass(String reloadableClass,
                              File dir){
    URL[] theUrl = null;

Listing 15

An array reference variable of type URL[]

Listing 15 declares an array reference variable of type URL[] initializing its value to null.  The code in the method will use the information about the directory containing the class file to create a URL object and will use that URL object to instantiate and populate the URL array object. 

Create the URL object

At this point, we need to get a URL object that points to the directory containing the class file for the class that is to be loaded.

Following the recommendations in the Sun documentation, the code in Listing 16 converts the incoming File object to a URI and then converts the URI to a URL.  The reference to the URL object is deposited in a new one-element URL array object.  The array object's reference is stored in the reference variable named theUrl, which was declared in Listing 15.

    try{
      URI uri = dir.toURI();                   
      URL url = uri.toURL();
      theUrl = new URL[]{url};
    }catch(Exception e){
      e.printStackTrace();
    }//end catch

Listing 16

Next step is to load the class

The next step in the process is to load the class, getting and returning a Class object that represents the class in the process.

Listing 17 declares a reference variable of type Class, which will be populated later with a reference to the Class object that represents the newly-loaded class.

    Class theClass = null;

Listing 17

Get a URLClassLoader object

Listing 18 instantiates a new class loader object of type URLClassLoader associated with the directory containing the class file that is to be loaded.

    try{
      ClassLoader classLoader = new URLClassLoader(theUrl);

Listing 18

An array of URL object references

Note that the constructor for the URLClassLoader class requires an array of URL object references as an incoming parameter.  The class loader object that is constructed is capable of loading classes only from the URLs that are specified by the references to URL objects stored in the array.

In this case, that list of URLs is restricted to the single directory containing the class that is to be loaded.  However, for a larger program, it may be necessary to load classes from a variety of different URLs.  If so, each of those URLs would be specified by references to URL objects stored in the array.

Load the class

Finally, the code in Listing 19 invokes the loadClass method on the URLClassLoader object, passing the String name of the class that is to be loaded as a parameter.  The class loader object will search the specified URLs for which it was constructed, looking for a class file having the specified name and will attempt to load it if it is found.

      theClass = classLoader.loadClass(reloadableClass);
    }catch (Exception e){
      e.printStackTrace();
    }//end catch

Listing 19

Success or failure in loading the class

If the load is successful, the loadClass method returns a reference to a Class object that represents the newly-loaded class.  That reference is saved in the reference variable named theClass.

If the load is unsuccessful, the method throws a ClassNotFoundException.  In this case, the exception handler simply prints a stack trace describing the problem.

Return the Class object's reference

Listing 20 returns the reference to the Class object that represents the newly-loaded class and terminates the method named reloadTheClass.

    return theClass; 
  }//end reloadTheClass

Listing 20

Note that if the load was unsuccessful, Listing 20 returns null.

Returning to the actionPerformed method

Returning once more to the code in Listing 14, which is part of the actionPerformed method being registered on the button shown in Figure 5, this code saves the reference to the Class object that represents the newly-loaded class in a reference variable named loadedClass.

Instantiate an object

Listing 21 invokes the newInstance method on the Class object to instantiate an object of the newly-loaded class, and saves the object's reference in a reference variable of type Object named obj.

              Object obj = loadedClass.newInstance();

Listing 21

Need to invoke the method named theMethod

Our objective is to invoke the method named theMethod on the object instantiated in Listing 21.  However, as explained earlier, it is not possible to invoke that method directly on the object's reference while the reference is being treated as type Object.  It is also not possible to cast the object's reference to the class type Reload01a because the compiler knows nothing about that class at compile time.  Therefore, another approach must be found to invoke the method named theMethod.

Will use reflection

This program will invoke that method using reflection.  This entails invoking the invoke method on an object of the Method class that represents the method named theMethod belonging to the object of the newly-loaded Reload01a class.

Get a Method object

Listing 22 does the following:

  • Invokes getDeclaredMethod of the Class class
  • To get a reference to an object of type Method
  • That represents the instance method named theMethod
  • That requires two parameters, one of type String and the other of type double
  • Defined in the class that is represented by the Class object
  • Whose reference is stored in the reference variable named loadedClass.

              Method methodObj = 
                 loadedClass.getDeclaredMethod(
                   "theMethod",
                   new Class[]{String.class,double.class});

Listing 22

(The method named theMethod belonging to the target object will be invoked later by invoking the method named invoke on the object of type Method, passing the target object's reference as a parameter to the invoke method.)

Note that nowhere does the code in Listing 22 mention the name of the target class, Reload01a.

The name of the target method

The first parameter passed to getDeclaredMethod specifies the name of the target method that will be invoked later.  In this case, the name is theMethod, and the name of the method is passed as a reference to an object of type String.

Number and types of parameters

The second parameter specifies the number and types of parameters required by the method named theMethod.  In this case, the method requires two parameters, one of type String and the other of type double.

Specify parameters in an array of type Class

Note that the number and types of the parameters are specified by passing a reference to an array object of type Class.  The number of elements in the array specifies the number of parameters.  In this case, the number of elements is two.  The class (or type) represented by each of the Class objects referred to by the elements in the array specifies the types of the parameters.  In this case, the types are String and double.

The target method could be overloaded

Keep in mind that a class could define many different overloaded versions of methods having the same name.  In that case, a different Method object could be obtained for each overloaded version by invoking getDeclaredMethod more than once with the first parameter being the same in each case, but the second parameter being different in each case.

Save the reference to the Method object

The reference to the Method object returned by getDeclaredMethod is saved in the reference variable of type Method named methodObj.

Cause theMethod to be invoked by reflection

Listing 23 causes the method named theMethod to be invoked on the target object by invoking the method named invoke on the Method object, passing the target object's reference and a reference to a parameter array as parameters to the invoke method.

              String returnVal = (String)methodObj.invoke(
                   obj,
                   new Object[]{"Hello",new Double(10.1)});

              System.out.println(returnVal);

Listing 23

Display the returned value

The invoke method returns type ObjectListing 23 casts the return value from Object to String and stores it in a String variable named returnVal.  Then Listing 23 displays the return value.

Passing parameters for the target method

Note that parameters for the target method are passed to the invoke method in an array of type Object.  Each parameter occupies one element in the array.  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.  In this case, the double value 10.1 is wrapped in an object of type Double for passing to the invoke method.

The primitive parameters are automatically unwrapped by the system for delivery to the target method when it is executed.

Returned values

Similarly, when the target method returns a value of a primitive type, the invoke method wraps that value in an object of the corresponding wrapper class and returns it as type Object.

As a result, the code that calls the invoke method must cast the returned reference to the appropriate wrapper type and extract the primitive value from the wrapper object.  That wasn't the case in this example because the return type for the target method was String.

Wrapping things up

Listing 24 shows the closing portions of the if-else statement that began in Listing 13.  If the compilation process fails, the value of compileStatus is false.  In that event, all of the code in the if clause of the statement is skipped, and the code in the else clause shown in Listing 24 is executed.

            }//end if on compileStatus
            else{
              System.out.println(
                               "Probable compiler error.");
            }//end else

Listing 24

The code in the else clause simply displays a message on the command-line screen announcing a Probable compiler error.  At that point, the user can correct the source code and click the button in Figure 5 to try again.

Complete the anonymous listener class definition

Listing 25 contains cleanup code for the actionPerformed method and for the anonymous listener class definition that began in Listing 4.

          }catch(Exception ex){
            ex.printStackTrace();
          }//end catch

        }//end actionPerformed
      }//end new ActionListener
    );//end addActionListener

Listing 25

Complete the constructor

Finally, Listing 26 contains the cleanup code for the constructor that began in Listing 4.

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(250,100);
    setVisible(true);
  }//end constructor

Listing 26

Listing 26 also signals the end of the program named Reload01.  And that wraps up the explanation of the program named Reload01.  Next, I will explain the more substantive program named Reload02.

The Program Named Reload02

The program named Reload02 is the plotting program that I described in some detail earlier.  Therefore, I won't repeat that description here.

Two versions of the same program

I will present and explain two versions of this program in the two parts of this lesson.  The version that I will explain in Part 1 (this document) uses reflection to access and execute the three methods (f1, f2, and f3) belonging to an object of a newly-loaded class named Reload02a.

In Part 2, I will present and explain a version of the program that uses a common interface to access and execute those methods, thus avoiding the complexity of reflection.  I will also describe, but probably won't demonstrate a third approach to dealing with the same issue.

Location of the class file

A new class file named Reload02a.class is created in a subdirectory of the current directory named temp.  This class file defines three methods named f1, f2, and f3.  The three methods are evaluated and plotted in the GUI shown in Figure 1.

If the subdirectory doesn't exist, it will be created.  If a class file having the same name already exists in that subdirectory, it will be overwritten.

Program testing

This program was tested using J2SE 5.0 under Windows XP.  Because this program uses static import directives, it requires J2SE 5.0 or later to compile and run successfully.

Will discuss in fragments

I will discuss this program in fragments.  You will find a complete listing of the program in Listing 36 near the end of the lesson.

Plotting code has been explained before

Those of you who are familiar with my earlier publications may have recognized the plotting format shown in Figure 1 as being very similar to plotting formats that I have used in earlier lessons.  In fact, the plotting code, as well as the code that manages the bottom portion of the GUI shown in Figure 1, is very similar to code that I have explained in earlier lessons.

The explanations in the earlier lessons began with the lesson entitled Plotting Engineering and Scientific Data using Java.  Therefore, I won't bore you by explaining that code again.  Rather, I will concentrate on code that is new to this lesson.  If you are unfamiliar with the plotting code, please refer back to those earlier lessons.

The class named Reload02

The class named Reload02 is shown in its entirety in Listing 27.

class Reload02{
  public static void main(String[] args){
    new ScienceGraph();
  }//end main
}//end class Reload02

Listing 27

The main method in Listing 27 simply instantiates a GUI object of the ScienceGraph class as shown in Figure 1.  Thus, most of the substance of this program is written into the GUI class named ScienceGraph.

The ScienceGraph class

The definition of the ScienceGraph class begins in Listing 28.  As explained above, I deleted the plotting code from Listing 28 for brevity.  You can view that code in its entirety in Listing 36 near the end of the lesson.

class ScienceGraph extends JFrame 
                                 implements ActionListener{

//Plotting code deleted for brevity
  
  //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;

Listing 28

Although long and tedious, the code in Listing 28 simply declares a large number of instance variables used later in the program.  The comments in Listing 28 should suffice and no further explanation of the code in Listing 28 should be required.

Implements the ActionListener interface

It is worth noting, however, that the ScienceGraph class implements the ActionListener interface.  Therefore, an object of the ScienceGraph class can and will be registered on the button in the bottom of Figure 1 to listen for action events on that button.

The constructor

The constructor for the ScienceGraph class begins in Listing 29

  ScienceGraph(){//constructor
    System.out.println(
           "Write, compile, and load initial class file.");

    boolean compileStatus = updateClass();
    
    if(!compileStatus){
      System.out.println(
                        "Unable to compile initial class");
    }//end if

Listing 29

The updateClass method is invoked in Listing 29 for the purpose of writing, compiling, and loading an initial class file.  The updateClass method returns true on success and false on a failure to compile.

The returned value from the updateClass method is stored in the boolean variable named compileStatus.  If the returned value is false, the code in Listing 29 posts an Unable to compile initial class message on the command-line screen.

The updateClass method

Setting the discussion of the constructor aside for awhile, Listing 30 shows the beginning of the updateClass method.

  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"));

Listing 30

Very similar code

Because this program uses the same methodology for compiling, loading, and instantiating classes, and for invoking the methods belonging to the objects of those newly-loaded classes, much of the code in this program is very similar to code that was explained earlier in conjunction with the program named Reload01.

(The purpose of presenting and explaining the program named Reload01 was to explain the code necessary to support those concepts in a relatively simple program.)

Won't repeat the explanation

In those cases where the code in this program is very similar to the code in the earlier program, I won't bore you by repeating the explanation.  Rather, I will simply refer you back to the earlier explanation.

For example, the code in Listing 30 is very similar to the code that was explained in conjunction with Listing 3 earlier.  Please refer to that explanation and mentally apply it to the code in Listing 30

Create the class file source code

This program creates the source code for three different class files whereas the program named Reload01 created the source code for only one class file.  In addition, the definitions of the class files in this program are based on information provided by the user in the three text fields shown at the top of the GUI in Figure 1.

The code in Listing 31 causes the source code files for the three class files to be written.

      //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.

Listing 31

Get the text from the text fields

Listing 31 begins by invoking the getText method to extract the current text from each of the three text fields at the top of Figure 1.  This text is saved in the String variables named eq1, eq2, and eq3.

Insert the text into skeleton methods

Then those three strings are concatenated into strings that provide the skeletons for three methods named f1, f2, and f3.  Those strings are written into the output file named Reload02a.java in a manner very similar to that explained for Listing 3 earlier in the lesson.

Compile and load the class file

The remainder of the updateClass method shown in Listing 32 compiles and loads the class file in a manner very similar to that explained for Listing 5 through Listing 20 earlier.

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

Listing 32

Returning to the constructor

Now, returning to the discussion of the constructor from Listing 29 above, Listing 33 assembles the GUI shown in Figure 1 by creating and adding various components to the JFrame object.  The boldface code in Listing 33 registers the object of the Reload02 class as an action listener on the Graph button shown in the bottom of the GUI in Figure 1.

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

Listing 33

Although the code in Listing 33 is long and tedious, it is straightforward and shouldn't require any explanation beyond that provided in the comments.  If you are unfamiliar with this kind of code, you may want to review my earlier tutorial lessons on the topic that you will find here and here.

The actionPerformed method

As mentioned earlier, because the Reload02 class implements the ActionListener interface, an object of the class can be, and is registered on the Graph button at the bottom of the GUI shown in Figure 1 (See the boldface code in Listing 33.)  Also, because the Reload02 class implements the ActionListener interface, and the class is not declared abstract, the class must define the interface method named actionPerformed.

The actionPerformed method, with some of the plotting code deleted for brevity, is shown in Listing 34.

  public void actionPerformed(ActionEvent evt){
    System.out.println("nExecuting actionPerformed");

//Plotting code deleted for brevity.

    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

Listing 34

This event handler is registered on the button to cause the methods to be re-plotted.  In addition, if the checkbox at the top of the GUI is checked, the class is rewritten, recompiled, and reloaded before being evaluated to produce the plots.

If the check box is checked...

If the check box at the top of the GUI is checked, the updateClass method discussed earlier is invoked to recompile and reload the class.

If the recompilation is successful, the repaint method is invoked on the Canvas object to cause the methods named f1, f2, and f3 to be evaluated and plotted.  If the recompilation is unsuccessful, an Unable to compile new class message is posted on the command-line screen and no attempt is made to evaluate and plot the methods.

The remaining program code

All of the remaining code in the program, including the overridden paint method, is either plotting code that is very similar to code that was explained in earlier lessons, or code that is very similar to code that was explained earlier in this lesson in conjunction with the program named Reload01.  Therefore, I won't explain the remaining code in the program.  You can view the remaining code in Listing 36 near the end of the lesson.





Page 1 of 2



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel