Depending on what you need to accomplish, one applet, or even several distinct applets, might not always be enough. Fortunately, applets can communicate with each other and cooperate to perform more complicated jobs. Teams of applets can produce effects that single applets working alone cannot.
Applet communication is accomplished in conventional ways: applets can call methods on one another or communicate through sockets or other data streams. The tricky part is actually finding one another. Applets can actually find each other in more than one way, and each mechanism has its advantages and limitations. This chapter discusses four mechanisms and presents a complete example applet that uses one of them.
The Java API has a built-in feature that is explicitly intended to support applet cooperation: the getApplet and getApplets methods in the AppletContext class. Using these facilities, applets can find each other by name. Here's how to call getApplet:
Applet friend = getAppletContext().getApplet("Friend");
Once that call completes, the friend variable will be set to the actual applet instance of the applet called "Friend" (if such an applet exists). If "Friend" is an instance of, say, Sun's Animator applet, friend will contain a reference to that object.
Applet names are specified in HTML, not in the Java code. To create an animator applet that could be found using the previous call, you could write HTML like this:
<applet code="Animator.class" width=25 height=25 name="Friend">
<!-- applet parameters go here -->
</applet>
The getApplets method is similar to the singular getApplet, except that it returns an Enumeration, which lists all the accessible applets. Applets can then be queried for various characteristics, including name. Here's how to find the "Friend" applet using getApplets:
Applet friend;
for ( Enumeration e = getAppletContext().getApplets();
e.hasMoreElements();
) {
try {
Applet t = (Applet) e.nextElement();
if ("Friend".equals(t.getParameter("name"))) {
friend = t;
break;
}
}
catch (ClassCastException e) {
}
}
That's obviously a lot more work, so you wouldn't want to use that method to find just a single applet. It can sometimes be useful in situations when you are looking for multiple applets, however, or where you don't know the precise names of the applets with which you need to rendezvous. For example, it's fairly easy with getApplets to find all the applets with names that begin with a certain string, such as "Helper", so that your applet could work with any number of appropriately named helper applets that might appear on the same Web page.
Unfortunately, there are at least two serious problems with these official applet location mechanisms. First, the proper behavior of these mechanisms currently is not completely specified, so different applications may choose to implement them in different ways. For example, the getApplets method returns only accessible applets, but there is no definition of what is meant by "accessible." You might only be able to see applets on the same page, applets that were loaded from the same network site, or the smaller set of applets that meet both of those restrictions, depending on the browser (or the version of a browser) within which your applet is running. There are other such implementation dependencies, and current applet environments actually differ in their interpretations. This limitation should cease to be a factor as Sun and Java's licensees work out a consistent, thorough specification for applet coordination mechanisms. For now, however, implementation differences are a nuisance.
The other problem is not so likely to go away. It's easy to understand, but it complicates things somewhat, and it has taken many applet programmers by surprise. The problem is that getApplet and getApplets won't show you an applet until that applet has been fully loaded and initialized. Because of the vagaries of the network and other variables such as applet size, there's no way to predict which applet on a page will be loaded first, or which will be loaded last. This means that the obvious implementation approach-where one controlling applet starts, looks up the other applets, and begins directing the coordinated effort-won't work without some extra effort.
There are ways around that problem, though. The controlling applet can check for its collaborators and, if they are not all present, sleep for a short while (a second or so) before checking again, looping until all the members of the team have been initialized. Such polling is inefficient and may result in a longer startup delay than necessary, but it will work. A better solution would be a two-way search-and-notification mechanism, in which the controlling applet searches for other applets when it is initialized, and the other applets attempt to locate and notify the controlling applet when they are initialized. That way, if all the helpers initialize first, the controller will find them immediately and can begin directing the cooperation, but if some of the helpers are initialized later, the controller will be notified immediately.
In many circumstances, it's possible to establish inter-applet communication by using static variables and methods within a common class. If multiple applets all depend on a common class in some way, they can use the class as a rendezvous point, registering their presence there and learning about the presence of other applets.
Here is an example to illustrate the point. If the ColorRelay applet is used multiple times on a Web page, the different instances will cooperate to flash their own copies of an image, in different colors, in round-robin fashion. You can think of the applets as relaying a token between themselves. Whoever has the token flashes an image in color, and the rest of the images are in black and white. Figure 1.1 shows ColorRelay in action on a page, with the middle applet flashing green. Listing 1.1 shows the HTML file for the page shown in Figure 1.1.
Figure 1.1 : The ColorRelay applet in action.
Listing 1.1. ColorRelay.html.
<html>
<body>
<h1>Used Applets Sale!</h1>
<p>
<applet align=baseline code="COM.MCP.Samsnet.tjg.ColorRelay.class"
width=50 height=50 name="first">
<param name="flashColor" value="0x0000ff">
<param name="sleepTime" value="1">
<param name="image" value="spiral.gif">
</applet>
Low, low prices!
<p>
<applet align=baseline code="COM.MCP.Samsnet.tjg.ColorRelay.class"
width=50 height=50>
<param name="flashColor" value="0x00ff00">
</applet>
This week only!
<p>
<applet align=baseline code="COM.MCP.Samsnet.tjg.ColorRelay.class"
width=50 height=50>
<param name="flashColor" value="0xff0000">
<param name="sleepTime" value="3">
</applet>
We won't be undersold!
</html>
Listing 1.2 is an overview of the ColorRelay applet, with methods replaced by comments. The code for the methods will appear in later listings.
Listing 1.2. ColorRelay.java (part 1).
/*
* ColorRelay.java 1.0 96/04/14 Glenn Vanderburg
*/
package COM.MCP.Samsnet.tjg;
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
/**
* An applet which coordinates with other instances of itself on a Web
* page to alternately flash copies of an image in different colors.
*
* @version 1.0, 14 Mar 1996
* @author Glenn Vanderburg
*/
public class
ColorRelay extends Applet implements Runnable
{
// These are used to maintain the list of active instances
static ColorRelay list, listTail;
ColorRelay next, prev;
// This thread switches between instances
static Thread relayer;
// This is the original, unmodified base image which all
// of the instances use.
static Image originalImage;
// The color that this instance uses to flash. White is the default.
Color flashColor = Color.white;
// The modified, colorized image.
Image modifiedImage;
// The image currently being displayed. This reference
// alternates between originalImage and modifiedImage.
Image image;
// We use a media tracker to help manage the images.
MediaTracker tracker;
// The time we wait while flashing. Two seconds is the default.
int sleepSecs = 2;
// Method: static synchronized
// addToList(ColorRelay elem) Listing 1.3
// Method: static synchronized
// removeFromList(ColorRelay elem) Listing 1.3
// Method: public init() Listing 1.4
// Method: public start() Listing 1.5
// Method: public stop() Listing 1.5
// Method: public run() Listing 1.5
// Method: public getAppletInfo() on CD
// Method: public getParameterInfo () on CD
// Method: public paint(Graphics g) on CD
// Method: public update(Graphics g) on CD
// Method: flash() on CD
// Method: parseRGB(String str) on CD
}
As you can see, there are several ordinary instance variables: a couple of images, a color, a media tracker, a duration in seconds, and a couple of link fields so that an instance of ColorRelay can be a member of a linked list. In addition, there are four static variables: the original image, which all the instances display when it's not their turn to flash, a thread that coordinates the activities of the applets, and the head and tail elements of the list of applets.
Using static variables for communication doesn't mean that the applets are somehow magically all initialized at the same time. The different instances are all started separately, and there's no guarantee that they will be initialized in any particular order. There is one guarantee, however: before even the first ColorRelay applet is created and initialized, the ColorRelay class will have been initialized, so all the applets will have the static variables available as soon as they start.
You have to be careful when you use static variables, though, because multiple instances might be trying to use them simultaneously. To help manage that, I've used two synchronized methods to add and remove applets from the list. Because they are synchronized static methods, the ColorRelay class is locked while they are running, preventing concurrent access. The two methods are shown in Listing 1.3. Note that, as soon as the first element is added to the list, the controller thread is started. We'll see later that the thread is written to stop automatically when the last element is removed from the list at some later time.
Listing 1.3. ColorRelay.java (part 2).
/**
* Adds an instance to the list of active instances maintained in the
* class. No check is made to prevent adding the same instance twice.
* @param elem the ColorRelay instance to add to the list.
* @see #removeFromList
*/
static synchronized void
addToList(ColorRelay elem) {
if (list == null) {
list = listTail = elem;
elem.next = elem.prev = null;
// Because the list has elements now, we should start the thread.
relayer = new Thread(new ColorRelay());
relayer.start();
}
else {
elem.prev = listTail;
listTail.next = listTail = elem;
elem.next = null;
}
}
/**
* Removes an instance from the list of active instances maintained in
* the class. Works properly but does <em>not</em> signal an error if
* the element was not actually on the list.
* @param elem the ColorRelay instance to be removed from the list.
* @see #addToList
*/
static synchronized void
removeFromList(ColorRelay elem) {
ColorRelay curr = list;
while (curr != null && curr != elem) {
curr = curr.next;
}
if (curr == elem) { // We found it!
if (list == curr) {
list = curr.next;
}
if (listTail == curr) {
listTail = curr.prev;
}
if (curr.next != null) {
curr.next.prev = curr.prev;
}
if (curr.prev != null) {
curr.prev.next = curr.next;
}
curr.next = curr.prev = null;
}
// If curr is null, then the element is not on the list
// at all. We could treat that as an error, but I'm
// choosing to report success.
return;
}
The init method-called when the applet is created-checks, converts, and stores the applet's parameters. Special care must be taken with the image parameter, because it is stored in another static variable. Instead of synchronized methods, a synchronized guard statement is used to lock the ColorRelay class before trying to access the originalImage static variable. (Really, only one instance of ColorRelay should have an image parameter, but this precaution helps the code to deal sensibly with HTML coding errors.) Listing 1.4 shows the init method.
Listing 1.4. ColorRelay.java (part 3).
/**
* Initializes the applet instance. Checks and stores
* parameters and initializes other instance variables.
*/
public void
init() {
String flash = getParameter("flashColor");
if (flash != null) {
try {
flashColor = new Color(parseRGB(flash));
}
catch (NumberFormatException e) {
// Ignore a bad parameter and just go with the default.
}
}
String sleep = getParameter("sleepTime");
if (sleep != null) {
try {
sleepSecs = Integer.parseInt(sleep);
}
catch (NumberFormatException e) {
// Ignore a bad parameter and just go with the default.
}
}
String imageURL = getParameter("image");
if (imageURL != null) {
Class cr = Class.forName("COM.MCP.Samsnet.tjg.ColorRelay");
synchronized (cr) {
if (originalImage == null) {
originalImage = getImage(getDocumentBase(), imageURL);
}
}
}
tracker = new MediaTracker(this);
}
The start method, called when the browser is ready for the applet to begin execution, actually adds the applet to the list. The stop method removes it from the list. As you saw earlier, adding the first applet to the list causes the controller thread to begin execution. The thread simply loops through the list over and over, directing each applet in turn to flash. It's up to the individual applets to flash their color for the appropriate amount of time and return when they are finished. The thread finishes automatically when there are no more applets on the list. Listing 1.5 shows the start and stop methods, along with the run method for the controller thread.
Listing 1.5. ColorRelay.java (part 4).
/**
* Starts the applet running. The ColorRelay hooks up with
* other instances on the same page and begins coordinating
* when this method is called.
*/
public void
start() {
// Ordinarily, we want to display the original image.
image = originalImage;
ColorRelay.addToList(this); // Let's get to work!
}
/**
* Stops the applet. The ColorRelay instance removes itself from the
* group of cooperating applets when this method is called.
*/
public void
stop() {
ColorRelay.removeFromList(this);
}
/**
* Loops through the list of active instances for as long as it is
* non-empty, calling each instance's 'flash' method.
* @see #flash
*/
public void
run () {
ColorRelay curr;
// Continue running through the list until it's empty ...
while (list != null) {
for (curr = list; curr != null; curr = curr.next) {
try {
curr.flash();
}
catch (InterruptedException e) {
}
}
}
}
The rest of the code for ColorRelay doesn't really have much to do with inter-applet communication, so it is omitted from this chapter (although it can be found on the CD accompanying this book). The getAppletInfo and getParameterInfo methods are recommended (but nonessential) parts of the Applet interface-sort of "good citizen" methods. getAppletInfo returns information about the applet, its author, and copyright conditions, whereas getParameterInfo returns information about the applet's HTML parameters and how to use them. The parseRGB method is used to parse an RGB color specification passed in as a parameter. The paint, update, and flash methods handle the graphics operations of the applet. Finally, ColorRelay also makes use of another class, ColorizeFilter, which makes a new image from an original by changing all-white pixels to a specified color.
In most cases, using static variables for communication has advantages over getApplet. Each applet must register itself when it initializes, but that's simple, especially because the class is guaranteed to be available to accept the registration. The class may begin orchestrating the cooperation between applets immediately, as in ColorRelay, or it may need to wait until a particular applet registers. Applets can communicate with each other even when they are not on the same Web page.
In this example, all the applets are the same, but that doesn't have to be the case. The applets could be completely different and still communicate via a common class. The class doesn't even have to be a superclass of the applets-each applet can simply refer to the common class, and the Java virtual machine will detect the dependency and load the class before the first applet is initialized. For example, any number of different applets could communicate through an AppletRendezvous class by means of statements such as this:
// Register my name and type at the rendezvous ...
AppletRendezvous.RegisterApplet("Applet1", "Bouncer");
None of the applets would have to inherit from AppletRendezvous in any way.
In spite of these advantages, however, inter-applet communication using static variables doesn't solve every problem. For one thing, under current security schemes, it's not possible for applets to communicate this way if they were loaded from different sites on the network. Of course, such communication is also prohibited by current applications when using getApplet.
A more serious problem is related to something mentioned as an advantage a couple of paragraphs ago: applets communicating via static variables can communicate across Web pages. When that's what you want, it's very useful, but when you aren't expecting it, it can be disastrous. Unless you explicitly intend for applets to continue to be a part of the team when the user moves on to another page, you need to write your applets carefully so that they use their stop methods to remove themselves from the team. Otherwise, if you try to use related applets together on one page to achieve one effect, and in a different way on another page to produce a different effect, those applets might step all over each other and get very confused if a user were to visit both pages in a single session.
The ColorRelay applet suffers from this problem to a degree. If you use it on one page with one image, and then on another page with a different image, the group of applets on the second page will continue to use the image that was loaded on the first page. With care, it is possible to avoid such confusion. One way is for applets to use the class only for establishing communication, storing references to shared information in instance variables rather than in the class. (The list of applets could stay in the class, because it's primarily a communication mechanism and applets are careful to remove themselves from the list when their stop method is called.) Another way of handling the situation is to use a hash table where the controlling applet on each page could store page-specific information, using itself as a key.
There is one final problem that might apply if you are doing something that requires applets to stay active after the user moves on from the applet's page: trimming. Under certain circumstances (such as when the browser's in-memory cache is full) the browser will forcibly destroy applets. Each browser will probably have a different trimming policy. There's nothing you can do to avoid trimming, but you can take action when it happens by overriding Applet.destroy.
It's possible for applets to learn about each other and communicate using a network server. The server could accept connections from applets and inform them about other applets that were connecting from the same host. This mechanism doesn't offer any real advantages on its own, however. It turns out to be roughly equivalent to communication via static variables. Applets from different sites still can't communicate with each other, at least within Netscape Navigator, because of the restriction that applets can make network connections only to the site from which they were loaded. Furthermore, if two people on a multiuser system such as UNIX are both running Web browsers looking at the same page, it will be difficult, if not impossible, for the applets to determine that they are not even in the same browser. That could be a real mess.
It could also be wonderful! Occasionally you might want applets to communicate with each other between browsers. Several applets already use this technique. One particularly interesting example is Paul Burchard's Chat Touring applet, which provides a fairly typical interactive chat service with a twist-people who are viewing the Chat Touring page and chatting with each other can direct each others' Web browsers. You can type in the URL of a Web page you find interesting and the Chat Touring applet arranges for everyone else's browser to also jump to that page. Occasionally, there are "guided tour" events, where one individual is in control, showing the others a selection of Web pages and guiding discussion about them. The Chat Touring applet can be found at the following address:
http://www.cs.princeton.edu/~burchard/www/interactive/chat/
Using the network for applet communication is primarily useful when the network is already an important part of what you want to accomplish. Communicating between different users is one example; another is a client applet, which interacts with a server to perform expensive calculations or access a database. If you are implementing such an applet and you think that having multiple cooperating applets might make your Web page easier to use, easier to understand, or more exciting, you might piggyback your inter-applet communication on the network, because you'll be using it anyway. On the other hand, if you don't already need to use the network, it's probably not the best choice for inter-applet communication.
One final mechanism deserves mention, because although it's extremely limited in most ways, it does permit some communication that isn't otherwise possible. One applet can learn about other applets by searching through the ThreadGroup hierarchy to find the threads in which the applets are running. Chapter 23, "Pushing the Limits of Java Security," contains an example applet, AppletKiller, which demonstrates how to find other applets in this manner. However, although AppletKiller is pretty good at finding other applets, it's not really concerned with communicating with any of them (except in an extreme sense!), so a discussion of the communication potential of the approach is worthwhile.
Even after you've found an applet's thread, communication isn't easy, because you haven't actually found the applet object itself-just the thread in which it is running. Because applets must inherit from the Applet class, and Java doesn't support multiple inheritance, applets can't actually be instances of Thread; instead, they must implement the Runnable interface and create new Thread objects.
Having found a thread, there are only a few things you can do. You can find out several pieces of information about the thread itself, but that doesn't lead you to the applet. You can interrupt the thread, but that's a pretty poor form of communication. The only real way to establish communication with the applet is to try to stop the thread by throwing an object of some type other than ThreadDeath:
// 'appthread' contains the thread we've found
appthread.stop(new CommunicationHandle(this));
If the applet is prepared to catch such an object, it can use that object to find your applet and establish communication, but you still will have aborted whatever the applet was in the process of doing. Even worse, if the applet isn't prepared to accept the CommunicationHandle, you will have inadvertently killed the entity you were trying to talk to, just like they occasionally do on Star Trek. As if that weren't bad enough, if the other applet was loaded from another site, applet security mechanisms might prevent the other applet from recognizing the object you pass in the stop method.
Because of all of these pitfalls, locating other applets via the ThreadGroup hierarchy is really more useful for control purposes than for cooperation. It's possible for one applet to keep watch over others, enabling a user to investigate what applets are currently active and kill those that are misbehaving.
Applets really shouldn't be able to control other threads, and their current ability to do so probably represents a security bug. Chapter 23 discusses this issue in some detail. If you build applets that depend on this capability to do their job, they may cease to work as applications tighten thread security. At the same time, though, mechanisms for more flexible security will be appearing, permitting you to grant special privileges to applets loaded from trusted sources (see Chapter 22, "Authentication, Encryption, and Trusted Applets," for more details). An applet that gives the user control over other, ill-behaving applets might remain a useful tool.
Inter-applet communication can be extremely useful. It can help you produce improved visual effects which enhance the content of a Web site, and it can help you build useful applets that are easy to understand. Unfortunately, there are also a lot of traps for the unwary. There are several ways of establishing communication between different applets, and each mechanism has its problems.
For most purposes, you should use AppletContext.getApplet or static variables in a shared class to establish communication. They work fairly well in most situations, and their limitations are not too serious. Additionally, getApplet should work more consistently in the future, as application implementors hammer out the details of how it really should work.
In certain special cases, applets can communicate using the network or locate each other by searching the thread hierarchy. These mechanisms have serious disadvantages, but they also offer unique capabilities, which might be essential for the function you have in mind.