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

advertisement

AddThis Social Bookmark Button

Learning Polymorphism and Object Serialization
Pages: 1, 2, 3

The BrainyDraw Class

The BrainyDraw class is the main class that integrates all the classes explained so far. The following are some details on how each part works.



The Constructor

The constructor starts by calling the Frame parent class constructor to create a Frame object.

super("Brainy Draw");

It then instantiates a menu bar with two menus (File and Edit). The File menu has the following menu items: New, Open, Save, and Exit. The Edit menu has one menu item: Undo. After adding ActionListener objects to all the menu items, it creates a CheckboxGroup containing three check boxes for the three types of shapes. The user clicks an appropriate check box to draw a shape. ItemListener objects are added to these check boxes.

It then continues by adding the MouseListener object to panel (PanelApplet object reference). It then adds a WindowListener to itself, so the user can clicks the X button to close the window.

Drawing a Shape

To draw a shape, the user clicks on one of the three check boxes to select the type of the shape. Then the user clicks on the drawing panel and drags the mouse to start drawing. The position where the mouse is pressed becomes the first coordinate of the shape and the position where the mouse is released becomes the second coordinate. Two MouseListener methods are overridden: the mousePressed(MouseEvent me) method and the mouseReleased(MouseEvent me) method.

In the mousePressed method, you use the getX and getY methods of the MouseEvent object to get the first coordinate, which is passed to the class variables x1 and y1.

  public void mousePressed(MouseEvent me) {
    x1 = me.getX();
    y1 = me.getY();
  }

The mouseReleased method does the rest. It first takes the position where the mouse is released and assigns it as the second coordinate to the class variables x2 and y2. The rest of mouseReleased adds the shape to the Vector shapes. It knows the shape type by checking the shapeType value. For Rectangle and Oval objects, valid objects can't have two coordinates with the same abscissas (zero height) or the same ordinates (zero width). For a Line object, it is a bit relaxed. A Line will be created unless the two coordinates are the same.

  public void mouseReleased(MouseEvent me) {
    x2 = me.getX();
    y2 = me.getY();
    Shape s = null;
    if (shapeType.equals("Rectangle")) {
      // a Rectangle cannot have a zero width or height
      if (x1!=x2 || y1!=y2)
        s = new Rectangle(x1, y1, x2, y2);
    }
    else if (shapeType.equals("Line")) {
      // a Rectangle cannot have a zero width or height
      if (x1!=x2 && y1!=y2)
        s = new Line(x1, y1, x2, y2);
    }
    else if (shapeType.equals("Oval")) {
      // an Oval cannot have a zero width or height
      if (x1!=x2 || y1!=y2)
        s = new Oval(x1, y1, x2, y2);
    }
    if (s!=null) {
      panel.shapes.add(s);
      panel.repaint();
    }
  }

Upon creating a Shape, it adds it to the shapes Vector in the panel applet. It then calls the repaint method of the applet to refresh the drawing area.

The Menu functions

Now that you know how to draw a shape, it is time to see what you can do with the menu items. The actionPerformed method from the ActionListener interface that BrainyDraw class implements redirects clicks on each menu item to the processing methods. The code for the actionPerformed method is given below. Note how the getActionCommand method of the ActionEvent class returns the source object that receives the action.

  public void actionPerformed(ActionEvent ae) {
    String command = ae.getActionCommand().toString();

    if (command.equals("Exit"))
      System.exit(0);
    else if (command.equals("New"))
      openNew();
    else if (command.equals("Open"))
      open();
    else if (command.equals("Save"))
      save();
    else if (command.equals("Undo"))
      undo();
  }

Undo

The Undo menu item is connected to the undo private method. The class signature and body are as follow.

  private void undo() {
    int shapeCount = panel.shapes.size();
    if (shapeCount!=0) {
      panel.shapes.removeElementAt(shapeCount-1);
      panel.repaint();
    }
  }

The undo method simply cancels the last object drawn. This is achieved by removing the last element in the shapes Vector in the applet. To update the display, you need to call the applet's repaint method.

Open New

Open New is connected to the openNew private method. It basically starts with a new document and clears all the objects drawn, if any. The openNew method is given in the following snippet.

private void openNew() {
    panel.shapes.removeAllElements();
    panel.repaint();
  }

Because all shape objects are stored in shapes Vector, clearing them is as simple as calling the removeAllElements method of the Vector object. As usual, the applet's repaint method is called to refresh the drawing area.

Save

The Save menu item is connected to the save private method. As the name indicates, this method saves all objects to a file. It does so by serializing an object using the writeObject method of the ObjectOutputStream class. The writeObject method is a really effective method because it not only serializes the object passed to it but also all other objects referenced by that object. To be serializable, the object passed must implement the Serializable interface. If the object passed references other objects, the other objects must also implement Serializable.

The signature and body of the save method are as follows.

  private void save() {
    try {
      ObjectOutputStream out =
        new ObjectOutputStream(
          new FileOutputStream(filename));
      out.writeObject(panel.shapes);
      out.close(); // Also flushes output
    } 
    catch(Exception e) {
      e.printStackTrace();
    }
  }

Because all of the shape objects are stored in shapes Vector, you need only serialize the shapes Vector itself. All of the objects in shapes Vector will be serialized automatically. The Vector class implements a couple of interfaces, including Serializable, so you can pass shapes Vector to the writeObject method. The Rectangle, Oval, and Line objects in shapes implement the Shape interface, which itself extends the Serializable interface. Therefore, all the Rectangle, Oval, and Line objects in the shapes Vector are also serializable.

The objects are serialized to the file indicated by filename.

Open

The Open menu item is connected to the open private method. It does the opposite of what the save method does. It retrieves the objects previously saved into the file. It does so by using the readObject method of the ObjectInputStream class. The readObject returns an Object object, so in the open method it must be cast to Vector.

The signature and the body of the open method are given below.

  private void open() {
    try {
      ObjectInputStream in =
        new ObjectInputStream(
          new FileInputStream(filename));
      panel.shapes = (Vector) in.readObject();
      panel.repaint();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }

Exit

The Exit menu item makes the application exit by calling the following simple line of code.

System.exit(0);

Conclusion

You have just seen how BrainyDraw demonstrates polymorphism and object serialization in practice. Polymorphism is shown by the draw method in the Shape interface which is overridden in the Rectangle, Oval, and Line classes. When the draw method in a Shape object is called, Java knows which draw method to call. As a result, the object can draw itself correctly.

BrainyDraw also demonstrates how easy it is to do object serialization in Java. In the save method, you saw how to write objects to a file. In the open method, you saw how to read the objects back. All of these are done with one single method call, the writeObject of the ObjectOutputStream and the readObject of the ObjectInputStream. The writeObject method not only serializes the object passed to it, but all objects referenced by the passed object. To be serializable, the object passed and all other objects referenced by it must implement the Serializable interface.

Budi Kurniawan is a senior J2EE architect and author.


Return to ONJava.com.