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:
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.
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.