All Categories :
Java
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.
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.
| Method | Description
|
| 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.
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!
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.
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.
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/Interface | Description
|
| Candidate | An accessor class that keeps vote totals for a specific candidate.
|
| ClientPacketAssembler | Tracks and assembles blocks of data packets.
|
| ClientRegistration | For registering as a new client.
|
| ClientUnregistration | For removing client registration with server.
|
| DGTPClient | To receive dynamic data updates from server.
|
| Election | The class for the Election applet that implements the LiveDataNotify interface to manage dynamic data from the server and creates components for visual display.
|
| ElectionTable | Keeps local copy of election results from the server. Uses synchronization so that incoming DGTP data does not collide with reads from visual components.
|
| ElectionUpdater | Periodically causes update of Election's visual components.
|
| LiveDataNotify | Interface that defines methods for handling dynamic delivery of data from the server.
|
| PartialPacket | Private class used by ClientPacketAssembler for assembling packets.
|
| StateBreakdownCanvas | ElectionTable observer that displays results of individual states in text or graphics format.
|
| StateEntry | Simple accessor class that keeps all election data related to a specific state.
|
| SummaryCanvas | ElectionTable 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;
}
}
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.

Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.