ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Teaching Java the Extreme Way
Pages: 1, 2, 3

The Harness

Like all teachers, I'm sure that I learn more when I teach than my students do. In constructing this example, I needed to learn about how classes are loaded in Java. The original version of the test harness was a double-clickable .jar file that the students needed to quit and restart each time they made adjustments to the Friend class. I added the button and found that I didn't really understand ClassLoaders. I thank Malcolm Davis and Bill Venners for their helpful suggestions -- a lot of what I know about programming in Java is the result of ongoing conversations with Malcolm and Bill. I've told my editor that any mistakes with the code are theirs -- but I'm sure he knows better. Check out Bill's Web site, www.artima.com, for more information on the Java Virtual Machine and class loading. In this section, I'll present the code for the harness.



The Main Frame

As you saw in the figures, the application is a JFrame with a text area of some sort and a button. Here's the code for the JFrame.

import javax.swing.JFrame;
import java.awt.BorderLayout;

public class HelloFrame extends JFrame{

   private HelloFrame(){
      setUpLookOfFrame();
      setUpFramesComponents();
      setVisible(true);
   }

   private void setUpFramesComponents() {
      MessagePane reporter = new MessagePane();
      getContentPane().add(reporter,BorderLayout.CENTER);
      getContentPane().add(new TestButton(reporter),BorderLayout.SOUTH);
   }

   private void setUpLookOfFrame() {
      setTitle("Hello World");
      getContentPane().setLayout(new BorderLayout());
      setSize(350,330);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
   }

   public static void main(String[] args){
      new HelloFrame();
   }
}

In setUpFramesComponents(), the text area and button are created and added to the frame. The rest of the code just tweaks the look of the application.

The Button

The button doesn't do very much. It sits around and waits to be pressed. When it is pressed, it tells the text area to figure out what message to display.

import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class TestButton extends JButton implements ActionListener {
   private MessagePane reporter;

   public TestButton(MessagePane reporter){
      super("Press here to check progress");
      addActionListener(this);
      this.reporter = reporter;
      setSelected(true);
   }
   public void actionPerformed(ActionEvent actionEvent){
      reporter.testProgress();
   }
}

The Text Area

The text area is a JEditorPane, so that HTML can be displayed. The testProgress() method contains a call to the custom class loader. There is an attempt to create an object of type Friend and then to invoke the getName() method. If the return type of getName() is a String, then we can go ahead and display the "congratulations" message. There are many places in this process where things could go wrong. For each case we can anticipate, we display an appropriate message. It is still possible that something we haven't anticipated will go wrong. We also display an appropriate message and provide a stack trace so that the instructor can try to figure out what went wrong.

import javax.swing.JEditorPane;

public class MessagePane extends JEditorPane{

   public MessagePane(){

      super("text/html","");
      welcomeUser();
   }

   public void testProgress(){

      try {
         Class friend = (new FriendLoader()).getFriend();
         String name = (String)friend.getMethod("getName",null).invoke
		               (friend.newInstance(),null);
         if (name == null){
            stringNotReturned();
         } else greetFriend(name);
      }
      catch (NoSuchMethodException e) {
         methodNotFound();
      }
      catch(ClassCastException e){
         stringNotReturned();
      }
      catch (NullPointerException e){
         classNotFound();
      }
      catch (Exception e){
         e.printStackTrace();
         unexpectedHappened();
      }
   }

   private void welcomeUser(){ //...
   }

   private void greetFriend(String name){ //...
   }

   private void classNotFound(){ //...
   }

   private void methodNotFound(){ //...
   }

   private void stringNotReturned(){ //...
   }

   private void unexpectedHappened(){ //...
   }
}

You can actually stop here and not create the class loader -- just replace the call to the class loader with a call to Class.forName(). Your application would be smaller, but it would require restarting it every time students want to check their work. This isn't a big deal, but you should probably get rid of the button and just call testProgress() in the constructor for HelloFrame.

The Class Loader

The only public method in the FriendLoader class is getFriend(). The getFriend() method calls the overridden findClass() method to try to find and load the Friend class. The key to creating your own class loader is in overriding this findClass() method. If you can find the class file, you then copy the byte code into an array that you pass, along with the class name, to the defineClass() method. Here's what this looks like:

import java.io.*;

public class FriendLoader extends ClassLoader {

   public Class getFriend() throws ClassNotFoundException {
      return findClass("Friend");
   }

   protected Class findClass(String className) throws ClassNotFoundException {
      byte classData[]= getFileData();
      if (classData == null) {
         throw new ClassNotFoundException();
      }
      return defineClass(className, classData,0,classData.length);
   }

   private byte[] getFileData()  {
      BufferedInputStream bufferedInputStream = getBufferedInputStream();
      ByteArrayOutputStream byteArrayOutputStream = 
	           getByteOutputFromFileInput(bufferedInputStream);
      return byteArrayOutputStream.toByteArray();
   }

   private BufferedInputStream getBufferedInputStream(){
      BufferedInputStream bufferedInputStream = null;
      try {
         File friend = new File("Friend.class");
         FileInputStream fileInputStream = 
		        new FileInputStream(friend.getPath());
         bufferedInputStream = new BufferedInputStream(fileInputStream);
      }
      catch (FileNotFoundException e){
         e.printStackTrace();
      }
      return bufferedInputStream;
   }

   private ByteArrayOutputStream getByteOutputFromFileInput
                (BufferedInputStream bufferedInputStream){
      ByteArrayOutputStream byteArrayOutputStream = 
	            new ByteArrayOutputStream();
      try {
         int c = bufferedInputStream.read();
         while(c != -1) {
            byteArrayOutputStream.write(c);
            c = bufferedInputStream.read();
         }
      }
      catch (IOException e){
         return null;
      }
      return byteArrayOutputStream;
   }
}

The Challenge

As a next step, I'd like the students to write to an interface. In this case, I don't care what they call their class; in fact, I don't care how they store their name information. This will, of course, require some changes to the harness. All of this is a topic for another day. For now, if your goal is to teach object-oriented programming and you plan to use Java as your language in an introductory course, how might you change or extend this proposed initial assignment? Next month, we'll continue to look at the test first approach to programming.

Daniel H. Steinberg is the editor for the new series of Mac Developer titles for the Pragmatic Programmers. He writes feature articles for Apple's ADC web site and is a regular contributor to Mac Devcenter. He has presented at Apple's Worldwide Developer Conference, MacWorld, MacHack and other Mac developer conferences.


Return to ONJava.com.