Designing a JDBC/Swing Application

The first half of this tutorial will introduce some user interface concepts and the Swing achitecture.  It will teach you only the basics of Swing.  After reading this section, you are encouraged to take the Swing tutorial at Sun.  Feel free to jump directly to the second half of this tutorial if you already know Swing, but make sure that you are familar with the topics listed in the Before You Move On section.  The second half of this tutorial will present a sample JDBC/Swing application and walk you through its design.

 

User Interfaces

A user interface allows two way communication between the user and the computer.  There are various types of user interfaces.  Some of the more popular ones are command line, text-based menu, and graphical.  An example of a command line interface is the interface of a Unix shell.  The interface of the sample program in the first JDBC tutorial is an example of a text-based menu interface.  Most programs on the Windows platform use a graphical user interface (GUI).  This section will describe the control flow of a text-based menu program and a GUI one.

 

Control Flow of a Text-Based Menu Program

The flow chart below shows the control flow of a text-based menu program:
 


 

Figure 1.  Control Flow of a Text-Based Menu Program
 

At the start step, the program begins its execution.  It may perform such initialization tasks as instantiating classes and connecting to a database.  It then displays a menu.  The program will need to wait until the user makes a selection.  After the user makes a selection, the program will process the user's input to determine which menu command was selected.  If the user selected quit, the program goes to the end step.  Before the program terminates, it may perform such "clean up" tasks as deallocating memory and disconnecting from the database.  If the user selected a command other than quit, the command's task is performed.  For example, if the selection was the show branch command, the program will query the database and then display the results.  The program then redisplays the menu and waits for the user to make another selection.

 

Control Flow of a GUI Program

The flow chart below shows the control flow of a GUI program:
 


 

Figure 2.  Control Flow of a GUI Program
 

The start and end steps are basically the same as the ones for a text-based menu program.  After a GUI component (aka widget) is created, it is positioned on the screen by a layout mananger (layout managers will be discussed in the Swing section).  Examples of GUI components include buttons, menus, and windows.  Before we explain the next step in the flow chart, we need to provide you with some background information on events.

An event is, for a lack of a better word, "something" that can happen to a GUI component.  An event may or may not result in a change in the component's appearance or state.  For example, usually, a mouse click on a button changes the appearance of the button so that it looks depressed.  However, usually, when the mouse pointer is moved over a button, the button does not change its appearance.  Nevertheless, this is still considered an event.  The reason is that the appearance change is not caused by the event.  It is caused by the thing that responds to the event (you will find out what this thing is soon).  If it helps, you can consider an event as the triggering of something, and that something is implemented by the thing that responds to the event.

Each event has its own set of triggers that result in the firing (creation) of the event.  Generally, user input act as the triggers.  For example, when a mouse button is clicked while the mouse pointer is over a button, a mouse event is triggered.  This results in the button firing a mouse event.  The event handling tutorial at Sun has a list of the types of events that each Swing component can fire.  Anything that can fire an event is an event source.  Not all event sources are GUI components, but most are.

An event that is fired will be "uneventful" if there is nothing to respond to it.  The things that respond to events are event listeners (aka event handlers or callbacks).  Generally, you need to register an event listener with an event source so that the event source can inform the listener about any events.  This is the third step in the flow chart.  Note: some GUI components have pre-registered event listeners that handle standard events like the example of a mouse click changing the appearance of a button.

In the next step, the program simply waits for events.  When an event occurs, the registered event listeners are notified, so that they can process the event.  If the user decided to quit, an event listener should terminate the program.  Otherwise the event listener that corresponds to the selected command will be invoked.  After the listener performs its task, the program waits for another event.

 

Swing

This section will cover the design of Swing components, event handling, and layout management.  But before we begin, let's talk about what Swing is.

Swing is a GUI application programming interface that is included in the Java development kit.  It is the javax.swing package.  The old GUI API for Java was AWT.  Swing is built on top of AWT, so many AWT features are found in Swing.  In particular, Swing uses AWT's event handling model and layout managers.  Swing also adds new components, event types, and layout managers.  Moreover, Swing includes replacements for most AWT components.  The name of a replacement component begins with a 'J' followed by the name of the corresponding AWT component.  Generally, it is not a good idea to mix AWT components with Swing components because the AWT components may cover up the Swing ones.  You should use the replacements instead.

Swing is actually a member of a set of GUI technologies called JFC (Java Foundation Classes).  In addition to Swing, JFC provides classes that support devices for the physically and mentally impaired, that customize the appearance and behavior of GUI components, that provide enhanced 2D graphics support, and that provide drag and drop support.  This tutorial will cover only Swing.

Since Java is an object-oriented language, understanding a component's inheritance hierarchy and the interfaces that it implements are important in understanding where the component gets its functionality (if you do not know what is an interface, check out the basic Java tutorial at Sun).  For example, the class JComponent provides the base functionality for most components because almost all Swing components are derived from this class.  Some of things that JComponent provides include support for borders, component sizing, component painting, mouse over tool tip text, actions (which is a combination of a GUI component and an action event listener), binding keystrokes to actions, and double buffering (which prevents a component from flickering when it is continuously painted as in animation).  Here is the inheritance hierarchy of JComponent:
 


 

Figure 3.  JComponent Hierarchy
 

Note that every JComponent is a container.  This means that a JComponent can contain other JComponents.

For a pictorial of all components and links to their tutorials, click here.

 

Model-View-Controller Architecture

The design of Swing components is based on the Model-View-Controller (MVC) design pattern.  This diagram describes it:
 


 

Figure 4.  The Model-View-Controller Architecture
 

The model encapsulates an object's data.  The view represents the appearance of the object.  It renders the data in the model.  When the model's data changes, the model notifies the view, so that the view can update itself.  The controller represents the behavior of the object.  When the user interacts with the view, the input is transferred from the view to the controller.  The controller then updates the model's data appropriately based on the semantics of the user input.  The purpose of a model-view-controller architecture is to decouple an object's data, appearance and behavior.  To change the interface ("look and feel") of an object, you only have to change the view and controller.  The figure below shows how the model-view-controller architecture applies to Swing components:
 


 

Figure 5.  The Model-View-Controller Architecture of a Swing Component
 

It is difficult to separate the appearance and behavior of a GUI component because a user's interaction with a component affects both the view and the controller.  For example, when a mouse button is clicked while the mouse pointer is over a button, the button should react to the input.  This reaction should change the appearance of the button so that the button appears depressed.  Because of this tight coupling, the view and controller are combined.  The combined entity is known as a UI Delegate.

For most Swing components, you can ignore the models.  However, for more complex components like JTable and JTree, you need to be aware of them.  You may even have to customize them because the default ones that are provided may not be sufficient.  Generally, customizing a model involves implementing a type of model interface, or extending an abstract model class, or extending the default model.  The sample JDBC/Swing program in this tutorial contains a custom table model.

Some components can have more than one model.  For example, there are models that store application data, such as the data that appears in a table.  There are also models that store the GUI properties and GUI state of a component, such as the size and resizibility of a particular table column, and the cells that are currently selected in a table.

Generally, there is one class for the Swing component itself, one class for its UI delegate, and one class for each of its models.  The class that represents the component acts as an interface to the delegate and model(s).  In addition to the UI delegate, some Swing components, such as JTable and JTree, use other objects to help render the model, and control how the user can interact with the component.  These objects are known as renderers and editors respectively.  Like models, you can customize a renderer and editor, or you can use the default ones.

 

Event Handling

The Swing event handling model is based on the observer-subject design pattern.  An event listener (observer) registers with an event source (subject) for a particular event, so that the listener will be notified when that event is fired by the event source.  An event source can be a GUI component or a non-GUI component, such as a data model.  An event listener is any object that implements the XXXListener interface where XXX is the name of the event minus the "Event" suffix.  For example, the class below implements the MouseListener interface so that it can handle a MouseEvent, which is triggered by a mouse button press on the event source, or mouse button release, or mouse button click (press and release), or mouse pointer movement into the bounds of the event source (mouse enter), or mouse pointer movement out of the bounds of the event source (mouse exit):
 
class ABC implements MouseListener
{
  . . .
  public void mousePressed(MouseEvent e)
  {
    . . .
  }
  public void mouseReleased(MouseEvent e)
  {
    . . .
  }
  public void mouseClicked(MouseEvent e)
  {
    . . .
  }
  public void mouseEntered(MouseEvent e)
  {
    . . .
  }
  public void mouseExited(MouseEvent e)
  {
    . . .
  }
}


Any class that implements the MouseListener interface must implement all 5 methods, where each method represents a MouseEvent trigger.  These methods accept a MouseEvent argument.  By calling the methods of the MouseEvent object, the listener can obtain information about the event.  For example, to obtain the event source, the getSource() method can be used (in fact all event objects have this method).  If the class does not want to respond to a MouseEvent from a particular trigger, the class can leave the trigger's method body empty.

Requiring a class to implement all the methods of an interface leads to unnecessary code if some of the method bodies will be empty.  Therefore, the Swing development team created adapter classes for some of the interfaces.  An adapter class provides an empty implementation of each method in an interface.  If a class only needs to implement one method, it can do so by extending the adapter class and overriding the desired methods (the class does not need to implement the other methods because it inherits them).  For example, if class ABC only needs to implement the mouseClicked() method, it can extend the MouseAdapter class:
 

class ABC extends MouseAdapter
{
  . . .
  public void mouseClicked(MouseEvent e)
  {
     . . .
  }
}


In order for a listener to be notified of an event, it needs to register with an event source by calling the event source's addXXXListener() method.  The only argument to addXXXListener() is the event listener.  This method adds the event listener to a special list stored by the event source.  The following registers abcObject, which is an object of class ABC, for mouse events from event source aSubject :
 

aSubject.addMouseListener(abcObject);


Continuing with the above example, when a mouse event occurs in aSubject, for example a mouse click, aSubject creates ("fires") a MouseEvent object.  It then extracts from its list of observers, references to each MouseListener.  The mouseClicked() method of each MouseListener including abcObject's is called.  The newly created MouseEvent object is passed as an argument to mouseClicked().

The event model that we have just described is called the delegation event model.  Here is an interaction diagram that summarizes it:
 


 

Figure 6.  Delegation Event Model
 

Note:  notifies() is just a placeholder that represents the actual method called by the event source.  The method called depends on the event (e.g., MouseEvent) and the event trigger (e.g. mouse click).

 

Low-Level Events and Semantic Events

Some components map low-level events, such as mouse events and keyboard events, to high-level events.  These mappings depend on the type of component.  For example, when a mouse button (usually the left) is clicked while the mouse pointer is over a button, an ActionEvent in addition to a MouseEvent is created (ActionEvent is a high-level event).  All registered ActionListeners and MouseListeners are then notified (the ActionListener's actionPerformed() method is called).  It is recommended that objects listen for high-level events instead of low-level ones.  Here is an example that illustrates why:  a class that implements the ActionListener interface can register with a JButton (the class that defines a button) and be notified by the button whenever a mouse click occurs or the button's keyboard short-cut key is pressed.  The ActionListener does not have to implement the listener interface for both mouse events and keyboard events.  It only has to implement the ActionListener interface.  For information on which types of listeners and thus events that each GUI component supports, check out the event handling tutorial at Sun.

 

Inner Classes and Anonymous Inner Classses

Inner classes and anonymous inner classes are useful for creating event listeners.  Check out the basic Java tutorial at Sun if you do not know what is an inner class and what is an anonymous inner class.  Here is an example of using an inner class:
 
class OuterClass
{
  . . .
  swingComponent.addMouseListener(new InnerClass());
  class InnerClass extends MouseAdapter
  {
    public void mouseClicked()
    {
       . . .
    }
       . . .
  }
}


Here is an example of using an anonymous inner class to do same thing:
 

swingComponent.addMouseListener(new MouseAdapter()
  {
     public void mouseClicked()
     {
        . . .
     }
        . . .
   });


Note that an anonymous inner class is particularly useful for creating an event listener on-the-fly and registering it with an event source at the same time.
 

Layout Management

A layout manager is used to place components within a container.  If you recall, all Swing components are containers; however, only certain components are actually used as containers.  Generally, components are added to top-level containers, such as JFrame (a window typically used for the main application window), JDialog (a window typically used as a subwindow that sits on top of the main application window), and JApplet (a window for applets).  Components can also be added to secondary containers, such as JPanel (a container used to group components).  To add a component, use one of java.awt.Container's add() methods.  The correct add() method to use depends on the layout manager.  Generally, the add() methods accept the component to add and optionally, an argument that specifies the component's desired location within the container.  There are many layout managers in Swing.  Some of them are inherited from AWT and some are new.  You can find out how to use each layout manager in the layout manager tutorial at Sun.  The choice of layout manager affects the size of each component in the container, the relative positions of the components (e.g., alignment), the size and relative positions of the components when the container is resized, and how much extra space is distributed to each component when the container is resized.  The setLayout() method is used to set the layout manager for a container.  The method accepts a layout manager object as an argument.  If you do not specify a layout manager for a container, a default one will be assigned.

Let's examine how components are typically laid out in a program.  Components are added to secondary containers, usually JPanel objects.  Secondary containers are generally used for grouping components.  The secondary containers are then added to a top-level container.  Actually, components cannot be added directly to a top-level container.  Instead they are added to the top-level container's content pane, which is a special container within the top-level container.  Here is an example of adding two buttons to a JPanel and then adding the panel to the content pane of a JFrame:
 

import java.awt.*;
import javax.swing.*;
class TestFrame extends JFrame
{
    public TestFrame(String title)
    {
       // call the superclass constructor
       super(title);
       JPanel panel = new JPanel();
       panel.setLayout(new BorderLayout());
       // this line adds the panel to the
       // Frame's content pane
       getContentPane().add(panel);
       // another way of doing the above is
       // setContentPane(panel);
       panel.add(new JButton("Hello"), BorderLayout.CENTER);
       panel.add(new JButton("World"), BorderLayout.SOUTH);
       // this line terminates the program when the X button
       // located at the top right hand corner of the
       // window is clicked
       addWindowListener(new WindowAdapter()
         {
           public void windowClosing(WindowEvent e)
           {
             System.exit(0);
           }
         });
    }
    public static void main(String[] args)
    {
       TestFrame testframe = new TestFrame("Test Frame");
       // this line causes the window to be sized to its
       // preferred size (this essentially compresses the
       // window)
       testframe.pack();
       // Initially, the JFrame is invisible. This line
       // makes the window visible.
       testframe.setVisible(true);
    }
}


Here is how the program looks:
 


 

Figure 7.  "Hello World" Application
 

Here is how it looks after it's been resized:
 


 

Figure 8.  "Hello World" Application Resized
 

Notice that the border layout manager distributes most of the extra space to the component that is placed at BorderLayout.CENTER.

 

Before You Move On

Before you move on to the next section, you should take the short Swing tutorial at Sun or if you have time the long one.  In addition, you should read the tutorials on the following topics in the long Swing tutorial because they will help you understand the code in the sample application:

 

The Sample Application

A way of finding out what an application does is to play with it.  Before you run the program, you need to create the branch, driver, license, and exam tables.  Those tables should have been created in the second SQL/Oracle tutorial.  If they weren't, here is an SQL*Plus script file that creates them and inserts a few tuples.

Now download the

     zip file

and unzip it. It should create a folder "Mvb-Swing" with the following the Java files. 

Create an eclipse project in that directory. Add the jdbc driver as external library and run it.

If you use the Unix command line you also need a make file. Here it is

Make sure the CLASSPATH environment variable includes the Oracle JDBC driver.  Refer to the first JDBC tutorial for instructions on how to add the driver to the CLASSPATH.  To compile the program, type make.  You need to type java MvbView to run the program. 

Run the program. A login window will appear when the program starts up.  Type your Oracle userid and password.  If the login information is incorrect, the password field will be cleared.  You only have three attempts to login.  After logging in, try displaying all the branches.  You can do so by accessing the Branch Admin menu or pressing <ctrl-b>.  <ctrl-b> is the keyboard short cut key for the show all branches command.

You should see a table of branch tuples.  Double click a column header.  Notice that the column gets sorted in ascending order.  Try holding <shift> and double clicking the same column header.  The column should now be sorted in descending order.  Try clicking a cell and leaving the mouse pointer over the cell (you will have to move the mouse pointer a little bit after clicking the cell).  The contents of the cell will be displayed above the mouse pointer.

Next press <alt-b> to display the Branch Admin menu.  Then press <u> to access the update branch name command.  Update the name of any branch.  The table should be updated if the SQL update was successful.

Next try inserting a branch.  Note that branch address and branch phone are the only fields that can accept a null value (to insert null, leave the text field blank).  After successfully inserting a branch, the table will be updated.

Next try deleting a branch.  Then try the edit all branches command.  This will display an editable table.  Before you can edit a cell, you must double click it.  Note: there is one limitation to this command.  The table that is displayed does not allow the contents to be sorted.  You will find out why later.

Finally, try violating some integrity constraints, such as inserting a branch with the same branch id as an existing branch, updating the name of a non-existent branch, deleting a branch that is referenced by more than one table, and editing a branch id that is referenced by more than one table.

While playing with the program, you probably notice that only the Branch Admin menu works.  This is because the functionality for the other menus has not been implemented yet.  Later, we will show you how to add functionality to the Driver Admin menu.

To quit the program, click the X button located at the top right hand corner of the program window.

 

Program Architecture

The design of the program is based on the model-view-controller design pattern.  If you haven't already, you can read about it in the Model-View-Controller section of this tutorial.  The purpose of using a model-view-controller architecture is to decouple the GUI from the backend.  For example, on a three tier client-server system, the GUI runs on the client machine, while the backend (the application logic) runs on another machine.  The database server may or may not be on the same machine as the backend.  You have learned in the CPSC 310 class why breaking a program into parts that run on separate machines improves performance, especially on high load systems.  To simplify the implementation, the sample program was created to run on a two tier system, where both the GUI and backend run on the client machine.  However, it can be converted to run on a three tier system.  Although we have used Swing for the GUI, we could have used something else.  For example, we could have written the GUI as a set of Web pages.

Here is a class diagram describing the interaction among the main classes in the sample program:
 


 

Figure 9.  The Main Classes in the Motor Vehicle Branch Application
 

The class MvbView constructs the application's main window, the menus, the scrollpane that stores the table, and the status area that provides status information to the user.  The class provides methods to update the GUI, such as adding text to the status area and adding a table to the scrollpane.  These methods are used by the BranchController class to update the view.  BranchController is the glue between the Branch Admin menu and the branch table transactions.  It accepts input from MvbView in the form of a menu item selection.  It determines which menu item in the Branch Admin menu is selected and then displays the appropriate dialog box, if necessary, to accept further input from the user.  Each dialog box is declared as an inner class in BranchController.  Alternatively, they could have been declared as non-inner classes and placed in separate files.  The class BranchModel contains all the branch table database transactions.  Once a dialog box has accepted input, it will call the appropriate method in BranchModel.  BranchModel will notify BranchController on the success or failure of the transaction.  Some of the methods in BranchModel create results sets that can be updatable, so that the user can update a table by updating the result set.  A short Tutorial on Result Set Enhancements provides more information about this type of result sets.

Note that the program's BranchController object is created after the user logs in.  It cannot be created before because its constructor creates a BranchModel object, whose constructor has the precondition that the database connection be valid.  Because of this problem with coupling, it may be better to structure the transactions as individual classes (see the first JDBC tutorial).  The connection object would then be passed into the transaction's constructor at the time you want to execute the transaction.  Thus, instead of one class for the branch model there would be a set of classes.  As an exercise, you may wish to modify the sample program to use this method after reading this tutorial.

BranchController is able to report all errors to the status area in MvbView, even those that occur in other classes.  An observer-subject architecture is used to accomplish this.  BranchController is registered as a listener to all classes from which it is interested in receiving error information.  When an exception occurs in a class with which it has registered, it is notified.  It then displays an appropriate error message in the status area.  In order to implement this feature, a new event type (ExceptionEvent) and event listener interface (ExceptionListener) was added to the current delegation event model.  Here is a diagram showing the classes that BranchController observes:
 


 

Figure 10.  How BranchController Forwards Errors to MvbView
 

BranchController implements the ExceptionListener interface, which has only one method: exceptionGenerated(ExceptionEvent ex).  It registers with CustomTable, CustomTableModel, and BranchModel by calling their addExceptionListener(ExceptionListener l) method.  When an exception occurs, the event source creates an ExceptionEvent object.  The fireExceptionGenerated(ExceptionEvent ex) method is then called.  This method passes the ExceptionEvent object to all registered ExceptionListeners (so far, only BranchController is registered) by calling each ExceptionListener's exceptionGenerated(ExceptionEvent ex) method.  The exceptionGenerated() method in BranchController simply calls MvbView's updateStatusBar(String s) to update the status area.  Note that each instance of CustomTable and CustomTableModel should only have one controller registered at a time.  Otherwise the status area might be updated more than once.  This is not a problem if we design each controller class to handle its own GUI tables.

The connection to the Oracle database is handled by the LoginWindow class and the MvbOracleConnection class.  The LoginWindow class constructs the login window.  Each login attempt results in a call to the connect() method in MvbOracleConnection.  connect() loads the Oracle JDBC driver and establishes a connection, i.e., creates a Connection object.  LoginWindow allows at most 3 login attempts or calls to the connect() method.  MvbOracleConnection represents a single connection to an Oracle database.  It is actually a singleton class.  An instance of this class is obtained by calling its static getInstance() method.  The JDBC Connection object that it stores can be retrieved by calling its getConnection() method.  The Connection object is used by BranchModel to create the PreparedStatement objects and commit changes to the database (MvbOracleConnection disables auto commit mode).  To handle different database connections to different database management systems, you can create a new class, e.g., MvbDB2Connection that is similar to MvbOracleConnection.  Alternatively, you can add a different connect() and getConnection() to MvbOracleConnection (e.g. connectToDB2() and getDB2Connection()).  However, you should rename the class to something like MvbDatabaseConnections.

The CustomTable class is a JTable subclass.  It displays a result set.  The CustomTableModel class represents the data in CustomTable.  Therefore, CustomTable's constructor accepts a CustomTableModel.  Since the data in CustomTable is a result set, the constructor for CustomTableModel accepts a result set.  It also accepts a Connection object because if the result set is updatable, the table will allow the user to edit the table's data.  CustomTableModel uses the Connection object to commit the changes, so that changes to a table cell will be propagated to the database.  The edit all branches command uses an updatable result set while the show all branches command doesn't.  In addition to the update feature, CustomTableModel also allows the data to be sorted.  The inner class Sorter handles this task.  There is, however, a limitation to this sorting feature.  If the result set is updatable, this feature will be disabled.  The reason is that the row indices in the result set will not match the row indices in the table after the table is sorted.  So if sorting is not disabled, updates to the table will not propagate correctly to the result set and thus the database.

When you go through the code, don't worry too much about the implementation of CustomTable and CustomTableModel.  For your assignments/projects, the only thing you need to know about them is how to use them, i.e. invoking their constructors.  Also, don't worry too much about the code that lays out the components in MvbView and in the dialog boxes in BranchController.  It is more important that you understand the program's model-view-controller architecture.

 

Extending the Sample Application

This section will show you how to add the show all drivers command and the insert driver command to the Driver Admin menu.  The only files that you need to modify are MvbView.java and Makefile.  In addition, you need to create 2 new files: one for the Driver Admin controller and one for the driver transactions.  Let's start by modifying MvbView.java.
 

Adding the Menu and Menu Items

First remove the following lines in MvbView():
JMenu menu = new JMenu("Driver Admin");
menu.setMnemonic(KeyEvent.VK_D);
menuBar.add(menu);
menu = new JMenu("License Admin");
This removes the dummy version of the Driver Admin menu.

Here are the rest of the changes:

private JMenu driverAdmin;
public MvbView()
{
  . . .
  setupDriverAdminMenu(menuBar);

  // this fixes the line that follows the dummy Driver Admin menu
  JMenu menu = new JMenu("License Admin");
  . . .
}
. . .
private void setupDriverAdminMenu(JMenuBar mb)
{
  driverAdmin = new JMenu("Driver Admin");
  driverAdmin.setMnemonic(KeyEvent.VK_D);
  createMenuItem(driverAdmin, "Insert Driver...",
                 KeyEvent.VK_I, "Insert Driver");
  JMenuItem menuItem = createMenuItem(driverAdmin, "Show All Drivers",
                                      KeyEvent.VK_S, "Show Driver");
  menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D,
                              ActionEvent.CTRL_MASK));
  mb.add(driverAdmin);
}
. . .
public void registerControllers()
{
  . . .
  DriverController dc = new DriverController(this);
  for (int i = 0; i < driverAdmin.getItemCount(); i++)
  {
    menuItem = driverAdmin.getItem(i);
    menuItem.addActionListener(dc);
  }
}

Adding the Transactions

The class for the driver transactions will be called DriverModel.
Here is DriverModel.java (note the similarities between it and BranchModel.java):
import java.sql.*;
import javax.swing.event.EventListenerList;
public class DriverModel
{
    protected PreparedStatement ps = null;
    protected EventListenerList listenerList = new EventListenerList();
    protected Connection con = null;
    public DriverModel()
    {
        con = MvbOracleConnection.getInstance().getConnection();
    }
    public boolean insertDriver(Integer did, String dname, String daddr,
                      String dcity, java.sql.Date dbirth, Integer dphone)
    {
        try
        {
            ps = con.prepareStatement("INSERT INTO driver VALUES (?,?,?,?,?,?)");
            ps.setInt(1, did.intValue());
            ps.setString(2, dname);
            ps.setString(3, daddr);
            ps.setString(4, dcity);
            ps.setDate(5, dbirth);
            if (dphone != null)
            {
                ps.setInt(6, dphone.intValue());
            }
            else
            {
                ps.setNull(6, Types.INTEGER);
            }
            ps.executeUpdate();
            con.commit();
            return true;
        }
        catch (SQLException ex)
        {
            ExceptionEvent event = new ExceptionEvent(this, ex.getMessage());
            fireExceptionGenerated(event);
            try
            {
                con.rollback();
                return false;
            }
            catch (SQLException ex2)
            {
                event = new ExceptionEvent(this, ex2.getMessage());
                fireExceptionGenerated(event);
                return false;
            }
        }
    }
    public ResultSet showDriver()
    {
        try
        {
            ps = con.prepareStatement("SELECT d.* FROM driver d",
                                      ResultSet.TYPE_SCROLL_INSENSITIVE,
                                      ResultSet.CONCUR_READ_ONLY);
            ResultSet rs = ps.executeQuery();
            return rs;
        }
        catch (SQLException ex)
        {

            ExceptionEvent event = new ExceptionEvent(this, ex.getMessage());
            fireExceptionGenerated(event);
            return null;
        }
    }
    public boolean findDriver(int did)
    {
        try
        {
            ps = con.prepareStatement("SELECT driver_sin FROM driver
                                       where driver_sin = ?");
            ps.setInt(1, did);
            ResultSet rs = ps.executeQuery();
            if (rs.next())
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        catch (SQLException ex)
        {
            ExceptionEvent event = new ExceptionEvent(this, ex.getMessage());
            fireExceptionGenerated(event);
            return false;
        }
    }
    public Connection getConnection()
    {
        return con;
    }
    protected void finalize() throws Throwable
    {
        if (ps != null)
        {
            ps.close();
        }
        super.finalize();
    }
    public void addExceptionListener(ExceptionListener l)
    {
        listenerList.add(ExceptionListener.class, l);
    }
    public void removeExceptionListener(ExceptionListener l)
    {
        listenerList.remove(ExceptionListener.class, l);
    }
    public void fireExceptionGenerated(ExceptionEvent ex)
    {
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length-2; i>=0; i-=2)
        {
            if (listeners[i]==ExceptionListener.class)
            {
                ((ExceptionListener)listeners[i+1]).exceptionGenerated(ex);
            }
         }
     }
}
 

Creating the Control

We'll call the class for the driver controller DriverController.
Here is DriverController.java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.sql.*;
import java.text.*;
public class DriverController implements ActionListener, ExceptionListener
{
    private MvbView mvb = null;
    private DriverModel driver = null;
    public static final int OPERATIONSUCCESS = 0;
    public static final int OPERATIONFAILED = 1;
    public static final int VALIDATIONERROR = 2;
    public DriverController(MvbView mvb)
    {
        this.mvb = mvb;
        driver = new DriverModel();
        driver.addExceptionListener(this);
    }
    public void actionPerformed(ActionEvent e)
    {
        String actionCommand = e.getActionCommand();
        if (actionCommand.equals("Insert Driver"))
        {
            DriverInsertDialog iDialog = new DriverInsertDialog(mvb);
            iDialog.pack();
            mvb.centerWindow(iDialog);
            iDialog.setVisible(true);
            return;
        }
        if (actionCommand.equals("Show Driver"))
        {
            showAllDrivers();
            return;
        }
    }
    public void exceptionGenerated(ExceptionEvent ex)
    {
        String message = ex.getMessage();
        Toolkit.getDefaultToolkit().beep();
        if (message != null)
        {
            mvb.updateStatusBar(ex.getMessage());
        }
        else
        {
            mvb.updateStatusBar("An exception occurred!");
        }
    }
    private void showAllDrivers()
    {
        ResultSet rs = driver.showDriver();
        CustomTableModel model = new CustomTableModel(driver.getConnection(),
                                                      rs);
        CustomTable data = new CustomTable(model);
        model.addExceptionListener(this);
        data.addExceptionListener(this);
        mvb.addTable(data);
    }
    class DriverInsertDialog extends JDialog implements ActionListener
    {
        private JTextField driverID = new JTextField(10);
        private JTextField driverName = new JTextField(15);
        private JTextField driverAddr = new JTextField(15);
        private JTextField driverCity = new JTextField(10);
        private JTextField driverBirth = new JTextField(10);
        private JTextField driverPhone = new JTextField(10);
        public DriverInsertDialog(JFrame parent)
        {
            super(parent, "Insert Branch", true);
            setResizable(false);
            JPanel contentPane = new JPanel(new BorderLayout());
            setContentPane(contentPane);
            contentPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10,
                                                                  10));
            JPanel inputPane = new JPanel();
            inputPane.setBorder(BorderFactory.createCompoundBorder(
                         new TitledBorder(new EtchedBorder(), "Branch Fields"),
                         new EmptyBorder(5, 5, 5, 5)));
            GridBagLayout gb = new GridBagLayout();
            GridBagConstraints c = new GridBagConstraints();
            inputPane.setLayout(gb);
            JLabel label= new JLabel("Driver SIN: ", SwingConstants.RIGHT);
            c.gridwidth = GridBagConstraints.RELATIVE;
            c.insets = new Insets(0, 0, 0, 5);
            c.anchor = GridBagConstraints.EAST;
            gb.setConstraints(label, c);
            inputPane.add(label);
            c.gridwidth = GridBagConstraints.REMAINDER;
            c.insets = new Insets(0, 0, 0, 0);
            c.anchor = GridBagConstraints.WEST;
            gb.setConstraints(driverID, c);
            inputPane.add(driverID);
            label = new JLabel("Driver Name: ", SwingConstants.RIGHT);
            c.gridwidth = GridBagConstraints.RELATIVE;
            c.insets = new Insets(5, 0, 0, 5);
            c.anchor = GridBagConstraints.EAST;
            gb.setConstraints(label, c);
            inputPane.add(label);
            c.gridwidth = GridBagConstraints.REMAINDER;
            c.insets = new Insets(5, 0, 0, 0);
            c.anchor = GridBagConstraints.WEST;
            gb.setConstraints(driverName, c);
            inputPane.add(driverName);
            label = new JLabel("Driver Address: ", SwingConstants.RIGHT);
            c.gridwidth = GridBagConstraints.RELATIVE;
            c.insets = new Insets(5, 0, 0, 5);
            c.anchor = GridBagConstraints.EAST;
            gb.setConstraints(label, c);
            inputPane.add(label);
            c.gridwidth = GridBagConstraints.REMAINDER;
            c.insets = new Insets(5, 0, 0, 0);
            c.anchor = GridBagConstraints.WEST;
            gb.setConstraints(driverAddr, c);
            inputPane.add(driverAddr);
            label = new JLabel("Driver City: ", SwingConstants.RIGHT);
            c.gridwidth = GridBagConstraints.RELATIVE;
            c.insets = new Insets(5, 0, 0, 5);
            c.anchor = GridBagConstraints.EAST;
            gb.setConstraints(label, c);
            inputPane.add(label);
            c.gridwidth = GridBagConstraints.REMAINDER;
            c.insets = new Insets(5, 0, 0, 0);
            c.anchor = GridBagConstraints.WEST;
            gb.setConstraints(driverCity, c);
            inputPane.add(driverCity);
            label = new JLabel("Driver Birthdate: ", SwingConstants.RIGHT);
            c.gridwidth = GridBagConstraints.RELATIVE;
            c.insets = new Insets(5, 0, 0, 5);
            c.anchor = GridBagConstraints.EAST;
            gb.setConstraints(label, c);
            inputPane.add(label);
            c.gridwidth = GridBagConstraints.REMAINDER;
            c.insets = new Insets(5, 0, 0, 0);
            c.anchor = GridBagConstraints.WEST;
            gb.setConstraints(driverBirth, c);
            inputPane.add(driverBirth);
            label = new JLabel("Driver Phone: ", SwingConstants.RIGHT);
            c.gridwidth = GridBagConstraints.RELATIVE;
            c.insets = new Insets(5, 0, 0, 5);
            c.anchor = GridBagConstraints.EAST;
            gb.setConstraints(label, c);
            inputPane.add(label);
            c.gridwidth = GridBagConstraints.REMAINDER;
            c.insets = new Insets(5, 0, 0, 0);
            c.anchor = GridBagConstraints.WEST;
            gb.setConstraints(driverPhone, c);
            inputPane.add(driverPhone);
            driverPhone.addActionListener(this);
            driverPhone.setActionCommand("OK");
            JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
            buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 2));
            JButton OKButton = new JButton("OK");
            JButton cancelButton = new JButton("Cancel");
            OKButton.addActionListener(this);
            OKButton.setActionCommand("OK");
            cancelButton.addActionListener(new ActionListener()
                {
                    public void actionPerformed(ActionEvent e)
                    {
                        dispose();
                    }
                });
            buttonPane.add(Box.createHorizontalGlue());
            buttonPane.add(OKButton);
            buttonPane.add(Box.createRigidArea(new Dimension(10,0)));
            buttonPane.add(cancelButton);
            contentPane.add(inputPane, BorderLayout.CENTER);
            contentPane.add(buttonPane, BorderLayout.SOUTH);
            addWindowListener(new WindowAdapter()
                {
                    public void windowClosing(WindowEvent e)
                    {
                        dispose();
                    }
                });
        }
        public void actionPerformed(ActionEvent e)
        {
            String actionCommand = e.getActionCommand();
            if (actionCommand.equals("OK"))
            {
                if (validateInsert() != VALIDATIONERROR)
                {
                    dispose();
                }
                else
                {
                    Toolkit.getDefaultToolkit().beep();
                    JOptionPane errorPopup = new JOptionPane();
                    errorPopup.showMessageDialog(this, "Invalid Input",
                                    "Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        }
        private int validateInsert()
        {
            try
            {
                Integer          did;
                String         dname;
                String         daddr;
                String         dcity;
                java.sql.Date dbirth;
                Integer       dphone;
                if (driverID.getText().trim().length() != 0)
                {
                    did = Integer.valueOf(driverID.getText().trim());
                    if (driver.findDriver(did.intValue()))
                    {
                        Toolkit.getDefaultToolkit().beep();
                        mvb.updateStatusBar("Driver " + did.toString() +
                                            " already exists!");
                        return OPERATIONFAILED;
                    }
                }
                else
                {
                    return VALIDATIONERROR;
                }
                if (driverName.getText().trim().length() != 0)
                {
                    dname = driverName.getText().trim();
                }
                else
                {
                    return VALIDATIONERROR;
                }
                if (driverAddr.getText().trim().length() != 0)
                {
                    daddr = driverAddr.getText().trim();
                }
                else
                {
                    return VALIDATIONERROR;
                }
                if (driverCity.getText().trim().length() != 0)
                {
                    dcity = driverCity.getText().trim();
                }
                else
                {
                    return VALIDATIONERROR;
                }
                if (driverBirth.getText().trim().length() != 0)
                {
                    String stringDate = driverBirth.getText().trim();
                    SimpleDateFormat fm = new SimpleDateFormat("dd/MM/yy");
                    java.util.Date utilDate = fm.parse(stringDate);
                    dbirth = new java.sql.Date(utilDate.getTime());
                }
                else
                {
                    return VALIDATIONERROR;
                }
                if (driverPhone.getText().trim().length() != 0)
                {
                    dphone = Integer.valueOf(driverPhone.getText().trim());
                }
                else
                {
                    dphone = null;

                }
                mvb.updateStatusBar("Inserting driver...");
                if (driver.insertDriver(did, dname, daddr, dcity, dbirth,
                    dphone))
                {
                    mvb.updateStatusBar("Operation successful.");
                    showAllDrivers();
                    return OPERATIONSUCCESS;
                }
                else
                {
                    Toolkit.getDefaultToolkit().beep();
                    mvb.updateStatusBar("Operation failed.");
                    return OPERATIONFAILED;
                }
            }
            catch (NumberFormatException ex)
            {
                return VALIDATIONERROR;
            }
            catch (ParseException ex)
            {
                return VALIDATIONERROR;
            }
        }
    }
}
The code is very similar to BranchController.  The only tricky part was converting the string in the driver birthdate text field to a date.  Note that the birthdate field accepts a date in the format dd/MM/yy.
 

Updating the Makefile

The final step is updating the Makefile to include DriverModel.java and DriverController.java.

Here are the changes (the additions are in bold):

SRCS = ExceptionEvent.java ExceptionListener.java CustomTableModel.java CustomTable.java \
       MvbOracleConnection.java BranchModel.java DriverModel.java LoginWindow.java \
       BranchController.java DriverController.java MvbView.java
. . .
DriverModel.class: DriverModel.java MvbOracleConnection.java ExceptionListener.java \
                   ExceptionEvent.java
DriverController.class: DriverController.java MvbView.java DriverModel.java CustomTable.java \
                        CustomTableModel.java ExceptionListener.java ExceptionEvent.java
MvbView.class: MvbView.java BranchController.java DriverController.java

 

Links




Last updated : November 07, 2010 10:24 AM