Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Channels for Inter-Applet Communication


Sep98: Channels for Inter-Applet Communication

Steve is an independent contractor specializing in C++/Java/UNIX development. He can be contacted at [email protected]. John is Interactive Technology Manager at Dave Clark Design Associates and a principal lecturer at the Auckland Institute of Technology. He can be contacted at [email protected].


Sidebar: Turns and About-Turns
Sidebar: The Monostate Pattern

Visitors enjoy a much richer experience when visiting your web site if Java applets communicate with each other and exchange data directly. There are a number of ways in which you can set up the channels of communication between applets. The Java Abstract Windowing Toolkit (AWT) offers one means of doing this using the AppletContext class. In his article "Java and Inter-Applet Communication" (DDJ, October 1997), Andrew Meckler presented another method. Yet another method is the technique we'll discuss in this article -- Java's class variables (static fields).

The Java Language Specification (JLS) requires that there exist "exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created." This is not sufficiently precise. What if other class instances exist in another program executing on the same machine? The JLS is silent. We are left to assume that, as in the case for C++, "there is only one copy...in a program" (The Annotated C++ Reference Manual, by Bjarne Stroustrup and Margaret Ellis, Addison-Wesley, 1990). Though this criticism of the JLS may seem like nit-picking, the imprecision takes on a crucial importance when we examine an applet's run-time status.

If an applet runs as a distinct program, its data will be impervious to the actions of other executing applets. But, if the applet's thread of execution is one of a number spawned within a larger process, you can expect its class variables to be shared with the other instantiations of its class. To determine the exact status given to an applet's thread, you need to look at how the Java Virtual Machine (JVM) deals with applets.

The Java Virtual Machine and Applets

The status of applets being executed in a browser is not nearly so straightforward as the status of a stand-alone Java application, for which the JVM dedicates a main thread to execute its main() function. In a browser environment, a main thread is still created by the JVM, but instead of searching for a main() function, it executes the browser's setup instructions and enters a dormant state where it waits to be notified by the browser that a new applet should be initialized. When this notification arrives (on the browser encountering an APPLET tag in an HTML page), a newly created thread is dispatched to execute the applet's constructor. This new thread inherits the state of its JVM progenitor and its access to the static fields therein. Each applet instantiation thus has the same status it would have if that applet's class were multiply instantiated, each in its own thread, within the context of a Java application.

The fact that applets of the same class share the state of their static fields opens up a channel of inter-applet communication (IAC). However, you need to keep in mind that this channel is an artifact of the browser vendors. There is nothing in the JLS dictating that a browser should treat applets in this way. Indeed, at least one browser manufacturer has had second (and third!) thoughts about this outcome (see the accompanying text box entitled "Turns and About-Turns").

How Threads can Communicate through Shared Objects

Inter-thread communication in Java, whether between applet peers or those a program itself creates, is based on "monitors" (see "Monitors: An Operating System Structuring Concept," by C. Hoare, Communications of the ACM, October 1974). The central idea of monitor-based communications is that a thread may acquire exclusive access to an object shared among threads and send signals to it that other threads currently observing the same object may detect. The crucial requirement imposed by this scheme is that two threads that wish to communicate with each other must have access to the same instantiation of an object. This, in turn, implies that the mediating object exists in the address space of both applets.

Executing all applets as separate threads of the same program solves this problem (though it introduces some others) -- any thread in a Java program may access the public static fields of any class.

Precisely how you use static fields depends on whether the communicating applets are instantiated from the same or different classes. In the former case, a static field of that class provides the best encapsulation. In the latter case, you can make good use of the Monostate pattern (see the accompanying text box entitled "The Monostate Pattern") to create a communicator class that can be separately instantiated by the participating applets. Since all fields in a Monostate class are declared static, a change in the state of any instance of the class is propagated directly to all other instances.

We'll demonstrate the advantages of using static fields for IAC by first exposing the shortcomings of the conventional AppletContext interface approach in the context of a web page for the Homely Hotels chain.

Limitations

The OriginChoice applet (available electronically; see "Resource Center," page 3) defines the OriginChoice class, which displays a single drop-down list box from which visitors select their country of origin. The highlighted "room rates" text items are instances of the PopupRates applet class. To make the visitor's choice available to the PopupRates applets, OriginChoice defines a getOrigin() method that returns the value of a private (nonstatic) member.

The PopupRates applet reads the current origin by obtaining a reference to its own applet context through a call to getAppletContext() and invoking the getApplet() method of the applet context to get a reference to the OriginChoice applet object. To isolate that particular applet out of all the ones present in the context, PopupRates supplies the name of the OriginChoice applet assigned to it in the HTML document with the NAME attribute. The getOrigin() method of the located object returns the current origin selection.

There are a number of shortcomings in this approach. One severe disadvantage is immediately evident. As Andrew Meckler notes, if the applets are on different web pages, you cannot use an applet context to set up a reliable channel of communication. Another point against it is its behavior when an OriginChoice applet is not present in the applet context: Let's say that the page containing the OriginChoice applet has not yet been viewed. In this case, the PopupRates must proceed with a default value for the origin. This is probably the best tack to take, but it represents poor encapsulation -- it is surely the role of the OriginChoice class to decide what default value is best used, but a task it can hardly perform if it does not exist!

Little can be done to alleviate the second problem. However, Meckler's CommManager class solves the first by essentially providing an applet context that embraces all applets from all currently displayed web wages. It achieves this by requiring each applet, upon startup, to insert a reference to itself into a static hash table. The applet can then be located in this collection of applets irrespective of which applet context possesses it. If you are reconciled to relying on static fields -- an incompletely defined area of the language -- to obtain desired behavior, then why not completely abandon yourself to the mercy of the browser manufacturers, switching fully to the static fields approach to IAC and then gain the benefit of resolving both problems with a single mechanism?

Homely Hotels with Static Fields

Switching to the static fields approach is done with the aid of a monostate class that holds the current origin; see Listing One. Every instantiation of the Origin class has an identical state. (In this regard, objects implementing the Monostate pattern are equivalent to those of the Singleton pattern. Their main point of distinctiveness is that they are locally instantiated, and, unlike Singleton objects, may be subclassed while retaining their singular nature.) The getOrigin() method is removed from the OriginChoice class and takes up residence in the Origin class. To select a new origin, the OriginChoice applet invokes the set() method of its own Origin member, immediately updating the state of every Origin object in every applet. Any PopupRates object may interrogate the current selection by merely querying its own Origin object via the get() method.

The Monostate nature of these instances ensures that when a country is selected in the OriginChoice applet, the resulting change in the state of its Origin instance variable is reflected in each of the PopupRates applets. When any of these applets is requested to display the room rates, the form that these rates take is determined by the current state of the applet's Origin member.

If you were concerned only with communication between the PopupRates applets, it would suffice to give their class a private static int field whose value represented the visitor's country. Indeed, you could also make this field visible to the OriginChoice applet to allow the applets of the two classes to communicate, at the expense of compromising the encapsulation of the PopupRates class. The virtue of using Origin instances as a communication conduit is that, being private members, encapsulation is preserved while at the same time the coupling between the communicating classes is commendably decreased.

Consider what now occurs when a default value must be supplied for the origin if no OriginChoice applet has yet been displayed. The Origin class's get() method is invoked, returning the initial value of its private member. The default value is the one defined in the Origin class and is now suitably encapsulated in the class considered authoritative on questions of origin.

An Additional Benefit of Static Fields

There is an additional advantage in using the static fields approach that is not immediately obvious in the Homely Hotels application but which may be imperative in other applications. In that example of a Producer-Consumer relationship between applet components there is only ever one producer -- a single instance of the OriginChoice applet. Listing Two defines a simple paintbrush applet that makes use of a separate applet to display a palette of brush colors. The Palette applet assumes the role of an information producer, and the Scribble applet that of an information consumer.

The Scribble and Palette applets (available electronically) may exist on different web pages (Figure 2). What color should the Scribble applet use if more than one Palette applet is displayed with different selected colors? Rather than arbitrarily selecting one, it would be better if the situation could be avoided by making a color selection in one palette affect all currently displayed Palette objects.

The Palette class achieves this by creating a background thread to watch for changes to the (static) palette color member and requesting a repaint when it observes that a new color has been selected. The alternative with applet contexts is grossly inferior in this situation. With applet contexts, when a color selection is made in one Palette applet, that thread would be required to locate all the other palettes and notify them separately of the change. This may not seem too burdensome, but synchronization issues (the consequences of the receiving palettes being currently active) may cause the sending palette to block waiting for the receiving palette's resources to become available. With the static fields approach, the message is asynchronous rather than synchronous, and the receiving palette can respond to the change when it next has the opportunity.

All cases of interthread communication, not just that between applet threads, necessarily rely on a shared object of some kind. Therefore, you must give due attention to issues such as lock contention and deadlock. The Homely Hotels example was able to avoid synchronization handling because the state variable being shared was a single integer, for which all reads and writes are performed atomically. If the value member of the Origin class were defined as a long (a type that does not enjoy atomic reading and writing), then it would also need to be defined as volatile or, alternatively, its accessor and mutator functions would need to be defined as synchronized.

Conclusion

An issue we've not addressed is the loss of the customary semantics of static fields within an applet. If you wish to give to an applet static fields which will be visible only within threads, which it explicitly creates itself, Java leaves you empty-handed; on this question too, the JLS is mute.

Unhappily, there is no definitive specification of the run-time environment of applets that would legitimize our use of class variables and Monostate objects as an IAC channel. The door browser manufacturers use for this purpose is an unmarked back door rather than a named and numbered front door.

The source code and working examples in this article are available from DDJ, http://www.geocities.com/SiliconValley/9592/interapps.html, and http://www.dcda.co.nz/jmc/interapps.html.

DDJ

Listing One

class Origin {    public void set(int value) { this.value = value; }
    public int get() { return value; }


</p>
    public final static int US = 0;
    public final static int UK = 1;
    public final static int NZ = 2;


</p>
    private static int value = US;
}

Back to Article

Listing Two

public class Scribble extends Applet {    public Scribble() {}
    public void init() {
        setBackground(Color.white);
    }
    public void paint(Graphics g) {
        for (int i = 0; i < lines.size(); ++i)
            drawLine(g, (Line) lines.elementAt(i));
    }
    public boolean mouseDown(Event evt, int x, int y) {
        start = new Point(x, y);
        return true;
    }
    public boolean mouseDrag(Event evt, int x, int y) {
        Line line = new Line(start.x, 
                          start.y, x, y, Palette.getPaletteColor());
        lines.addElement(line);
        drawLine(getGraphics(), line);
        start = new Point(x, y);
        return true;
    }
    private void drawLine(Graphics g, Line line) {
        g.setColor(line.color);
        for (int dx = -1; dx <= 1; ++dx)
            for (int dy = -1; dy <= 1; ++dy)
                g.drawLine(line.x1 + dx, line.y1 + dy, 
                                    line.x2 + dx, line.y2 + dy);
    }
    private Vector lines = new Vector();
    private Point start;
}
class Line extends Rectangle {
    Line(int x1, int y1, int x2, int y2, Color color) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.color = color;
    }
    int x1, y1, x2, y2;
    Color color;
}
public class Palette extends Applet implements Runnable {
    public Palette() {}
    public void init() {
        size = size();
    }
    public void start() {
        if (selector == null)
            (selector = new Thread(this)).start();
    }
    public void stop() {
        if (selector != null) {
            selector.stop();
            selector = null;
        }
    }
    public void paint(Graphics g) {
        synchronized (colorLock) {
            for (int sqx = 0; sqx < NB_COLUMNS; ++sqx)
                for (int sqy = 0; sqy < NB_ROWS; ++sqy)
                    drawColorSquare(g, sqy * NB_COLUMNS + sqx);
            updateSelection();
        }
    }
    public boolean mouseDown(Event evt, int x, int y) {
        int sqx = x * NB_COLUMNS / size.width;
        int sqy = y * NB_ROWS / size.height;
        if (sqx >= 0 && sqx < NB_COLUMNS && sqy >= 0 && sqy < NB_ROWS)
            synchronized (colorLock) {
                color = colors[colorIndex = sqy * NB_COLUMNS + sqx];
                try {
                    colorLock.notifyAll();
                } catch (InterruptedException e) {
                }
            }
        return true;
    }
    public void run() {
        synchronized (colorLock) {
            for (;;) {
                updateSelection();
                try {
                    colorLock.wait();
                } catch (InterruptedException e) {
                }
            }
        }
    }
    public static Color getPaletteColor() {return color;}
    private void updateSelection() {
      Graphics g=getGraphics();
      drawColorSquare(g,selectedColor);            // remove old selection
      drawColorSquare(g,selectedColor=colorIndex); // highlight new selection
      g.dispose();
    }
    private void drawColorSquare(Graphics g, int index) {
        // must synchronize on colorLock before calling this function
        int sqx = index % NB_COLUMNS;
        int sqy = index / NB_COLUMNS;
        int x1 = sqx * size.width / NB_COLUMNS;
        int x2 = (sqx + 1) * size.width / NB_COLUMNS;
        int y1 = sqy * size.height / NB_ROWS;
        int y2 = (sqy + 1) * size.height / NB_ROWS;


</p>
        g.setColor(colors[index]);
        g.fillRect(sqx * size.width / NB_COLUMNS, sqy * size.height / 
                                             NB_ROWS, x2 - x1, y2 - y1);
        g.setColor(index == colorIndex ? Color.white : Color.black);
        g.drawRect(x1, y1, x2 - x1 - 1, y2 - y1 - 1);
    }
    private Dimension size;
    private Thread selector;
    private int selectedColor;
    private static final int NB_COLUMNS = 18;
    private static final int NB_ROWS = 12;
    private static Color[] colors = new Color[216]; // const
    private static Object colorLock = new Object();
    private static Color color;
    private static int colorIndex;  // index of color in colors array


</p>
    static {
        for (int i = 0; i < 216; ++i) {
            int red = i / 36 * 0x33;
            int green = i / 6 % 6 * 0x33;
            int blue = i % 6 * 0x33;
            colors[i] = new Color(red, green, blue);
        }
        color = colors[colorIndex = 0];
    }
}


</p>

Back to Article


Copyright © 1998, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.