All Categories :
Java
Chapter 6
Building a Catalog Applet
CONTENTS
In this part of the book, a basic framework will be developed
for creating catalog applications with general kiosk-like features.
The application is described as "kiosk-like" because
it emphasizes the use of images (rather than buttons and menus)
to guide you through the various pages of the catalog. Consequently,
the catalog will make heavy use of images, a primary subject of
this part of the book.
Since images put a heavy load on the network, smarter methods
are needed for loading them from the server to the client. Chapter 7,
"Java and Images," introduces one of Java's most important
features, multithreading, to illustrate some techniques for loading
images before they are needed, thus making your application faster.
The general overview of multithreading will form the foundations
of thread programming, which will be used frequently in the rest
of the book.
The kiosk will also make use of many of the features native to
Java's applet classes. These classes make it easy to bring audio
and images into your applet. They also give you a way to access
features of the native browser, such as its status bar. One of
the applet classes also provides a gateway for creating links
to other HTML pages. The discussion of how this works will lead
to the illustration of yet another component of Java, the URL
classes. There will be an overview of how to open a stream to
other URL objects, such as an image or text file residing on the
server, and how to bring them into your client applet.
This chapter's tutorial focuses on a variety of ways that the
Applet class can be used to enhance the way your applet works.
Recall that the Applet class provides the foundation for creating
applets-Java applications that run in a browser environment. Besides
launching your applet, the Applet class provides many useful services.
It can be used to load image and audio files, work with URLs,
and access the native browser environment. Since the Applet class
is also a component of the AWT package (as was discussed in Part
II, "Developing a Spreadsheet Applet with the AWT Package"),
Applet objects provide many of the visual features that are part
of the standard AWT repertoire, especially using the Graphics
class for painting text, shapes, and images. Since The Applet
class is a subclass of the AWT Component class, it can handle
events such as mouse events and keystrokes.
Four often misunderstood Applet methods are overridden to manage
the life cycle of an applet. None of these methods are required
to be overridden, although their use will generally give you a
more stable applet. These are the four methods:
- init() This is
used to initialize an applet whenever it is loaded. You typically
override this method to set up resources that will be used throughout
an applet, such as fonts, or to initialize variables. This method
is called once and only once during the lifetime of your applet.
However, if the applet is reloaded for some reason or another,
the init() method will be
called again. Some Java literature may lead you to believe that
you have to always override this method. This is not true!
You need to override init()
only when your applet's circumstances dictate that you should.
A good example of this is initializing resources, such as AWT
components.
- start() This is
called whenever the HTML document on which an applet resides becomes
the current page of a browser. When an applet is first run, the
start() method is called
after init(). Unlike the
latter, however, start()
will be called whenever the user visits the applet's page. Two
very important types of activities should be located in the start()
method. The show() method
of instances of the Frame class are best called in the start()
method. Since Frames occur outside the confines of an applet page,
they will stay onscreen even after you have left the page. Consequently,
they should be shown when you enter the page and hidden when you
leave (see the stop() description
method that follows). There will be an example in the upcoming
listings. The start() method
is also a good place to begin threads since their existence is
also not confined to the page where they began.
- stop() This method
is called whenever the user leaves a page-it is the converse of
the start() method. Therefore,
it's a good place to hide frames and terminate threads.
- destroy() The
destroy() method is called
whenever the applet is being shut down. Typically, this will occur
when the browser is being closed, although there could be other
circumstances that could lead to destroy()
being invoked. This method is a good place to do some cleanup.
However, since it's unpredictable when destroy()
will be called, it should be used with some discretion.
The example that follows shows how these four methods work with
the Frame class, providing an interesting insight into their behavior.
Listing 6.1 shows code used to create a simple frame from an applet.
The init() and destroy()
methods print out their invocations to standard output so their
behavior can be tracked.
Listing 6.1. An applet that creates Frames at initialization.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;
// This applet illustrates how Frames work
// with basic Applet methods
public class AppletBasics extends Applet {
Frame f;
// Called once and only once upon
// applet initialization
public void init() {
System.out.println("In
Applet init. Create frame!");
// Create a frame and make
it visible...
f = new Frame("Applet
Basics test!");
f.resize(300,200);
f.show();
}
// Applet destroyed. Browser probably
shutting down...
public void destroy() {
// Destroy the frame
f.dispose();
System.out.println("In applet destroy!");
}
}
Frame's behavior is curious. If you leave the page where the Frame
was created, the Frame will still be active and visible. This
may or may not be the behavior you want. Furthermore, if you remove
the dispose() method from
the destroy call, you can get some downright undesirable behavior.
In some browsers, you could create multiple instances of the frame
by reopening its location reference. In Figure 6.1, three instances
of the frame were created by going to its location several times.
Figure 6.1 : Creating multiple frames by reloading its location
Listing 6.2 gives the applet its desirable behavior of hiding
the frame every time you leave the page and redisplaying it when
you come back. It simply calls the show()
method of the Frame class in an overridden Applet start()
method and hides the Frame in an overridden stop()
method.
Listing 6.2. An applet that displays the frame only when you
are on the applet's page.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;
// This applet illustrates how Frames work
// with basic Applet methods
public class AppletBasics extends Applet {
Frame f;
// Called once and only once upon
// applet initialization
public void init() {
System.out.println("In
Applet init. Create frame!");
// Create a frame and make
it visible...
f = new Frame("Applet
Basics test!");
f.resize(300,200);
}
// Move the show to the start method so the
Frame
// disappears when you leave the page...
public void start() {
System.out.println("In
applet start!");
f.show();
}
// Hide the frame when you leave the page...
public void stop() {
System.out.println("In
applet stop!");
f.hide();
}
// Applet destroyed. Browser probably
shutting down...
public void destroy() {
f.dispose();
System.out.println("In
applet destroy!");
}
}
It is worth spending a few minutes playing around with this applet
to get a full understanding of how these methods work. Try discarding
or moving the code to see what will happen.
A special HTML tag is used for including applets within a Web
page. The <APPLET> tag is used to indicate that an applet
is to be run at the current location in the HTML. The typical
syntax for the tag would be like the following:
<APPLET CODE="AppletTags"
WIDTH=200 HEIGHT=60>
</APPLET>
The <APPLET> tag has required attributes, as this example
shows. The CODE attribute states the name of the class file that
runs the applet. The "AppletTags" class of this example
will be a subclass of Applet. Java will look in the path specified
in the CLASSPATH variable
to find the class. The WIDTH
and HEIGHT parameters describe
the bounding region of the applet. These are also required and
need to be set to the proper values if the applet is to look the
way you want. Instances of the Window class, like frames, can
appear outside this region. The </APPLET> tag is used to
indicate the end of the HTML applet block.
An interesting characteristic of this <APPLET> tag pair
is that any non-tagged text appearing within this block will appear
in a browser that supports Java, but will not for non-Java-capable
browsers. This feature can be used to indicate that a page is
missing some functionality because the browser does not support
Java. For example, the modifications of the previous lines
<APPLET CODE="AppletTags"
WIDTH=200 HEIGHT=60>
You need to get a Java capable browser!
</APPLET>
will show the appropriate message for a browser that does not
support Java.
The <APPLET> tag supports a couple of other optional attributes.
The ALIGN attribute is used to control the alignment of the applet
on the page. This attribute has about half a dozen possible values,
including LEFT, RIGHT,
TOP, MIDDLE,
and so forth. The ALIGN attribute can be used to wrap text around
an applet. The accompanying CD-ROM has a variation of the following
example that uses the ALIGN attribute to right-align the text.
(The file, which is in the Chapter 6 directory,
is launched by appletrighttags.html.)
The HSPACE and VSPACE attributes use the pixel values to control
spacing between the applet and the text around it.
The CODEBASE attribute can be used to complement the CODE attribute.
It specifies an alternative path where the class specified by
CODE can be found.
Programmers will find the <PARAM> tag to be of the most
interest. It is a separate tag from <APPLET>, although it
appears within its block. The <PARAM> tag consists of NAME-VALUE
attribute pairs that describe the name of a parameter variable
and its corresponding value. It can appear multiple times with
the <APPLET> block. This is illustrated with an example.
Figure 6.2 shows a page displaying a couple of strings of text.
The first line is produced by the normal HTML text mechanism,
but the second line is written out by an applet. Within the <APPLET>
and </APPLET> tag pair are two parameters. The first one,
with the NAME of "text," is used by the applet to display
the second line of text, which is specified by the VALUE attribute.
The second parameter, called "unused," is ignored but
shows how multiple parameters can be included in the <APPLET>
block.
Figure 6.2 : An applet that uses the PARAMETER attribute to specify display text
Listing 6.3. An HTML listing of Figure 6.2 illustrating the
use of <APPLET> and <PARAMETER> tags.
<TITLE>Applet Tags First Test</TITLE>
<HR>
<P> This is HTML text.
<P>
<P>
<APPLET CODE="AppletTags" WIDTH=200 HEIGHT=60>
<PARAM NAME=text VALUE="This is the AppletTags applet">
<PARAM NAME=unused VALUE="This doesn't matter">
You need to get a Java capable browser!
</APPLET>
<P>
<HR>
The Applet class uses the <PARAMETER> tags through the getParameter()
method. This method takes a String indicating the NAME attribute
and returns a String specifying the VALUE attribute. It will return
null if the NAME attribute is not found. Listing 6.4 shows the
code that uses the getParameter()
method to display the second line of text in Figure 6.2.
Listing 6.4. Applet code of Figure 6.2 illustrating the use
of parameters.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;
// This applet takes an applet parameter
// and displays it...
public class AppletTags extends Applet {
String text;
public void init() {
// Get the text to be displayed
from
// the applet tag parameters...
if ((text = getParameter("text"))
== null)
text
= "Parameter not found!";
}
// Display the parameter text...
public void paint(Graphics g) {
g.drawString(text,10,10);
}
}
The project at the end of this chapter makes extensive use of
parameters and gives you a more involved illustration of how parameters
can be constructed in the HTML and read in by the applet.
The Applet class provides some basic methods for loading an image
into memory. Both of these methods return an instance of an implementation
of the Image class. The Image class is an abstract class providing
methods that define the kind of behaviors an image should have.
The underlying implementation of images in the JDK is somewhat
complex. However, it isn't necessary to understand how this works
to display images. Consequently, the internals of the Image and
related classes will be postponed to later chapters. In this chapter,
the focus will be on the basic mechanics of loading an image and
displaying it.
The two forms of the getImage()
method of the Applet class give you a simple way to load an image.
Both these methods require an instance of a URL object. Although
the specifics of the URL class will be discussed in more detail
shortly, it's enough to say here that the URL class is used to
encapsulate a URL. Typically, the URL object used in the getImage()
method will be, in part, generated by one of two Applet class
methods:
| getCodeBase()
| Returns the URL of this Applet class (the one used to start the applet). This could be the value specified in the <APPLET> tag CODEBASE attribute or the directory of the HTML file in which this applet is embedded.
|
| GetDocumentBase()
| Returns the URL of the HTML containing this applet.
|
Perhaps the getImage() method
you will find the most useful is the one that takes two arguments:
a base URL and a String representing the path or filename in relation
to the base. In the example that follows, the image is loaded
from a subdirectory off where the applet HTML is located:
Image img = getImage(getDocumentBase(),"images/mail.gif");
The other getImage() method
takes a URL object as its only parameter. This will have the full
path and filename of the image. The multiple constructors of URL
objects will be discussed shortly, but the construction of a URL
object with a String is illustrated as follows:
Image img = getImage(new URL("http://AFakeServer.com/images/mail.gif"));
Once you get the image, it is ready to be drawn. When the paint()
method is invoked, an image is drawn by calling the drawImage()
method of the Graphics object:
g.drawImage(img,10,10,this);
This code draws the Image created in the previous example at an
x, y coordinate. The last
parameter refers to an implementation of the ImageObserver interface.
This class is used for tracking the progress of an image as it
is loaded and decoded. This makes it possible to show partial
images as the full image is being constructed. The ImageObserver
interface will be discussed in more detail later, along with its
related classes and methods. For now, it's enough to say that
the AWT Component class provides the basic methods necessary for
managing this image display behavior. The "this" of
the above drawImage() sample
code refers to the component displaying the object.
Listing 6.5 and Figure 6.3 illustrate an applet that loads an
image and displays it at different scales each time the applet
receives a mouse click. The image is loaded in the applet init()
method by using getImage().
The first time the image appears, it is at its normal size using
the version of drawImage()
previously discussed. When you click the mouse, it changes the
image's scale and forces the applet to be redrawn. If the image
is not at its normal scale, then it's displayed at its modified
size with the following code:
Figure 6.3 : Drawing a scaled Applet image
int width = img.getWidth(this);
int height = img.getHeight(this);
g.drawImage(img,10,10,scale * width,scale * height,this);
The first two statements use methods of the Image class to get
the width and height of the image. A different version of the
drawImage() method is used
to draw the image scaled to fit inside a bounding box specified
by a width and height. The image is automatically scaled to fit
tightly inside the box. In the case of this example, the width
and height are multiplied by values of 2,
3, and 4.
Listing 6.5. Code for drawing scaled images.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;
public class TestImage extends Applet {
// Load the image off the document base...
Image img;
int scale = 1;
public final int MAX_SCALE = 4;
public void init() {
img = getImage(getDocumentBase(),"images/mail.gif");
// Set toggle state...
}
// Paint the image at its normal size or at
twice
// its normal size...
public void paint(Graphics g) {
// Show at normal scale
if (scale == 1) {
g.drawImage(img,10,10,this);
}
// Or make bigger...
else {
int width
= img.getWidth(this);
int height
= img.getHeight(this);
g.drawImage(img,10,10,scale
* width,
scale
* height,this);
}
}
// Mouse clicks change the scale of the image
public boolean mouseDown(Event ev, int x, int
y) {
++scale;
// If the max size of the
image is reached, then
// go back to normal size...
if (scale > MAX_SCALE)
scale
= 1;
// Force a repaint of the
image...
repaint();
return true;
};
}
An interesting thing to note is that the Toolkit class provides
its own versions of getImage().
One takes a single URL parameter as in the Applet single parameter
getImage() method; this Toolkit
method is used in this chapter's project. The other version of
getImage() takes a single
String parameter that describes the file containing the image.
The Applet class has two simple methods for loading and playing
an audio clip. These play()
methods have two variations, as the getImage()
method does: One takes a fully constructed URL of the desired
audio clip; the other method takes a base URL, plus a String specifying
additional directory and filename information. For example, the
following code could be called to play a sound located off the
HTML directory:
play(getDocumentBase(),"audio/song.au");
The only audio format currently supported by Java is the AU format,
but this limitation will probably be relaxed in the near future.
Another way to play a sound is to create an AudioClip object.
AudioClip is an interface implemented by the native environment
Java is running on. Just like play()
and getImage(), the Applet
class offers two ways to get a reference to an AudioClip. The
Applet class getAudioClip()
method with a single URL parameter is one way of getting a reference
to an AudioClip object:
import java.applet.AudioClip;
//
AudioClip sound = getAudioClip(new URL("http://AFakeServer.com/audio/song.au"));
Note that the AudioClip interface is actually located in the Applet
package. You can also refer to an AudioClip by giving a URL and
a String representing additional directory and filename information.
Once you have an AudioClip, it can be played with the play()
method. This method takes no parameters and plays the clip only
once. The loop() method plays
the sound repeatedly:
sound.loop();
The stop() method is used
to terminate the playing of the audio clip:
sound.stop();
It is important to remember to stop an audio clip when you leave
the page that started the clip. The stop()
method of the applet should, therefore, call the AudioClip stop()
when appropriate.
It is worth spending a few moments to look at the underpinnings
of the Applet class. Closely related to the Applet class is the
AppletContext interface, which represents the underlying applet
environment. This will typically be a browser, such as Netscape
Navigator or HotJava. Therefore, the AppletContext provides a
link to the resources of the browser. The Applet method getAppletContext()
is used to return a reference to this underlying context.
Once you get access to the AppletContext object, it's possible
to do all kinds of interesting things. One of the simplest and
most useful things to do is to display a message on the browser's
status bar:
getAppletContext().showStatus("This
is a message");
The various projects throughout this book use this technique to
display problems to the user. An interesting thing to do is to
use this to show the results of an exception:
try {
//
do something
}
// If exception, then show detail message to status bar
catch (Exception e) {
getAppletContext().showStatus(e.getMessage());
}
Since getAppletContext()
is a method of the Applet context, this code needs to be called
from within an Applet subclass. However, now that this has been
explained, it needs to be pointed out that the Applet class has
its own showStatus() method
with the same function. Therefore, the first code fragment could
just as easily have been the following:
showStatus("This is a message");
Actually, this code does little more than call the underlying
AppletContext showStatus()
method.
Another important thing that the AppletContext can do is return
references of Applets running on the current HTML page. The getApplet()
method takes a String name of an Applet and returns a reference
to it. The getApplet() method
enumerates the applets on the current page. Both of these are
useful for inter-applet communication.
The AppletContext also ties in to one of the basic functions of
a browser-dynamically linking to another HTML page. This can be
done in one simple method call! The basic form of showDocument()
takes a URL and makes it the current HTML of the browser. This
implies that the stop() method
of the current applet will be called, because its container page
will no longer be current. This chapter's project will use showDocument()
to link from one page to another. Here is a code fragment from
the project:
try {
URL
u = new URL(URLBase,link);
System.out.println("Show
new doc: " + u);
a.getAppletContext().showDocument(u);
}
catch (MalformedURLException e) {
a.showStatus("Malformed
URL: " + link);
}
catch (Exception e) {
a.showStatus("Unable
to go to link: " + e);
}
It creates a URL of the new page to be loaded and calls showDocument()
to go to it. Note how the browser status bar is used to display
any errors.
An alternative form of showDocument()
takes a second String parameter that specifies the target frame
or window to place the loaded page. This is useful for browsers
that support framed pages. Note, however, that this method (like
the other AppletContext methods) might not do anything if the
native browser does not support the action.
Finally, a word should be said about the AppletStub interface.
This interface is used to create a program to view applets. Consequently,
any browser that supports Java will need to use the AppletStub
interface to make the Applet class functional.
Since the Uniform Resource Locator, or URL, is at the heart of
the World Wide Web, it is only appropriate that an Internet language
like Java has a URL class. Several examples of how to create and
use a URL object have already been presented in this chapter.
There are four different constructors of URL objects. Two of them
should already be familiar. The simplest constructor takes a string
and converts it to a URL:
URL u = new URL("http://AFakeServer.com/");
Another method should also be familiar. It takes a URL and a String
representing a relative path and creates a new URL from it.
URL urlNew = new URL(u,"audio/sound.au");
Recall that a URL typically consists of a protocol, the name of
the host computer, and a path to the location of the resource.
A third URL constructor takes these as protocol, host, and file
Strings, respectively, and returns a URL. The final constructor
adds a String specifying the port as an additional parameter.
However, protocols generally have a fixed port number (HTTP is
80), so this information is usually not needed.
A couple of methods can be used for deconstructing a URL. The
following code prints the protocol, host, port, file, and finally
the URL itself of the HTML of the applet:
System.out.println("Protocol: "
+ getDocumentBase().getProtocol());
System.out.println("Host: " + getDocumentBase().getHost());
System.out.println("Port: " + getDocumentBase().getPort());
System.out.println("File: " + getDocumentBase().getFile());
System.out.println("URL: " + getDocumentBase());
Once constructed, the URL can be used to open up a network connection
to the URL. This is done through the openStream()
method, which returns an instance of InputStream. You saw how
InputStream classes worked in Chapter 4,
"Enhancing the Spreadsheet Applet." The FilterInputStream
subclasses can be constructed from this to create high-level interfaces
to the streams. The DataInputStream class can be used to read
in streams according to a specified data type, such as Strings.
Listing 6.6 shows how to combine the URL and stream classes for
a quick and easy printout of the contents of the HTML containing
an applet.
Listing 6.6. Printing the contents of the applet's HTML.
void printSelf() {
// Open up a stream to the document URL
and
// print its contents...
try {
DataInputStream
dis = new DataInputStream(
new
BufferedInputStream(
getDocumentBase().openStream()
) );
String s;
while ( (s = dis.readLine())
!= null)
System.out.println(s);
System.out.println("EOF");
}
catch (IOException e) {
System.out.println("URL
read error");
}
}
The key to this is the first line in the try
clause. The URL of the base document is taken from getDocumentBase();
its OpenStream() method is
then applied. Once the stream is open, an instance of the easy-to-use
DataInputStream class is created. Each line of the HTML is then
fetched by the readLine()
method of DataInputStream and sent to standard output.
This chapter's project begins the development of a kiosk-style
online catalog. It has a couple of interesting characteristics.
First of all, it uses HTML applet parameters to describe how each
applet is constructed and operates. Each page in the catalog has
images describing the current choices. Figure 6.4 shows the main
menu of the catalog. The images are loaded and displayed by the
applet, not the HTML. When a choice is made, the applet jumps
to the next HTML and reloads the applet with the new parameters.
Figure 6.4 : Main menu of the online catalog
Since the applet makes extensive use of images, it poses certain
problems. Images use much network bandwidth and so need to be
used efficiently. The project works around this problem by creating
a MediaLoader class that acts as a cache for images. In the next
chapter, this class is improved by acting as a pre-loader of images
before the next applet is retrieved.
Another notable feature of the project is its configurability.
The same applet runs on every page of the catalog; its features
are determined by <APPLET> tag parameters in the current
HTML. Furthermore, it uses a URL stream to load in additional
data to be displayed on the button. This data comes from a local
text file, which can be edited outside the actual applet code.
Table 6.1 lists the classes used in this chapter's version of
the catalog applet.
Table 6.1. Catalog project classes.
| Class | Description
|
| CacheEntry | Represents a single entry in the image cache maintained by the MediaLoader class.
|
| Catalog | The Applet class that takes HTML parameters and constructs the components that represent the current choices.
|
| CatalogButton | A image-based button that shows text representing a choice and links to another Catalog applet when selected.
|
| MediaLoader | A class that actually loads the images and uses static methods to employ a cache that exists across Catalog applet invocations.
|
| MediaLoaderException | An exception thrown by the MediaLoader when there is a problem.
|
| SelectionCanvas | Displays a large image representing a possible choice of the user. Appears to the right of its companion CatalogButton.
|
Listing 6.7 shows the HTML of the catalog page displayed in Figure
6.4. As the listing shows, the HTML does not actually display
anything. The <PARAM> tag fields actually tell the applet
what to display. These are passed to the Catalog applet run for
each page of the applet. It reads in the parameters to determine
what images to display. There are three rows of display for each
Catalog applet. On each row there is a field (also known as a
CatalogButton, after its class) that appears on the left-hand
side; it is effectively a button that appears as an image. The
field is complemented by a larger image that appears on its right
(called a SelectionCanvas, after its class).
The three <PARAM> tagsý whose NAME attribute begins
with the "field" prefix specify the left-hand image
buttons. The corresponding VALUE attribute has four subfields
used to create the CatalogButton. The first subfield is the name
appearing on the button. The second is the image to be displayed
in the button's area. The third subfield is a style, which represents
the size of the button. Although the MEDIUM style is the only
one used in the sample applets, its existence gives you a way
of customizing the applet. The last subfield specifies the URL
that the applet goes to when you click the image button.
The three <PARAM> tags whose NAME attribute begins with
the "image" prefix specify the SelectionCanvas objects
that appear to the right of the fields. The VALUE attribute specifies
the image to be displayed in the canvas area.
The <PARAM> tag with the NAME attribute of "data"
specifies a URL containing text data that can be used to complement
the display of the CatalogButton objects. This data would be such
things as "On Sale" that would appear underneath the
larger font name of the button.
Listing 6.7. The HTML of the main catalog page (index.html).
<title>Catalog Applet</title>
<hr>
<applet code="Catalog" width=400 height=300>
<param name=field1 value="Computers,catalog/field1.gif,MEDIUM,computer/main.html">
<param name=image1 value="catalog/selection1.gif">
<param name=field2 value="Software,catalog/field1.gif,MEDIUM,software/main.html">
<param name=image2 value="catalog/selection1.gif">
<param name=field3 value="Accessories,catalog/field1.gif,MEDIUM,accessory/Âmain.html">
<param name=image3 value="catalog/selection1.gif">
<param name=data value="catalog/data.txt">
</applet>
<hr>
Listing 6.7 gives the full listing of the Catalog class. This
subclass of Applet represents the applet loaded for every page
in the catalog project. The initialization of the applet has three
steps. Its main job is to create the CatalogButton and SelectionCanvas
objects by parsing out the parameters specified in the current
HTML. However, it also traps for a mouse click on one of the CanvasButton
objects in handleEvent().
When this occurs, it calls the CanvasButton select()
method, which may result in the browser loading in a new URL representing
a new page in the catalog.
After the Catalog applet initializes the fonts, it gets the applet
parameter in the HTML that corresponds to the "data"
NAME attribute. It does this through the getParameter()
method, which will return either a String representation of the
corresponding VALUE attribute, or a null value if the name cannot
be found. The getParameter()
method is used to get all the CatalogButton and SelectionCanvas
parameters that follow.
After the "data" value is retrieved, it is used to derive
a URL that contains the additional text data. This is performed
in the applet's loadURLData()
method. It creates a URL object by taking the path to the text
data in relation to the base document of the current HTML:
u = new URL(getDocumentBase(),dataPath);>
It uses this URL object to open up an input stream to the text
file, then reads the data in by Strings delimited by newlines.
This process is similar to the example discussed in the "Creating
and Reading a URL" section above.
The last step in the applet's initialization is to create the
three rows of CatalogButton and SelectionCanvas couplets. It uses
the getParameter() method
to get the information needed to create the canvas components.
In the createCatalogButton()
method, the parameter values are parsed with an instance of the
StringTokenizer class. Given a set of delimiters (like commas),
this class simply walks through and produces String tokens that
appear between the delimiters.
The other thing that the Catalog class does is handle the painting
of the applet. This is simple because it walks through the three
rows and displays the components based on the size of their images.
Listing 6.7. The Catalog class.
import java.awt.*;
import java.lang.*;
import java.util.StringTokenizer;
import java.applet.*;
import java.net.URL;
import java.io.DataInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
// This is the main class that loads the parameters
// for the current applet and sets up the images
// fields.
public class Catalog extends Applet {
CatalogButton button[] = new CatalogButton[3];
SelectionCanvas drawing[] = new SelectionCanvas[3];
// Three styles
private static final int SMALL_STYLE = 0;
private static final int MEDIUM_STYLE = 1;
private static final int LARGE_STYLE = 2;
private static final int DEFAULT_STYLE = MEDIUM_STYLE;
Font styleFont[] = new Font[3];
Font dataFont;
String data[] = new String[3];
// Initialize the graphic display...
public void init() {
// First create fonts...
styleFont[0] = new Font("Helvetica",Font.PLAIN,16);
styleFont[1] = new Font("Helvetica",Font.BOLD,18);
styleFont[2] = new Font("Helvetica",Font.BOLD,24);
dataFont = new Font("TimesRoman",Font.ITALIC,14);
// Get the additional data
from URL...
loadURLData();
// Add the components...
addComponents();
show();
}
// Add the components to the display...
void addComponents() {
// Create Font for buttons
String fieldParam;
String selectParam;
// Add first row of field
and display image...
if (((fieldParam = getParameter("field1"))
!= null) &&
((selectParam
= getParameter("image1")) != null) ) {
button[0] = createCatalogButton(fieldParam,data[0]);
drawing[0] = new SelectionCanvas(this,
selectParam,getDocumentBase());
button[0].resize(150,100);
drawing[0].resize(250,100);
} // end if
else {
getAppletContext().showStatus("Invalid
parameter");
button[0] = null;
}
// Add second row of field
and display image...
if (((fieldParam = getParameter("field2"))
!= null) &&
((selectParam
= getParameter("image2")) != null) ) {
button[1] = createCatalogButton(fieldParam,data[1]);
drawing[1] = new SelectionCanvas(this,
selectParam,getDocumentBase());
drawing[1].resize(250,100);
button[1].resize(150,100);
} // end if
else {
getAppletContext().showStatus("Invalid
parameter");
button[1] = null;
}
// Add third row of field
and display image...
if (((fieldParam = getParameter("field3"))
!= null) &&
((selectParam
= getParameter("image3")) != null) ) {
button[2] = createCatalogButton(fieldParam,data[2]);
drawing[2] = new SelectionCanvas(this,
selectParam,getDocumentBase());
button[2].resize(150,100);
drawing[2].resize(250,100);
} // end if
else {
getAppletContext().showStatus("Invalid
parameter");
button[2] = null;
}
}
// Load additional data from URL...
void loadURLData() {
// Get path to data from parameter...
String dataPath = getParameter("data");
if (dataPath == null) {
System.out.println("No
data variable found");
return;
} // end if
// Create URL for data...
URL u;
try {
u = new URL(getDocumentBase(),dataPath);
}
catch (MalformedURLException e) {
System.out.println("Bad
Data URL");
return;
}
// Now load the data by opening up a stream
// to the URL...
try {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
u.openStream()
) );
// Read only the first three lines...
int i;
for (i = 0; i < 3; ++i) {
data[i]
= dis.readLine();
} // end for
}
catch (IOException e) {
System.out.println("URL
read error");
}
}
// Update message sent when repainting is needed...
// Prevent paint from getting cleared out...
public void update(Graphics g) {
paint(g);
}
// Repaint all the canvas components...
public synchronized void paint(Graphics g) {
int i,x,y;
Dimension dm;
int defHeight = 150;
// Go through the buttons...
for (x = y = i = 0; i < 3; ++i) {
if (button[i] != null) {
button[i].paint(g,x,y);
dm = button[i].size();
x += dm.width;
drawing[i].paint(g,x,y);
x = 0;
y += dm.height;
}
else
y += defHeight;
} // end for
}
public boolean mouseDown(Event ev, int x, int y) {
// See if you clicked on any
of the buttons...
for (int i = 0; i < 3;
++i) {
if ((button[i]
!= null) && (button[i].inside(x,y)) ) {
System.out.println("Hit
Button " + i);
//
Link to the button's selected field...
button[i].select();
break;
} //
end if
}
return true;
};
// Parse a parameter and create catalog field...
CatalogButton createCatalogButton(String param,String
data) {
// Set up defaults...
String fieldName = "";
String imageName = "";
String style = "MEDIUM";
String link = getDocumentBase().toString();
// Parse out the string...
StringTokenizer s = new StringTokenizer(param,",");
if (s.hasMoreTokens()) {
fieldName = s.nextToken();
if (s.hasMoreTokens()) {
imageName = s.nextToken();
if (s.hasMoreTokens()) {
style =
s.nextToken();
if (s.hasMoreTokens())
{
link
= s.nextToken();
}
} // end style if
} // end image if
} // end field if
// Figure out the style. Convert it all
to uppercase...
style = style.toUpperCase();
int styleType;
if (style.equals("MEDIUM"))
styleType
= MEDIUM_STYLE;
else if (style.equals("SMALL"))
styleType
= SMALL_STYLE;
else if (style.equals("LARGE"))
styleType
= LARGE_STYLE;
else
styleType
= DEFAULT_STYLE;
// Create button according to these parameters...
return new CatalogButton(this,
imageName,fieldName,
styleFont[styleType],getDocumentBase(),
link,data,dataFont);
}
}
The CatalogButton class is used to represent the choices you can
make for each applet screen. It appears on the left-hand side
of the applet and goes to another URL when the button is selected.
This URL will represent another page in the catalog.
The CatalogButton constructor takes as input the main and sub
text fields, the corresponding fonts, the background image, and
a URL and relative path used to construct the URL the button will
link to. One of the key things about the class is that its Image
object is loaded through the MediaLoader class (described shortly)
and not through the applet's getImage()
method. This approach takes advantage of the caching techniques
that will be developed in the MediaLoader throughout this chapter.
The paint() method is invoked
by the Catalog class and passed the x-y coordinate where the image
should be painted on the applet. It draws the image by using the
drawImage() method. It then
draws a title field in the center of the button. The FontMetrics
are used to center the text. Finally, if an additional data field
exists, it is drawn underneath the title in a smaller font.
If the button is selected, the CatalogButton object changes its
text to white by setting an internal state variable and forcing
a repaint. It then creates a URL object out of the base URL and
its relative path. This URL represents the new catalog page in
which to link. If the URL is successfully created, it links to
the new page with the showDocument()
method. If there is an error, then a message is displayed on the
browser's status bar.
Listing 6.8. The CatalogButton class.
import java.awt.*;
import java.lang.*;
import java.net.*;
import java.applet.*;
// This class represents a button on the current page.
// It represents the lefthand element of a kiosk selection
// that has a background
// image and text describing what the button represents.
// The field has a link to the Web page it should go to
// if it is selected...
// Parent of button is responsible for sizing and setting
// correct position...
public class CatalogButton extends Canvas {
String text; // What to display on left side...
String data; // Additional data
URL URLBase; // The base URL to link from...
String link; // Where to link to relative to
base...
Image img; // Background image...
Font f; // Font to paint with...
Font dataFont; // Font to paint data
with...
Applet a; // Use its ImageObserver...
int lastX,lastY; // Store last coordinates....
// Store states of button
private static final int NORMAL_STATE = 0;
private static final int SELECTED_STATE = 1;
int state; // From above states...
// Create the catalog button...
public CatalogButton(Applet aIn,
String backgroundImage, String
textIn, Font fIn,
URL URLBaseIn, String linkIn,
String dataIn,
Font dataFontIn) {
// Store
parameters...
a = aIn;
text = textIn;
f = fIn;
URLBase
= URLBaseIn;
link = linkIn;
lastX =
lastY = -1;
state =
NORMAL_STATE;
data = dataIn;
dataFont
= dataFontIn;
// Now start
loading the background image
// through
the Media Loader...
try {
img
= MediaLoader.getMediaLoader().loadImage(URLBase,backgroundImage);
}
catch (MediaLoaderException
e) {
img
= null;
a.showStatus(e.getMessage());
}
}
// Paint the image...
public synchronized void paint(Graphics g,int
x,int y) {
// Kick out if internal image
is bad...
if (img == null)
return;
// Resize the image, if necessary...
Dimension dm = size();
g.drawImage(img,x,y,dm.width,dm.height,a);
// Center font in image...
int textX,textY;
g.setFont(f);
FontMetrics fm = g.getFontMetrics();
if (state == NORMAL_STATE)
g.setColor(Color.black);
else
g.setColor(Color.white);
textX = x + ((dm.width - fm.stringWidth(text))/2);
textY = y + ((dm.height -
fm.getHeight())/2);
g.drawString(text,textX,textY);
// Show additional data...
g.setFont(dataFont);
fm = g.getFontMetrics();
if (data != null) {
textX = x + ((dm.width -
fm.stringWidth(data))/2);
textY += (2 * fm.getHeight());
g.drawString(data,textX,textY);
} // end if
// Store the coordinates...
lastX = x;
lastY = y;
}
// See whether coordinates are inside this button...
public synchronized boolean inside(int x,int
y) {
// Kick out if not ready yet...
if ((lastX < 0) || (lastY
< 0))
return false;
// Make clipping rectangles
for comparions...
Dimension dm = size();
Rectangle thisRect,inRect;
thisRect = new Rectangle(lastX,lastY,dm.width,dm.height);
inRect = new Rectangle(x,y,0,0);
// See rectangles overlap...
if (thisRect.intersects(inRect))
return true;
return false;
}
// Button was selected...
public synchronized void select() {
state = SELECTED_STATE;
// Force repaint to show selected
state...
Dimension dm = size();
a.repaint(lastX,lastY,dm.width,dm.height);
// Go to the next URL if there
is a link...
if ((link != null) &&
(link.length() > 0)) {
try {
URL
u = new URL(URLBase,link);
System.out.println("Show
new doc: " + u);
a.getAppletContext().showDocument(u);
}
catch (MalformedURLException
e) {
a.showStatus("Malformed
URL: " + link);
}
catch (Exception
e) {
a.showStatus("Unable
to go to link: " + e);
}
} // end if
}
}
This class, shown in Listing 6.9, is really a simplified version
of the CatalogButton. Like CatalogButton, it loads an Image from
the MediaLoader. It paints the image at the specified applet parameters
with the drawImage() method
of the Graphics class.
Listing 6.9. The SelectionCanvas class.
import java.lang.*;
import java.awt.*;
import java.net.URL;
import java.applet.*;
// Shows visual cue as to what an item is...
public class SelectionCanvas extends Canvas {
URL URLBase; // The base URL to link from...
Image img; // Display image...
Applet a; // Use its ImageObserver...
// Create the canvas display...
public SelectionCanvas(Applet aIn,
String displayImage,URL URLBaseIn) {
// Store
parameters...
a = aIn;
URLBase
= URLBaseIn;
// Now start
loading the background image
// through
the Media Loader...
try {
img
= MediaLoader.getMediaLoader().loadImage(URLBase,displayImage);
}
catch (MediaLoaderException
e) {
img
= null;
a.getAppletContext().showStatus(e.getMessage());
}
}
// Paint the image...
public synchronized void paint(Graphics g,int
x,int y) {
// Kick out if internal image
is bad...
if (img == null)
return;
// Resize the image if necessary...
Dimension dm = size();
g.drawImage(img,x,y,a); //
dm.width,dm.height,a);
}
}
Listing 6.10 shows some of the code for the MediaLoader class.
This class will be a focal point for developing the catalog project
throughout this part of the book. Eventually, the MediaLoader
class will pre-load images of possible catalog pages that may
be viewed. It will be developed in the next chapter as a background
thread, but in this chapter, it will be part of a single-task
applet.
The MediaLoader also has an internal cache that keeps track of
images that have been loaded; its major feature is that it needs
to persist across applet invocations. So if you are on one page
of the catalog, go to a Yahoo page, and then go to another catalog
page, the MediaLoader cache should still persist and return any
pre-loaded images that might be found.
To allow the MediaLoader to have a persistent cache, you need
to prevent the MediaLoader from being instantiated by another
object. Therefore, it has a private constructor. Its variables
are static, so they exist for the class and not for a specific
instantiation. The cache, a Hashtable object, is created once
and only once for the MediaLoader. Therefore, the cache can persist
whether a catalog page is present or not. The MediaLoader and
the cache will exist until they are destroyed-probably by the
browser being shut down.
The only public method of the MediaLoader is loadImage().
Like getImage(), it takes
a URL and a relative path as its parameter. Eventually, the method
calls getImage()-although
it uses the Toolkit version of the method, as opposed to the applet
version. It is structured to do this because the MediaLoader should
not be tied to a specific applet; the Toolkit class, which also
persists outside a specific applet, is, therefore, a good match.
After creating the URL, the loader checks to see whether the Image
object is in its cache. The cache is a Hashtable that takes URL
objects as its key and a CacheEntry object as its data. The CacheEntry
object is an instantiation of a simple accessor class that does
nothing more than contain an Image and an age variable (the function
of which will be discussed briefly). If the URL is found in the
cache, the corresponding Image is returned. If it is not found,
then the Toolkit's getImage()
method is called. The returned image is then placed in the cache.
As shown in Listing 6.10, the age field is used for the MediaLoader's
internal "garbage collector." A static integer counter
called currentAge increases every time the loadImage()
method is invoked. The cache entry of the Image returned is then
set to an age that matches the currentAge. This way, it's possible
to tell when an Image has not been used for a while. Occasionally,
the loadImage() method will
invoke a method called sweeper().
The role of this method is to remove any cache entries that have
not been used for a while. It does this by enumerating the CacheEntry
objects in the cache, sorting them by age, and removing any objects
older than a certain limit. This limit is set to the size of the
cache; the goal is to keep the number of entries in the cache
to a number near its original size.
In the next chapter, the sweeper()
method will be replaced by a background thread that runs independently
of the MediaLoader.
Listing 6.10. The MediaLoader class.
// This class loads image media through
URL commands and
// keeps a local cache of images. Each image has an
// address used for aging. When the image is too
// old, then it is removed from that cache.
public class MediaLoader {
// The loader is static and so can be created
only once
private static MediaLoader loader = new MediaLoader();
// Cache size is used for tracking age of URL
static int cacheSize = 40;
static int currentAge = 0;
// Cache is hashtable...
static Hashtable cache = new Hashtable(cacheSize);;
// Private internal constructor: Create the
cache...
private MediaLoader() {
}
// Return reference to this MediaLoader object
public static synchronized MediaLoader getMediaLoader()
{
return loader;
}
// Load an image through a URL
// Check to see whether it is in the cache
// If it isn't, then load it in, store in cache,
// and return it
public synchronized Image loadImage(URL base,String
name) throws ÂMediaLoaderException
{
// Create a URL for the image...
URL u;
try {
u
= new URL(base,name);
}
catch (MalformedURLException
e) {
throw
new MediaLoaderException("Malformed URL");
}
// See whether it is in the
cache...
++currentAge;
CacheEntry ce = (CacheEntry)
cache.get(u);
// If it's in the cache, update
the age and
// return image...
if (ce != null) {
ce.setAge(currentAge);
System.out.println("MediaLoader:
Cache hit URL " + u);
return ce.getImage();
}
// See whether you need to
run the sweeper...
// Just run it every 20 fetches...
if ((currentAge%20) == 0)
sweeper();
// Otherwise, get the Image...
System.out.println("MediaLoader:
Loading URL " + u);
Image img = Toolkit.getDefaultToolkit().getImage(u);
// Put in cache...
cache.put(u,new CacheEntry(img,currentAge));
return img;
}
// Removes any item from cache that has an
// age that is too old...
private synchronized void sweeper() {
// Do nothing if cache is
too small...
if (cache.size() < cacheSize)
return;
CacheEntry ce;
// Array for placing hashtable
elements...
int ages[] = new int[cache.size()];
// First step is to go through
and get all the ages...
Enumeration em = cache.elements();
for (int i = 0; em.hasMoreElements();
++i) {
ce = (CacheEntry)em.nextElement();
ages[i]
= ce.getAge();
}
// Next step is to get minimum
age...
// This is ugly since you
have to perform
// a sort...
sort(ages);
// Now get nTh element
int minAge = ages[cacheSize
- 1];
// Do nothing if you have
nothing that's old...
if (minAge > (currentAge
- cacheSize)) {
System.out.println("Nothing
is old enough. No cleaning necessary...");
return;
}
System.out.println("Run
Sweeper. Min Age = " + minAge);
// Final step is to walk through
and remove
// old elements...
em = cache.keys();
URL u;
while (em.hasMoreElements())
{
u = (URL)em.nextElement();
// Get cache
entry...
ce = (CacheEntry)cache.get(u);
// See whether
it's too old...
if (ce.getAge()
< minAge) {
System.out.println("Remove
cache element: " + u);
cache.remove(u);
}
}
}
// The identifying String of the loader
public String toString() {
return ("MediaLoader
ID: " + hashCode());
}
}
Listing 6.11 shows the MediaLoaderException class. An instance
of this class is thrown whenever there's a problem with the MediaLoader.
The Exception will usually have a custom detailed message attached
to it. Note that it is a subclass of the AWTException class, which
is a hierarchy of Exceptions related to the AWT package.
Listing 6.11. The MediaLoaderException class.
import java.awt.AWTException;
// Create object for throwing MediaLoaderExceptions...
public class MediaLoaderException extends AWTException {
public MediaLoaderException(String msg) {
super(msg);
}
}
In this chapter, you see the first steps for developing a catalog-style
application. Its most interesting feature is a cache that uses
static methods to persist across browser pages. This MediaLoader
cache is expanded on in the next chapter to pre-load images from
the next pages of the catalog. This added function is done in
the context of introducing you to the world of multithreading.
Not only will the loader run as a thread, but so will the "sweeper"
that removes old images from the cache.

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.