Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Developing Professional Java Applets

Chapter 11 -- Building a Live Data Applet

Chapter 11

Building a Live Data Applet


CONTENTS


This chapter concludes Part IV with an application that brings together most of the JDK features you have seen throughout this book. The application is an Election Night applet that shows election returns as they arrive. It can do this because the applet gets datagrams from a back-end Web server that broadcasts returns as they are entered into a server database. This client-server application is Internet-ready! You can run the server on the host and have multiple clients connect to your site to see the live returns.

This application brings in many of the JDK's major components, such as AWT, threads and synchronization, streams, exception handling, native methods, and sockets. Because of the project's size, many less critical but useful components are also applied-such as the Hashtable class, static methods, and string parsing. Some new concepts are also introduced; for example, the model-view paradigm is covered briefly before you get into the heart of the chapter project.

Observers and the Model-View Paradigm

One problem that programmers often face is showing different views of data that may change over time. A classic example of this is a spreadsheet with multiple graphs (this sounds familiar!). If the spreadsheet were tied to a relational database, traditional programmers would represent the graph by making SQL calls to the same set of tables the spreadsheet uses. However, if the spreadsheet changes (and, therefore, its underlying table structure), the graph programs would need to be modified, also. In other words, the spreadsheet structure is closely tied to both the spreadsheet program and the graphs that use its data.

The problem can get even worse if the graphs must update themselves as the spreadsheet data changes. How do the graphs monitor the data? Should the graphs keep querying the data to see whether it has changed? This would be inefficient from a performance standpoint since such data will probably not change much from second to second, although it can change dramatically over longer periods of time. Furthermore, with multiple graphs and a spreadsheet constantly accessing the data, the database host's performance will be strained.

The model-view paradigm offers a solution to these problems with two fundamental tenets: Separate the underlying data model from the programs that view (use) this data, and let the underlying data model inform the views that something has changed, which prevents the view from having to constantly query the data. To illustrate the first tenet being applied, take a look again at the spreadsheet applet developed in Part II. The data was maintained in the CellContainer class, whose only function was the proper upkeep of the spreadsheet data. The CellContainer class provided a "model" for the spreadsheet data. There were three "views" on the data-the two graphs and the spreadsheet itself. The latter was represented by the SpreadsheetContainer class, which simply uses the CellContainer model. For example, the SpreadsheetContainer didn't have any responsibility for verifying a formula or evaluating its results-that was the job of the model. In a sense, the SpreadsheetContainer was little more than a "dumb" view of the model.

The second tenet of the model-view paradigm is how to let a view know when a model has changed. For this situation, the Observable class and Observer interface in the java.util package offer an effective solution. The Observable class represents the model. To create an Observable model, you need to build a subclass of Observable. Observer objects can then attach to your model; when the model changes, you notify the observers.

In the applet portion of the chapter project, a class called ElectionTable maintains a local cache of election results on a state-by-state and grand-total basis; it is a direct subclass of Observable. When its data is modified, it notifies the Observer objects of the changes:

setChanged();
notifyObservers();

The first method, setChanged(), tells the Observable class that the model has been modified. When a notify method is next called (immediately afterward, in this case), the Observable class checks to see whether the data has changed and, if so, broadcasts a notification to the observers. Table 11.1 lists a summary of the Observable class's methods.

Table 11.1. The Observable class's methods.

MethodDescription
addObserver Add an Observer object to the list of observers.
deleteObserver Remove an Observer from the observer list.
deleteObservers Clear the observer list.
notifyObservers Notify all the Observers that the data has changed. One version of this method has an optional parameter to send data about what was modified.
setChanged Signals internally that a change has occurred.
hasChanged Returns whether the data has changed.
countObservers Returns a count of all the Observers.

The Observer interface has only one method, update(). It takes as its parameters the Observable object and an object that can be used to convey more information about what has changed. The Observer object hooks to the Observable object through the addObserver() method. In this chapter's project, the two observers simply repaint when they get an update() message.

Chapter Project

This chapter project demonstrates an applet and corresponding Java server used to show you election night returns as they come in. When you select the Election home page, the applet connects to the server and is added to a list of registrants to be notified whenever election data changes. The server uses its local database to check for any incoming election returns. When they occur, the server broadcasts the results to the client applets. The applets, in turn, update themselves to show the latest totals.

To add a little fun to the project, the system was designed for the likely 1996 presidential election between the incumbent and his challenger. (Note that it wouldn't be hard to change the project if a third-party candidate throws a monkey wrench into this mix, since the project is mostly driven by the database. However, a few assumptions about there being two candidates were made.) Figure 11.1 shows the Election applet before the returns start rolling in, Figure 11.2 illustrates what the applet might look like later in the evening, and Figure 11.3 shows the applet as the election reaches its possible conclusion. This last figure demonstrates the applet with its graphical view turned on.

Figure 11.1 : The Election applet before the election returns start rolling in.

Figure 11.2 : The Election applet as the evening progresses. It's a close one.

Figure 11.3 : The applet as the election reaches in climax-who is going to win?

You are given a database consisting of filled-in states and corresponding electoral votes. However, the totals are left blank. If you want, you can rig the election in favor of the candidate of your choice!

General Architecture of the Project

The general architecture of this project is based on the project architecture from Chapter 10, "Native Methods and Java." For the sake of clarity, a visual overview of the architecture is repeated in Figure 11.4. Refer to Chapter 10 for a description of the general architecture and detailed server and database implementation, but a couple of additional points about the structure of the project are covered here. Furthermore, a full description of the client applet will follow in the section "Applet Client."

Figure 11.4 : The project architecture.

The hierarchy of the server environment is as follows: The server classes should be placed in the parent directory (which could be in a variety of places); the database (ELECT.DBF) can also be in this directory, but if your ODBC data source is set up properly, the database can be located elsewhere.

Underneath the parent is a directory called htdocs. The only file it must have is index.html, which is the HTML for the applet at hand. Located underneath htdocs is a classes subdirectory. This will contain classes and any additional files the client applet will need.

To start the server, go to the parent directory and type:

java BasicWebServer

An additional debug 1 parameter will display debug information about the server.

Changes to the Server

As in the previous chapter, the ElectionServer class is used to send data to multiple client applets. It does this at the behest of the BasicWebServer class because it implements the LiveDataServer interface. The main part of this Thread class is its run() method, which constantly looks for new data and broadcasts its results to the client applets. This code was slightly modified from Chapter 10 for a fuller implementation of the election night project. Only the run() method was modified, as illustrated in Listing 11.1.


Listing 11.1. Changes to the run() method of the ElectionServer Class.
/**
     * Run method for this thread.
     * Recheck the database every 30 seconds
     * and send changed data
     * Resend all data every couple minutes.
     */
    public void run()
    {
     int timestamp = 0;
     int iterationsBeforeFull = 60;  // Fifteen minutes
     int iteration = 0;
        if ( results != null )
        {
            servThread.start();
            while(true)
            {
               sleep(30);
                try
                {
                    SQLStmt nn;
                    // After so many iterations, resend all data...
                    if (iteration >= iterationsBeforeFull) {
                       nn = election.sql("select * from election order by
Âstate,candidate");
                       synchronized (results)
                       {
                           results = nn;
                       }
                       servThread.sendToUsers(
                           formatResults("RESULTS", results));
                       iteration = 0;
                    } // end full if
                    else {
                     // Otherwise just check timestamps...
                     String partialSQL =
                      "select * from election where updated > '" +
                        timestampToChar(timestamp) +
                        "' order by state,candidate";
                     nn = election.sql(partialSQL);
                     // Make sure a valid number of rows is returned...
                     if ((nn.numRows() > 0) && ((nn.numRows()%2) == 0) ) {
                        // Send data and update timestamp...
                        servThread.sendToUsers(
                           formatResults("RESULTS", nn));
                        ++timestamp;
                     }
                     ++iteration;
                    }
                }
                catch (DBException de)
                {
                    System.out.println("Error: " + de);
                }
            }
        }
    }

Note
Note that the database used here does not have a "triggering" capability as many relational databases do. With triggers, the database would update the server, much like the model-view paradigm discussed earlier. This is much better than having the server query the database all the time.

There were mainly two changes to the method. The first was the introduction of partial updates. After broadcasting all the election data (in the NewRegistrant() method, which is not listed here), the server loops and waits for partial updates. The server uses the timestamp mechanism discussed in the next section, "Database," to see whether data has changed. Every 30 seconds, the server queries the database to see whether any data has been added with a timestamp older than the current timestamp. If not, the server sleeps again and then retries. If new data is found, the changes are broadcast to the client applets. The timestamp is then incremented and the process repeats itself.

The other change in the server's run() method is indicated by the iteration variable. Every 15 minutes, the server rebroadcasts all of the data to the clients. This might seem unnecessary, but remember that the datagram protocol used to transmit data is unreliable, and packets could be lost. Given this, it's possible that the clients might not have received some earlier updates. However, you could increase the rebroadcast time to a higher value, such as 30 minutes, an hour, or more. The right figure for this will depend on a variety of factors, including the size and sensitivity of your data.

Database

The database used in this chapter is generally the same dBASE IV table described in the previous chapter. An UPDATED field was added so that changes can be timestamped, thus allowing the server to download only the state information that has changed.

A couple of other things should be mentioned about the database. First of all, a blank version of the database can be found in the file NEWELECT.DBF located on the CD-ROM. It has all the states, candidates, and electoral votes. Data that changes over time, such as the popular vote, is initialized to zero. Since the runtime server uses ELECT.DBF, you can simply replace NEWELECT.DBF to generate a clean database after the ELECT.DBF data is modified for testing.

Another peculiarity of the table is that the ELECTORAL field initially has negative numbers. The absolute value of the negative numbers symbolizes the number of electoral votes a state carries. If the number is positive, it indicates that the corresponding candidate has won the state. A normalized database would have been a better solution here (separate tables for state information and declared winners), but it didn't seem appropriate to focus on this aspect of the project.

Yet another curiosity is the UPDATED field. Since the native method's ODBC driver implements only character data types, all the fields in the database need to be of a character type. Unfortunately, this makes timestamping a little tricky, so timestamps in this project have the following format: 000XXX. That is, all timestamps need to be prefixed by zeroes to produce a timestamp six characters long. So the first stamp would be 000001, the hundredth would be 000100, and so on. If your server updates aren't working properly, be careful how you enter the timestamps in the database.

After sending data down the first time, the server retrieves partial updates based on a timestamp. Although internally it is a number, it's in the format just described in the database. Any database rows with a timestamp higher than the last timestamp are made part of any new partial update broadcasts. Once the database timestamp is surpassed by the external timestamp, the corresponding row is no longer sent down as part of a partial update. The server will always print out the current timestamp it is working on. Suppose it is 000003, and you want to update the data. You can do this by changing both entries of the candidates in a state and setting the timestamp to 000004. The data will then be sent down in the next partial update broadcast and should show up in your applet.

Applet Client

The Election applet continually gives you updated election results as they arrive to the server. The applet consists of basically two parts: back-end threads to manage the arrival of datagrams with the latest election results from the server and a front-end that displays the results in either a graphical or text-based format. The network-processing classes were mostly developed in the previous two chapters; the display classes are new for this chapter.

Class Organization

Table 11.2 lists the classes used in this chapter's Election applet. Since many of these classes were created in the previous section, the new classes are specified by having their names in boldface type; the classes that were modified have their names italicized.

Table 11.2. The Election applet classes and interfaces.

Class/InterfaceDescription
CandidateAn accessor class that keeps vote totals for a specific candidate.
ClientPacketAssemblerTracks and assembles blocks of data packets.
ClientRegistrationFor registering as a new client.
ClientUnregistrationFor removing client registration with server.
DGTpclientTo receive dynamic data updates from server.
ElectionThe class for the Election applet that implements the LiveDataNotify interface to manage dynamic data from the server and creates components for visual display.
ElectionTableKeeps local copy of election results from the server. Uses synchronization so that incoming DGTP data does not collide with reads from visual components.
ElectionUpdaterPeriodically causes update of Election's visual components.
LiveDataNotifyInterface that defines methods for handling dynamic delivery of data from the server.
PartialPacketPrivate class used by ClientPacketAssembler for assembling packets.
StateBreakdownCanvasElectionTable observer that displays results of individual states in text or graphics format.
StateEntrySimple accessor class that keeps all election data related to a specific state.
SummaryCanvasElectionTable observer that displays national election totals.

How It Works

Figure 11.5 illustrates the workflow of the Election applet. The DGTpclient object receives datagrams from the server indicating the latest election results. This data may be a partial update of just the states that have changed, or it may be a broadcast of the full election results. In either case, the Election object receives the new data since it implements the LiveDataNotify interface.

Figure 11.5 : Data flow of the Election applet.

The Election object parses the data and passes it to the ElectionTable object. The ElectionTable class has only one instance since it has a private constructor; objects that use the data must get a reference to the table object from a public ElectionTable method. The Election object is the only object that updates the table and does so when it's notified by the DGTpclient with new data. When you get the first full batch of election data, with all the states identified, the ElectionTable object creates a private table of the individual state and summary totals. This table is updated by any calls that follow.

ElectionTable is an instance of the Observable class. It notifies its two Observers, the StateBreakdownCanvas and SummaryCanvas, whenever its data change. These two Canvas objects implement the Observer interface and are mainly responsible for displaying the election information. The ElectionUpdater thread runs in the background, periodically forcing refreshes of the Canvas objects.

Since the underlying classes that implement the network interfaces haven't changed since the previous chapter, the discussion that follows will focus on the classes involved in the visual interface.

The Election Class

The Election class runs the Election applet. It implements the LiveDataNotify interface so that it can be notified whenever the DGTpclient gets new election data packets. The Election class also creates the two components that display the election results-the StateBreakdownCanvas and SummaryCanvas classes. Listing 11.2 shows the Election class's code.

Several important things happen at initialization. In the init() method, the first step is to get an applet parameter that specifies the port the server is listening on, then initializes the display. It creates a panel at the top of the screen so that you can toggle between a text-based or graphical display. The Election class then creates the components that display the election results and starts a simple thread, ElectionThread, that periodically causes the applet to repaint. This is needed because paint messages could be lost if election returns come in rapid-fire fashion. The last step in the init() method is to resize the applet so that it's large enough to show all of the state returns.

The other major thing that happens at initialization occurs when the start() method is first called. The Election class creates an instance of the DGTpclient class, specifying itself as the LiveDataNotify parameter-this ensures that the Election applet is notified of all incoming datagrams.

The DGTpclient calls the Election class's recvNewData() method when data has arrived. The Election class then parses this data into a large two-dimensional String array, which is then passed to the ElectionTable class that provides the data's final storage location.


Listing 11.2. The Election class.
// This class starts the Election applet. It implements
// the LiveDataNotify interface that sends the latest
// results to it.  It creates objects to display the
// returns as they arrive.
public class Election extends Applet
    implements LiveDataNotify
{
    private static final int SPACING = 70;
    private boolean init = false;
    DGTpclient ct = null;
    int destPort;
    String destHost = null;
    String numUsers = "Unknown at this time";
    String users = null;
    String results[][];
    int nRows = 0, nCols = 0;
    // Place canvas drawing objects...
    SummaryCanvas sc;
    StateBreakdownCanvas states;
    Checkbox graphView,textView;
    Panel p;

    public void init()
    {
        if ( init == false )
        {
            // Initialize network connections...
            init = true;
            String strPort = getParameter("PORT");
            if ( strPort == null )
            {
                System.out.println("ERROR: PORT parameter is missing");
                strPort = "4545";
            }
            destPort = Integer.valueOf(strPort).intValue();
            destHost = getDocumentBase().getHost();
            // Initialize AWT components...
          // Do basic setup...
          setLayout(new BorderLayout());
          // Add panel to set views...
          p = new Panel();
          CheckboxGroup cg = new CheckboxGroup();
          graphView = new Checkbox("Graphical View",cg,false);
          textView = new Checkbox("Text View",cg,true);
          p.add(graphView);
          p.add(textView);
          add("North",p);

          // Add summary information at top of applet
          sc = new SummaryCanvas(this);
          // Show state breakdowns...
          states = new StateBreakdownCanvas(this);
          // Create update thread...
          Thread t = new ElectionUpdater(this);
          t.start();
          // Resize to fit all the states...
          Dimension d = size();
          FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
          int height = sc.getHeight() + states.getHeight()
            + (2 * fm.getHeight());
          resize(d.width,height);
          show();
        }
    }

   // Update message sent when repainting is needed...
   // Prevent paint from getting cleared out...
   public void update(Graphics g) {
      paint(g);
   }


   // If table is ready, paint all the canvases...
   public synchronized void paint(Graphics g) {
       if (!(ElectionTable.getElectionTable().ready()) )
         return;
       Dimension d = size();
       int height = g.getFontMetrics(getFont()).getHeight();
       int y = 2 * height;
       y = sc.paint(g,0,y,d.width);
       y += states.paint(g,0,y,d.width,d.height - y);
       g.drawString("Registered Users: " + getUsers(),
            10,d.height - height);
   }

   // Toggle radio buttons...
   public boolean action(Event ev,Object o) {
      if (ev.target instanceof Checkbox) {
         if (ev.target.equals(graphView)) {
               states.setMode(StateBreakdownCanvas.GRAPHICS);
               sc.setMode(SummaryCanvas.GRAPHICS);
         }
         else {
               states.setMode(StateBreakdownCanvas.TEXT);
               sc.setMode(SummaryCanvas.TEXT);
         }
         repaint();
      }
      return true;
   }


    public String getDestHost()
    {
        return destHost;
    }

    public int getDestPort()
    {
        return destPort;
    }

    public synchronized void recvNewData(byte[] newDataBlock, boolean error)
    {
         if ( error )
         {
             ct.send("REFRESH");
             return;
         }
         String cmd = new String(newDataBlock, 0);
         StringTokenizer cmds = new StringTokenizer(cmd, " \t");
         String current = cmds.nextToken();
         if (current.equals("CLIENTS"))
             users = cmds.nextToken();
         else if (current.equals("RESULTS"))
         {
             nRows = Integer.valueOf(cmds.nextToken()).intValue();
             nCols = Integer.valueOf(cmds.nextToken()).intValue();
             results = new String[nRows][nCols];
             for ( int x = 0; x < nRows; x++ )
             {
                 for ( int y = 0; y < nCols; y++ )
                 {
                     results[x][y] = cmds.nextToken("|\r\n");
                 }
             }
             // Update the election table with the new data...
             ElectionTable.getElectionTable().tableUpdate(nRows,results);
         }
         // QUERY response is unimplemented because
         // this applet currently sends no QUERY commands
         else if (current.equals("QUERY_RSP"))
         {
         }
    }

    public synchronized String getUsers()
    {
        if (users != null)
            return users;
        return numUsers;
    }

    public void start()
    {
        ct = new DGTpclient(this);
        ct.start();
    }

    public void stop()
    {
        System.out.println("Election.stop()");
        ct.terminate();
    }

    public void connectRefused()
    {
    }
}

The ElectionTable Class

The ElectionTable class, shown in Listing 11.3, creates a private table of the individual state and summary totals. Since ElectionTable is responsible for keeping the local cache of the election data, it isn't allowed to be instantiated by an external class. The table has only one instance because it has a private constructor; objects that use the data must get a reference to the table object from a public ElectionTable method called getElectionTable().

An internal array, called states, maintains each state's election information. It is composed of StateEntry objects that contain electoral information about the state, as well as the state totals of the two candidates. The states array is not created in the constructor; rather, it's created when data is first received in the tableUpdate() method, the only entry point into the table for updating data. When the states array is null, it is set up in the initializeTable() method. All subsequent updates (called through the partialUpdate() method) are applied to the states array created at initialization. These write methods are synchronized to prevent any collisions by other threads reading data.

After each update, ElectionTable compiles the state totals to get the summary figures by using the updateTotals() method. Since ElectionTable is an instance of the Observable class, it notifies its observers of the changes at the end of the tableUpdate() method.

The observers use the getStates() method to walk through the results of the individual states. This method has an interesting solution to the synchronization problem, such as when an update occurs while an observer is reading the state information. The getStates() method makes a shallow copy of the states array; it's "shallow" because it copies only the array references and not the actual state elements. If an update thread modifies the original States table, it doesn't affect the copy the reader has because the update makes new StateEntry objects for each update. Therefore, the reader won't be adversely affected by any updates.


Listing 11.3. The ElectionTable class.
import java.lang.*;
import java.util.Observable;

// This class keeps a local cache of the election results
// The table can only be created once so constructor is private...
// A producer thread keeps the table updated
public class ElectionTable extends Observable {
// The table is static and so can be created only once
  private static ElectionTable table = new ElectionTable();

  // Data objects...
  Candidate Incumbent;
  Candidate Challenger;
  int totalPopular;
  static StateEntry states[];

  // Create the table information...
  private ElectionTable() {
      // Set up the candidates...
      Incumbent = new Candidate("Incumbent");
      Challenger = new Candidate("Challenger");
      // Not ready yet...
      states = null;
  }

  // Get reference to election table...
  public static synchronized ElectionTable getElectionTable() {
      return table;
  }

  // See if table is ready...
  public static synchronized boolean ready() {
      return ((states != null) ? (true) : (false));
  }

  // Update the states table with new values...
  public void tableUpdate(int rows,String results[][]) {
      if (states == null)
            initializeTable(rows,results);
      else
            partialUpdate(rows,results);
      // Update totals...
      updateTotals();
      // Notify observers of changes...
      setChanged();
      notifyObservers();
  }

  // Initialize the table with the first batch of data...
  // Code ASSUMES that the results are ordered by
  // state and then candidate
  private static synchronized void initializeTable(int rows,String results[][]) {
      int i,j;
      StateEntry newState;
      // Create the state array...
      states = new StateEntry[rows/2];
      // Go through each row
      for (i = j = 0; i < rows; i+=2,++j) {
         newState = createStateEntry(i,results);
         // Now add to state array...
         states[j] = newState;
      } // end for
  }

  // Update just parts of the table...
  private void partialUpdate(int rows,String results[][]) {
      int i,j;
      StateEntry newState;
      // Kick out if rows is not a multiple of 2
      if ((rows % 2) != 0) {
         System.out.println("Data not formatted right. Rejected.");
         return;
      }
      // Go through each row
      for (i = j = 0; i < rows; i+=2,++j) {
         // Create new state table...
         newState = createStateEntry(i,results);
         // Now update state array...
         stateUpdate(newState);
      } // end for
  }

  // Take index into results table and get a StateEntry
  // object from it...
private static StateEntry createStateEntry(int index,String results[][]) {
      Candidate newChallenger;
      Candidate newIncumbent;
      String stateName;
      double precincts = 0.0;
      int electoral = 0;
      int candElectoral = 0;
      int IncumbentVotes = 0;
      int ChallengerVotes = 0;
      int i = index;
      String s;
         // First row is Challenger. Get his and state info...
         s = results[i][1];
         stateName = s.trim();
         // Get precinct, electoral,votes...
         try {
            s = results[i][2];
            ChallengerVotes = Integer.valueOf(
               s.trim()).intValue();
            s = results[i][3];
            precincts = Double.valueOf(s.trim()).doubleValue();
            s = results[i][4];
            electoral = Math.abs(Integer.valueOf(s.trim()).intValue());
            candElectoral = Integer.valueOf(s.trim()).intValue();
            if (candElectoral < 0)
                     candElectoral = 0;
         }
         catch (NumberFormatException e) {
            System.out.println("Format error: " + e.getMessage());
         }
         s = results[i][0];
         newChallenger = new Candidate(s.trim(),
               candElectoral, ChallengerVotes);
         // Now get Incumbent info...
         ++i;
         try {
            s = results[i][2];
            IncumbentVotes = Integer.valueOf(s.trim()).intValue();
            s = results[i][4];
            candElectoral = Integer.valueOf(s.trim()).intValue();
            if (candElectoral < 0)
                     candElectoral = 0;
         }
         catch (NumberFormatException e) {
            System.out.println("Format error: " + e.getMessage());
         }
         s = results[i][0];
         newIncumbent = new Candidate(s.trim(),
               candElectoral,IncumbentVotes);
         // Return state entry field...
         return new StateEntry(stateName,precincts,
            electoral,(IncumbentVotes + ChallengerVotes),
            newIncumbent, newChallenger);
  }

  // Get table of state listings.  This is a copy of the
  // state entry TABLE, but not the individual listings.  This
  // solves synchronization problems.  The user of the table
  // will get the references to the states but does not actually
  // have references to the actual keys used by the ElectionTable
  // class.
  public synchronized StateEntry[] getStates() {
      if (states == null)
            return states;
      StateEntry tempStates[] = new StateEntry[states.length];
      for (int i = 0; i < states.length; ++i)
               tempStates[i] = states[i];
      return tempStates;
  }

  // Get candidate total information...
  public synchronized Candidate getIncumbent() {
   return Incumbent;
  }
  public synchronized Candidate getChallenger() {
   return Challenger;
  }

  // Update the totals for each Candidate...
  public synchronized void updateTotals() {
      int ChallengerPopular = 0;
      int ChallengerElectoral = 0;
      int IncumbentPopular = 0;
      int IncumbentElectoral = 0;
      totalPopular = 0;
      // Calculate the totals...
      for (int i = 0; i < states.length; ++i) {
         ChallengerPopular += states[i].getChallenger().getPopular();
         ChallengerElectoral += states[i].getChallenger().getElectoral();
         IncumbentPopular += states[i].getIncumbent().getPopular();
         IncumbentElectoral += states[i].getIncumbent().getElectoral();
         totalPopular += states[i].getTotalVotes();
      } // end for
      // Update the Candidates...
      Incumbent = new Candidate("Incumbent",IncumbentElectoral,IncumbentPopular);
      Challenger = new Candidate("Challenger",ChallengerElectoral,
ÂChallengerPopular);
  }

  // Take a state field and find spot in state
  // table to update...
  public synchronized void stateUpdate(StateEntry newState) {
      String name = newState.getName();
      for (int i = 0; i < states.length; ++i) {
         // Replace state that matches current entry...
         if (name.equals(states[i].getName())) {
               states[i] = newState;
               return;
         }
      } // end for
System.out.println("STATE UPDATE ERROR!");
  }

  // Get total popular vote...
  public synchronized int getTotalPopular() {
   return totalPopular;
  }

  // Get info for a specific state...
  private synchronized StateEntry getState(String name) {
      for (int i = 0; i < states.length; ++i) {
         // Replace state that matches current entry...
         if (name.equals(states[i].getName())) {
               return states[i];
         }
      } // end for
System.out.println("STATE GET ERROR!");
      return null;
  }
}

The Candidate Class

Listing 11.4 displays the Candidate class, which contains the name and vote totals of a specific candidate. Although it's a simple accessor class, it effectively functions as a row in a virtual table of candidates.


Listing 11.4. The Candidate class.
// This is a simple accessor class to keep information
// about candidates...
public class Candidate {
   String Name;  // Name of candidate
   int totalElectoral;  // Electoral votes so far...
   int votePopular;  // Popular vote...
   // Create object with states long name and abbreviation
   public Candidate(String Name) {
      this.Name = Name;
      totalElectoral = 0;
      votePopular = 0;
   }
   // Candidate with new totals...
   public Candidate(String Name,
    int totalElectoral,int votePopular) {
      this.Name = Name;
      this.totalElectoral = totalElectoral;
      this.votePopular = votePopular;
   }
   // Access the variables...
   public String getName() {
      return Name;
   }
   public int getElectoral() {
      return totalElectoral;
   }
   public int getPopular() {
      return votePopular;
   }
}

The StateEntry Class

Listing 11.5 displays the StateEntry class, which contains general electoral votes of the state, the percent of precincts counted so far, and the total number of votes cast. It also holds Candidate objects for the two candidates. The StateEntry class, coupled with the Candidate class, provides for normalized data, something that was missing in the back-end database.


Listing 11.5. The StateEntry class.
// This is a simple accessor class to keep information
// about candidates and corresponding state totals...
public class StateEntry {
   String Name;  // Name of state
   int electoral; // Number of electoral votes
   int totalVotes; // Total number of votes in state
   double precincts; // % of precincts counted
   Candidate Incumbent;  // The two candidates
   Candidate Challenger;
   // Create object with states long name and abbreviation
   public StateEntry(String Name) {
      this.Name = Name;
      precincts = 0;
      electoral = 0;
      totalVotes = 0;
      Incumbent = new Candidate("Incumbent");
      Challenger = new Candidate("Challenger");
   }
   // Candidates with new totals...
   public StateEntry(String Name,double precincts,
    int electoral,int totalVotes,
    Candidate Incumbent, Candidate Challenger) {
      this.Name = Name;
      this.totalVotes = totalVotes;
      this.precincts = precincts;
      this.electoral = electoral;
      this.Incumbent = Incumbent;
      this.Challenger = Challenger;
   }
   // Access the variables...
   public String getName() {
      return Name;
   }
   public int getElectoral() {
      return electoral;
   }
   public int getTotalVotes() {
      return totalVotes;
   }
   public double getPrecincts() {
      return precincts;
   }

   // Get candidate-state information...
   public Candidate getIncumbent() {
    return Incumbent;
   }
   public Candidate getChallenger() {
    return Challenger;
   }
}

The StateBreakdownCanvas Class

The StateBreakdownCanvas class displays the election results on a state-by-state basis, as shown in Listing 11.6. The class can show the data graphically or in a primarily text-based format, indicated by its mode. If the canvas is in graphical mode, it shows the popular vote as a bar graph representing the percentages of each candidate in the state.

When the StateBreakdownCanvas is initialized, it sets up a variety of variables used when painting the canvas. Its last step is to declare itself an observer of the ElectionTable by calling its addObserver() method. When the table changes, the canvas's update() method is called, which results in the canvas being repainted to yield the new results.

The paint() method uses many of the techniques discussed elsewhere in this book. The most interesting thing about it, however, is that the canvas occurs over a large area, bigger than most screens. However, most browsers-such as Netscape Navigator-support large applets. If you scroll down the applet, you will see the other state totals. Figure 11.6 shows what the StateBreakdownCanvas looks like when the Election applet, while set in graphical mode, is scrolled down toward the middle of the state listings.

Figure 11.6 : The Election applet set in graphical mode.


Listing 11.6. The StateBreakdownCanvas class.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;
import java.util.Observable;
import java.util.Observer;

// This canvas shows the breakdown of each state
// by candidate. An Observer of the election table
// is added and is called when election
// data has changed which forces update
public class StateBreakdownCanvas extends Canvas implements Observer {
   Applet a; // The applet
   Font fonts[];  // Font to display...
   int fontHeight; // Quick reference of font height...
   int totalWidth;  // Width coordinates...
   int colStart[];
   Rectangle lastPaint; // Keep track of last paint...
   // Various display strings...
   String titleBanner = "1996 ELECTION RETURNS";
   String winner = "WINNER!";
   String headers[] = { "State", "Electoral",
      "Precincts %",
      "Candidate", "Popular Vote" };
   // Graphics or text mode.
   public static final int TEXT = 0;
   public static final int GRAPHICS = 1;
   int mode = TEXT;

   // Calculate some dimension information...
   public StateBreakdownCanvas(Applet app) {
      // Create display fonts...
      fonts = new Font[2];
      fonts[0] = new Font("Helvetica",Font.BOLD,12);
      fonts[1] = new Font("Helvetica",Font.PLAIN,12);
      // Get font height info...
      FontMetrics fm;
      fm = Toolkit.getDefaultToolkit().getFontMetrics(fonts[0]);
      fontHeight = fm.getHeight();
      // Initialize column width displays...
      colStart = new int[5];
      colStart[0] = fm.stringWidth(" " + winner + "!! ");
      colStart[1] = colStart[0] +
         (int)(1.25 *(double)fm.stringWidth("New Jersey!!!"));
      colStart[2] = colStart[1] +
         (int)(1.25 *(double)fm.stringWidth(headers[1]));
      colStart[3] = colStart[0];
      colStart[4] = colStart[1];
      totalWidth = colStart[2] + fm.stringWidth(headers[2]);
      // Make Canvas an observer of the ElectionTable...
      a = app;
      ElectionTable.getElectionTable().addObserver(this);
   }

   // Get target height...
   public int getHeight() {
      return 3 * 53 * fontHeight;
   }

   // Set the mode to paint in...
   public void setMode(int mode) {
      this.mode = mode;
   }

   // Called when Table changes....
   public void update(Observable o, Object arg) {
    if (lastPaint != null)
       a.repaint(lastPaint.x,lastPaint.y,
         lastPaint.width,lastPaint.height);
   }

   // Paint the summary totals...
   public int paint(Graphics g,int xOrg,int yOrg,
    int width, int totalHeight) {
      // Set the background color to white...
      int y = yOrg + fontHeight;
      g.setColor(Color.white);
      g.fillRect(xOrg,yOrg,width,totalHeight);
      lastPaint = new Rectangle(xOrg,yOrg,width,totalHeight);

      // Paint the header...
      g.setFont(fonts[0]);
      g.setColor(Color.blue);
      for (int i = 0; i < 5; ++i) {
         g.drawString(headers[i],colStart[i],y);
         if (i == 2) {
            g.setFont(fonts[1]);
            y += fontHeight;
         }
      }

      // Draw line across bottom...
      g.drawLine(colStart[0],y,totalWidth,y);
      y += fontHeight;

      // Now get the listing of states...
      int length;
      double graphicsWidth = (double)(totalWidth - colStart[1]);
      double percent;
      String s;
      StateEntry[] stateData =ElectionTable.getElectionTable().getStates();
      // Walk through each state...
      for (int i = 0; i < stateData.length; ++i) {
         // Print the state information...
         g.setColor(Color.black);
         g.setFont(fonts[0]);
         g.drawString(stateData[i].getName(),colStart[0],y);
         g.drawString(Integer.toString(stateData[i].getElectoral()),
            colStart[1],y);
         g.drawString(Double.toString(stateData[i].getPrecincts()),
            colStart[2],y);
         // Print the candidates...
         // Incumbent...
         g.setColor(Color.red);
         g.setFont(fonts[1]);
         y += fontHeight;
         Candidate c = stateData[i].getIncumbent();
         // See if he won the state...
         if (c.getElectoral() > 0)
            g.drawString(winner,2,y);
         g.drawString(c.getName(),colStart[0],y);
         // Get election percentage...
         if (stateData[i].getTotalVotes() > 0)
            percent = ((double)c.getPopular()) /
               ((double)stateData[i].getTotalVotes());
         else
            percent = 0.0;
         // Draw text or graphics based on mode...
         if (mode != GRAPHICS) {
            s = Integer.toString(c.getPopular()) +
               " (" + ((float)(percent * 100.00) ) + "%)";
            g.drawString(s,colStart[1],y);
         } // end if
         else {
           if (stateData[i].getTotalVotes() > 0) {
            length = (int)(percent * graphicsWidth);
            g.fillRect(colStart[1],y - fontHeight + 2,
               length,fontHeight - 1);
           } // end if
         } // end else
         // then Challenger...
         g.setColor(Color.blue);
         y += fontHeight;
         c = stateData[i].getChallenger();
         // See if he won the state...
         if (c.getElectoral() > 0)
            g.drawString(winner,2,y);
         g.drawString(c.getName(),colStart[0],y);
         // Get election percentage...
         if (stateData[i].getTotalVotes() > 0)
            percent = ((double)c.getPopular()) /
               ((double)stateData[i].getTotalVotes());
         else
            percent = 0.0;
         // Draw text or graphics based on mode...
         if (mode != GRAPHICS) {
            s = Integer.toString(c.getPopular()) +
               " (" + ((float)(percent * 100.00) ) + "%)";
            g.drawString(s,colStart[1],y);
         } // end if
         else {
           if (stateData[i].getTotalVotes() > 0) {
            length = (int)(percent * graphicsWidth);
            g.fillRect(colStart[1],y - fontHeight + 2,
               length,fontHeight - 1);
           } // end if
         } // end else
         y += fontHeight;
      } // end for

      return totalHeight;
   }

}

The SummaryCanvas Class

The SummaryCanvas class, whose code is given in Listing 11.7, displays the election results on a national basis. It, too, can show the data graphically or in a primarily text-based format, depending on its mode. If the canvas is in graphical mode, then it shows the popular vote as a bar graph representing the percentages of each candidate in the state.

When the SummaryCanvas is initialized, it sets up variables used when painting the canvas. The canvas also declares itself an observer of the ElectionTable by calling its addObserver() method. When the table changes, then the canvas's update() method is called. This repaints the canvas, yielding the new results.


Listing 11.7. The SummaryCanvas class.
import java.awt.*;
import java.lang.*;
import java.util.Observable;
import java.util.Observer;
import java.applet.Applet;

// This canvas shows the top banner and the
// election totals...
// The Observer is called when election
// data has changed and, if so, forces update
public class SummaryCanvas extends Canvas implements Observer {
   Applet a; // The applet
   int totalWidth;  // Width coordinates...
   int colStart[];
   Font fonts[];  // Font to display...
   int fontHeights[]; // Quick reference of font heights...
   int topMargin; // Where to start painting...
   int totalHeight; // How much to paint...
   Rectangle lastPaint; // Keep track of last paint...
   // Various display strings....
   String titleBanner = "1996 ELECTION RETURNS";
   String headers[] = { "Candidate",
      "Electoral Total", "Popular Vote" };
   String winner = "WINNER!";
   // Graphics or text mode.
   public static final int TEXT = 0;
   public static final int GRAPHICS = 1;
   int mode = TEXT;

   // Calculate some dimension information...
   public SummaryCanvas(Applet app) {
      // Create display fonts...
      fonts = new Font[3];
      fonts[0] = new Font("Helvetica",Font.BOLD,20);
      fonts[1] = new Font("Helvetica",Font.BOLD,12);
      fonts[2] = new Font("Helvetica",Font.PLAIN,12);
      // Initialize column width array...
      colStart = new int[3];
      // Get the font and use to calculate some margins...
      fontHeights = new int[3];
      FontMetrics fm;
      for (int i = 0; i < 3; ++i) {
         fm = Toolkit.getDefaultToolkit().getFontMetrics(fonts[i]);
         fontHeights[i] = fm.getHeight();
      } // end for
      topMargin = fontHeights[0];
      fm = Toolkit.getDefaultToolkit().getFontMetrics(fonts[1]);
      colStart[0] = fm.stringWidth(" " + winner + "!! ");
      colStart[1] = colStart[0] +
         (int)(1.5 *(double)fm.stringWidth(headers[0]));
      colStart[2] = colStart[1] +
         (int)(1.5 *(double)fm.stringWidth(headers[1]));
      totalWidth = colStart[2] + fm.stringWidth(headers[2]);
      totalHeight = 8 * fontHeights[1];
      // Make Canvas an observer of the ElectionTable...
      a = app;
      ElectionTable.getElectionTable().addObserver(this);
   }

   // Get target height...
   public int getHeight() {
      return totalHeight;
   }

   // Set the mode to paint in...
   public void setMode(int mode) {
      this.mode = mode;
   }

   // Called when Table changes....
   public void update(Observable o, Object arg) {
    if (lastPaint != null)
       a.repaint(lastPaint.x,lastPaint.y,
         lastPaint.width,lastPaint.height);
   }

   // Paint the summary totals...
   public int paint(Graphics g,int xOrg,int yOrg,int width) {
      // Set the background color to white...
      int y = yOrg + topMargin;
      g.setColor(Color.white);
      g.fillRect(xOrg,yOrg,width,totalHeight);
      lastPaint = new Rectangle(xOrg,yOrg,width,totalHeight);
      // Paint the title banner...
      g.setFont(fonts[0]);
      FontMetrics fm = g.getFontMetrics();
      g.setColor(Color.red);
      int x = (width - fm.stringWidth(titleBanner))/2;
      g.drawString(titleBanner,x,y);
      y += fontHeights[0];

      // *********
      // Paint the header...
      // *********
      g.setFont(fonts[1]);
      fm = g.getFontMetrics();
      g.setColor(Color.blue);
      for (int i = 0; i < 3; ++i) {
         g.drawString(headers[i],colStart[i],y);
      }
      // Draw line across bottom...
      g.drawLine(colStart[0],y,totalWidth,y);
      y += fontHeights[1];

      // Display the candidate totals...
      String s;
      g.setFont(fonts[2]);
      g.setColor(Color.red);
      double graphicsWidth =
        (double)(totalWidth - colStart[2] + fm.stringWidth(winner) );
      ElectionTable t = ElectionTable.getElectionTable();
      int totalPopular = t.getTotalPopular();
      // *********
      // Show Incumbent...
      // *********
      g.setColor(Color.red);
      int electoral,length;
      Candidate c = t.getIncumbent();
      electoral = c.getElectoral();
      if (electoral >= 270)
         g.drawString(winner,2,y);
      g.drawString(c.getName(),colStart[0],y);
      g.drawString(Integer.toString(electoral),
            colStart[1],y);
      double percent;
      // Get popular vote percentage...
      if (totalPopular > 0)
            percent = ((double)c.getPopular()) /
               ((double)totalPopular);
      else
            percent = 0.0;
      // Paint Text or Graphics?
      if (mode != GRAPHICS) {
         s = Integer.toString(c.getPopular()) +
               " (" + ((float)(percent * 100.00) ) + "%)";
         g.drawString(s,colStart[2],y);
      }
      else {
           if (totalPopular > 0) {
            length = (int)(percent * graphicsWidth);
            g.fillRect(colStart[2],y - fontHeights[2] + 2,
               length,fontHeights[2] - 1);
           } // end if
      }
      y += fontHeights[2];
      // *********
      // Show Challenger...
      // *********
      g.setColor(Color.blue);
      c = t.getChallenger();
      electoral = c.getElectoral();
      if (electoral >= 270)
         g.drawString(winner,2,y);
      g.drawString(c.getName(),colStart[0],y);
      g.drawString(Integer.toString(electoral),
            colStart[1],y);
      // Get popular vote percentage...
      if (totalPopular > 0)
            percent = ((double)c.getPopular()) /
               ((double)totalPopular);
      else
            percent = 0.0;
      // Paint Text or Graphics?
      if (mode != GRAPHICS) {
         s = Integer.toString(c.getPopular()) +
               " (" + ((float)(percent * 100.00) ) + "%)";
         g.drawString(s,colStart[2],y);
      }
      else {
           if (totalPopular > 0) {
            length = (int)(percent * graphicsWidth);
            g.fillRect(colStart[2],y - fontHeights[2] + 2,
               length,fontHeights[2] - 1);
           } // end if
      }
      return yOrg + totalHeight;
   }

}

Summary

This part of the book has brought together many of Java's most powerful elements. Sockets and streams are used to manage network connections, features of AWT are used to display the election results, and multithreading and synchronization constructs are used to run the applet and server application efficiently. The result is a large project that concludes the project-development portion of this book. Part V, "Advanced Applet Development," will focus on smaller, standalone projects, giving you more exposure to the subtleties of Java.