Advanced Topics

Swing

JComponents (i.e., Swing components) are peerless or lightweight components:

Building GUIs with swing components allow programmers to decouple the GUI from its look-and-feel. Java provides a default look-and-feel called Metal.

Here are some import statements we commonly need:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

"javax" stands for "Java extension". Swing used to be an extension to the core package, but now it belongs to the core. The package name "javax" is retained for backward compatibility.

Closeable Frames

Let's begin with the Swing version of our MainFrame class. Instances of this class are closeable frames centered in the middle of the screen:

public class MainJFrame extends JFrame {

   protected int screenHeight;
   protected int screenWidth;

   class Terminator extends WindowAdapter {
      public void WindowClosing(WindowEvent e) {
         System.exit(0);
      }
   }

   public MainJFrame() {
      addWindowListener(new Terminator());
      setTitle("Main Window");
      
      Toolkit tk = Toolkit.getDefaultToolkit();
      Dimension d = tk.getScreenSize();
      screenHeight = d.height;
      screenWidth = d.width;
      setSize(screenWidth/2, screenHeight/2);
      setLocation(screenWidth/4, screenHeight/4);
   }

   // test harness
   public static void main(String[] args) {
      MainJFrame frame = new MainJFrame();
      frame.show();
   }
}

Graphics

We don't redefine the Frame.paint() method in Swing. Instead, we redefine JPanel.paintComponent() method of a JPanel, then add this panel to a JFrame.

Let's redo the polygon example:

The polygon is drawn onto the graphical context of a JPanel:

public class GraphicsPanel extends JPanel {

   public int sides = 12;
   public int red = 255, green = 0, blue = 200;

The paintComponent method is similar to the paint method. It receives a graphical context as an argument:

   public void paintComponent(Graphics g) {
   
      super.paintComponent(g);
      
      Dimension d = getSize();
      Insets in = getInsets();
      int clientWidth = d.width - in.right - in.left;
      int clientHeight = d.height - in.bottom - in.top;
      int xUpperLeft = in.right;
      int yUpperLeft = in.top;
      int xLowerRight = xUpperLeft + clientWidth;
      int yLowerRight = yUpperLeft + clientHeight;
      int xCenter = xUpperLeft + clientWidth/2;
      int yCenter = yUpperLeft + clientHeight/2;

      g.setColor(new Color(red, green, blue));
      Polygon p = new Polygon();
      double radius = Math.min(clientHeight, clientWidth)/2;
      double angle = 2 * Math.PI/sides;
      for(int i = 0; i < sides; i++)
         p.addPoint(
            (int)(xCenter + radius * Math.cos(i * angle)),
            (int)(yCenter + radius * Math.sin(i * angle)));
      g.drawPolygon(p);
      
      g.setColor(Color.black);
      String s = "# sides = " + sides;
      FontMetrics fm = g.getFontMetrics();
      int w = fm.stringWidth(s);
      int h = fm.getHeight();
      g.drawString(s, xCenter - w/2, yCenter - h/2);
   }

Let's add a mouse click handler that increments the number of sides of the polygon, then forces the panel to repaint itself:

   class MouseHandler extends MouseAdapter {
   
      public void mouseClicked(MouseEvent e) {
         sides = Math.max(3, (sides + 1) % 20);
         repaint();
      }
   }
   
   public GraphicsPanel() {
      addMouseListener(new MouseHandler());
   }

Another important difference between Swing and AWT is that we don't add components to JFrames. A JFrame actually consists of multiple layers:

In Swing, components are added to the content pane:

   public static void main(String[] args) {
      MainJFrame frame = new MainJFrame();
      frame.setTitle("Polygon");
      Container contentPane = frame.getContentPane();
      contentPane.add(new GraphicsPanel());
      frame.show();
   }
} // GraphicsPanel

Actions

Our next application is a calculator that allows users to perform arithmetic operations by pushing buttons or by selecting items from a menu:

Recall the Interpreter2 framework developed earlier. In this framework we decoupled the expressions entered by the user from the commands they represented. By treating commands as distinct objects we could implement an undo/redo mechanism. Each command provided execute() and undo() methods with a Context parameter. In a graphical user interface we can also associate the same commands with several controls. This is the basic idea behind Swing. In Swing, commands are called actions. An action is an object that instantiates any class that implements the Action interface:

interface Action {
   void putValue(String key, Object value);
   Object getValue(String key);
   void actionPerformed(ActionEvent evt);
   // etc.
}

Java provides a default implementation of this interface:

class AbstractAction implements Action { ... }

In our calculator example we will need actions for each arithmetic operation. For example, the AddAction extends the AbstractAction class. AddActions maintain a pointer to a calculator (this is the context or model). The property list of an action maintains information such as name, tool tips, help text, hot keys, etc. The actionPerformed() method is analogous to the command's execute() method:

class AddAction extends AbstractAction {

   private Calc model;

   public AddAction(Calc m) {
      model = m;
      putValue(Action.NAME, "Add");
      // tooltip text:
      putValue(Action.SHORT_DESCRIPTION, "add args");
   }

   public void actionPerformed(ActionEvent evt) {
      double arg1 = model.getArg1();
      double arg2 = model.getArg2();
      double result = arg1 + arg2;
      model.setResult(result);
   }
}

An ActionButton is a JButton that has an Action object as a listener:

class ActionButton extends JButton {
   public ActionButton(Action a) {
      setText((String) a.getValue(Action.NAME));
      addActionListener(a);
      setToolTipText((String) a.getValue(Action.SHORT_DESCRIPTION));
   }
}

We are now ready for the calculator, which encapsulates three text fields and four action buttons:

public class Calc extends MainJFrame {

   private JTextField arg1, arg2, result;
   private ActionButton addButton, subButton, mulButton, divButton;

   public double getArg1() {
      String a = arg1.getText();
      Double d = new Double(a);
      return d.doubleValue();
   }

   public double getArg2() {
      String a = arg2.getText();
      Double d = new Double(a);
      return d.doubleValue();
   }

   public void setResult(double d) {
      String r = "" + d;
      result.setText(r);
   }

The calculator constructor creates some action objects. These objects are associated with buttons and menu items:

   Calc() {

      setTitle("Calculator");
      Container contentPane = getContentPane();
   
      // make some actions:
      Action addAction = new AddAction(this);
      Action subAction = new SubAction(this);
      Action mulAction = new MulAction(this);
      Action divAction = new DivAction(this);
      
      // make some panels:
      JPanel northPanel = new JPanel(new GridLayout(2, 2));
      JPanel centerPanel = new JPanel();
      JPanel southPanel = new JPanel(new GridLayout(1, 4));

      // make buttons:
      addButton = new ActionButton(addAction);
      JPanel addPanel = new JPanel();
      addPanel.add(addButton);
      southPanel.add(addPanel);
      // etc.

      // make some labels:
      JLabel arg1label = new JLabel("argument 1");
      JPanel label1Panel = new JPanel();
      label1Panel.add(arg1label);
      northPanel.add(label1Panel);
      // etc.

      // make some text fields:
      arg1 = new JTextField(10);
      JPanel arg1Panel = new JPanel();
      arg1Panel.add(arg1);
      northPanel.add(arg1Panel);
      // etc.

      // add panels to frame:
      contentPane.add(northPanel, "North");
      contentPane.add(centerPanel, "Center");
      contentPane.add(southPanel, "South");

      // make a menu:
      JMenu m = new JMenu("Calc");
      m.add(addAction);
      m.add(mulAction);
      m.add(subAction);
      m.add(divAction);
      JMenuBar mbar = new JMenuBar();
      mbar.add(m);
      setJMenuBar(mbar);
   }

Here's main():

   public static void main(String[] args) {
      Calc calc = new Calc();
      calc.show();
   } // main

} // Calc

Reflection

Runtime Type Information in Java

The Java byte code compiler uses type information provided by program declarations to check for type errors and select variants of overloaded functions, long before the program runs. But type information isn't discarded after compilation. Like LISP, type information is still available at runtime. (Of course this makes Java programs larger, but at least there is no runtime type checking.)

Recall that all Java classes-user or system defined -specialize the Object base class. In addition, Java provides Class, Method, and Field meta classes that correspond to our RuntimeClass, MemberFunction, and MemberVariable meta classes. Every Java object inherits from the Object base class a getClass() function that returns a reference to an appropriate Class meta-level object:

For example, assume the following Java class declarations are made:

class Document { ... }
class Memo extends Document { ... } // Memos are Documents
class Report extends Document { ... } // Reports are Documents

In other words, Memo and Report are specializations of Document:

We can think of Document, Report, and Memo as base classes and Class, Method, and Field as meta classes.

Assume x is declared as a Document reference variable:

Document x; // x can hold a reference to a document

Then we can assign references to Memo or Report objects to x:

x = new Report(); // x holds a reference to a Report object
x = new Memo(); // now x holds a reference to a Memo object

At some point in time we may be unsure if x refers to a Memo object, a Report object, or perhaps some other special type of Document object (Letter? Contract? Declaration of Independence?) This isn't a problem. Programmers can fetch x's runtime class object by calling the inherited getClass() function:

Class c = x.getClass();

For example, executing the following Java statements:

Document x = new Report();
Class c = x.getClass();
System.out.println("class of x = " + c.getName());
c = c.getSuperclass();
System.out.println("base class of x = " + c.getName());
c = c.getSuperclass();
System.out.println("base of base class of x = " + c.getName());
x = new Memo();
c = x.getClass();
System.out.println("now class of x = " + c.getName());

produces the output:

class of x = Report
base class of x = Document
base of base class of x = java.lang.Object
now class of x = Memo

Notice that the name of the Object class is qualified by the name of the package (i.e., the namespace) that it belongs to, java.lang.

Java reflection goes further by introducing Method and Field meta classes. (In Java member functions are called methods and member variables are called fields.) Let's add some fields and methods to our Document class:

class Document
{
public int wordCount = 0;
public void addWord(String s) { wordCount++; }
public int getWordCount() { return wordCount; }
}

Next, we create a document and add some words to it:

Document x = new Document();
Class c = x.getClass();
x.addWord("The");
x.addWord("End");

Executing the following Java statements:

Method methods[] = c.getMethods();
for(int i = 0; i < methods.length; i++)
   System.out.println(methods[i].getName() + "()");

Field fields[] = c.getFields();
try
{
   System.out.print(fields[0].getName() + " = ");
   System.out.println(fields[0].getInt(x));
}
catch(Exception e)
{
   System.out.println("fields[0] not an int");
}

produces the output:

getClass
hashCode
equals
toString
notify
notifyAll
wait
wait
wait
addWord
getWordCount
wordCount = 2

Notice that the methods inherited from the Object base class were included in the array of Document methods. (There are three overloaded variants of the wait() function in the Object class.) If wordCount is declared private, as it normally would be, then its value doesn't appear in the last line:

wordCount =

Dynamic Instantiation in Java

Java reflection allows objects to be created dynamically. Assume the following declarations have been made:

String cName; // holds a class name
Object x;
Class c;

Executing the Java statements:

try
{
   cName= "Horn";
   c = Class.forName(cName); // find & load a class
   x = c.newInstance();
   c = x.getClass();
   System.out.println("class of x = " + c.getName());

   cName= "Drum";
   c = Class.forName(cName);
   x = c.newInstance();
   c = x.getClass();
   System.out.println("now class of x = " + c.getName());
}
catch(Exception e)
{
   System.out.println("Error: " + e);
}

produces the output:

class of x = Horn
now class of x = Drum

Calling Class.forName("Horn") locates the file containing the declaration of the Horn class, compiles it if necessary, then dynamically links it into the program.

Containers