One of the major advantages of the Java language is its power and flexibility. Java is a full-featured programming language with all the constructs one needs to develop object-oriented applications. However, as you have already seen in chapter 11, "A Java Tutorial," Java is not as directly connected with the environment of its Web page as JavaScript. Java cannot really access HTML elements on a Web page in a direct manner. As compensation for this deficiency, Java provides some extremely powerful tools for manipulating images and URLs. Java also has a set of components, known as the Advanced Windowing Toolkit (AWT), which enable Java applets to create pushbuttons, text entry fields, and other HTML-like entities.
The term Java encompasses many things. In chapter 11, we focused on gaining some initial understanding of Java as a programming language. In the process, you encountered some
old familiar methods, such as parseInt() and charAt(), and also some new ones, such as paint(). This points to the fact that Java is more than a language. Java is also a set of methods, organized into a collection known as the Java Class Hierarchy, which enables us to do complex tasks. Much of the expressiveness of Java only becomes clear when we learn more about some of the components of the Java Class Hierarchy and what they can do for us.
This chapter explores the Java Class Hierarchy with particular emphasis on image and URL manipulation. It presents the basic concepts necessary to explore Java further, as well as enabling you to write more complex and interesting Java applets.
In chapter 11, "A Java Tutorial," you were first exposed to the concept of inheritance in Java. In particular, in the applet described in the section "An Accounting Applet in Java," you saw three Java classes working together: the Account class, the Acex class, which drove the applet itself, and, implicitly, the Java class java.applet.Applet. We were introduced to the special keyword extends and we saw that Acex was said to extend the built-in class java.applet.Applet. This idea of having one class extend another, also known as subclassing, is critical to understanding the Java Class Hierarchy.
In chapter 11, we built our Account class from the ground up and gradually refined the methods to perform a set of simple, but useful, operations. We could have continued this process ad infinitum, adding more and more functions for more and more specialized situations. This would make the Account class cover a larger number of situations, but it would also lead to dramatic overkill in some cases. It would be nice to have the capability to handle escrow accounts, foreign currency transactions, and the like, but in many situations, you would not use these extra capabilities.
This leads to the notion that perhaps we do not want to extend
a class by adding more and more to it, but rather by creating
specialized versions of that class. The specialized versions would
have all the capabilities of the generalized class, but would
also have their own unique features. The specialized classes,
such as EscrowAccount and InternationalAccount,
have all the methods and instance variables of Account,
but also have their own methods, which Account does not
have. The specialized classes inherit the attributes of
their parent. The specialized classes are subclasses of
their parent class, which is known as the superclass.
NOTE |
There is no multiple inheritance in Java. Every Java class has exactly one parent class. |
Naturally, this simple idea of inheritance acquires some twists and turns when it is actually implemented. The first such variation is the idea of having a subclass override a method in the superclass. The Acex applet discussed at the end of chapter 11 overrides the paint method of its java.applet.Applet superclass. It does not override the inherited method repaint-it just uses it as is.
You can imagine that the international version of Acex would keep the withdraw and balance methods the same and would add convert and transfer methods (to convert between different currencies and to transfer money). It might also override the deposit method so that deposits could be made in foreign as well as local currency. A subclass not only extends its superclass, it also tends to modify its behavior for special situations.
Java has a special keyword, super, that is used to refer to the superclass of a class. Superclass instance variables can be accessed as super.varname, and superclass methods can be invoked as super.methodname(). This keyword is particularly useful if you want the subclass to use its own method named NAME and also use its parent's method, also named NAME.
For example, our internationalized version of the deposit method might look something like listing 12.1. This version of deposit simply converts the deposit amount, in any arbitrary currency, into the local equivalent (line 3) and then calls the deposit method in the superclass (Acex) to perform the deposit. This avoids the tedious approach of copying all the deposit code in any subclass that over-rides it.
Listing 12.1 A Class Method Calls Its Superclass
Method
// Assume that "currency" is a variable specifying the type of currency, // and that convert is a method that converts between currencies // this is the subclass deposit method void deposit(int amount, int which, int currency) { // 1; int localamount; localamount = convert(amount, currency); // 3; convert to local super.deposit(localamount, which); // 4; invoke superclass method }
What happens to instance variables of a class when a subclass is derived from it? As one might imagine, public instance variables remain public. Interestingly enough, private instance variables (and private methods) are completely private-they are unknown in the subclass just as they are unknown outside the class. This means that no subclass can reference private instance variables or make use of private methods of its superclass. Java also has a third category, known as protected variables and methods, which are known to the class and to all its subclasses, but remain invisible outside the class. Figure 12.1 illustrates the relationship between the various types of class members and their subclass counterparts.
Figure 12.1 : Subclassing can be used in Java to create specialized classes.
The Java Class Hierarchy is the collection of all the classes that are provided as a standard part of Java. These classes are organized in a class hierarchy, as previously described, with a series of very general classes-such as the ubiquitous class known as Object-at the top of this hierarchy. This might lead you to guess that the class java.applet.Applet, which is the superclass of all applets, is a subclass of java.applet, which is in turn a subclass of an all encompassing java class. This is an excellent guess, but it is incorrect.
Java actually has two kinds of organization for its classes. It has a strict class hierarchy, which describes all the children of each class. It also has a more horizontal organizational structure, known as the Java package system. Packages are used to group together similar, but not necessarily directly related, classes into a set of groups. These groups are the Java packages. Packages can be distinguished notationally from classes because they all begin with a lowercase letter, while classes always start with an uppercase letter. Thus Applet is a class in the java.applet package, which is a part of the java package. As a class, Applet is derived as follows:
Object -> Component -> Container -> Panel -> Applet
An applet is therefore actually a specialized form of the graphics class Panel, which is derived from two other graphics classes, Container and Component, and ultimately from Object. This is an excellent example of the matrix organization of classes and packages. Applet is a member of the java.applet package; Panel, Container, and Component are members of the java.awt (Advanced Windowing Toolkit) package; and Object is the member of the java.lang package.
The top of the Java package hierarchy is the java package.
There are other top level hierarchies, such as the sun
hierarchy, which are platform and/or operating system dependent.
The java hierarchy, however, is always guaranteed to
be present. It contains the following packages:
|
|
|
|
|
|
Theutilutil java.lang Package The java.lang package is one of the most impioortant andio fundamental of the java packages. It defines the basic object types that correspond to elements of the language. It also includes several very interesting pieces of machinery that are used throughout Java programming, including the critical concept of a thread, which will be reviewed shortly.
The data type classes contained within java.lang include Boolean, Character, and String, as well as the numerical types Integer, Long, Float, and Double. These latter four classes are actually subclasses of a generic Number class. As one might expect, each of the numerical types defines conversion methods. You have already seen one of these, namely the parseInt method of the Integer class, which is used to convert strings to integers.
The java.lang package also contains a class known as Math, which is very similar to the JavaScript object of the same name. Math provides an expanded set of mathematical operations. The same can be said for the String class, which is a full- fledged class (object) in Java-unlike its implicit counterpart in JavaScript. Java also provides a second string class within the java.lang package known as StringBuffer. This is used for extensible strings. Whenever you concatenate strings using the plus sign (+) operator, you are actually using a StringBuffer. More precisely, whenever the Java compiler sees an expression that involves merging two strings, it rewrites that expression to use a StringBuffer behind the scenes.
Finally, the java.lang package contains two critical
classes with enormous utility: System and Thread.
The System class provides system-level functionality
with a platform-independent interface. The way in which it is
actually implemented, of course, depends heavily on the actual
platform. You have already seen an example of the System
class in the print statement, System.out.println("message"),
which sends the string "message," with a subsequent
carriage return, to the standard output. Where this output
actually goes is, of course, platform dependent. Threads
are the subject of the next section and are used in the "Image
Viewer Applet" section at the end of this chapter.
NOTE |
In Netscape Navigator, the output generated by System.out.println can be seen by activating the Java Console under the Options menu. |
Using Java Threads It is often very useful to do several things at once. Not only does this get more done, it brings everything to completion earlier. Of course, in this aspect, most humans are like most computers. It is not really possible to do more than one meaningful thing at a time, such as reading two books at once, but it is often highly desirable (particularly for one's image) to make it appear that way. This is the advantage of modern multitasking. Each user process gets its own set of tiny slices of a single CPU, and the illusion of simultaneous processing is maintained. Most modern operating systems enable you to seem to perform several tasks, such as editing while printing.
In this hustle and bustle world of ours, there is never enough time to do all the things we want to do without it looking like we are ignoring someone or something. The same can be said for the programs we write. In the days of plain old DOS, for instance, people were used to waiting for the program to finish printing or repaginating before they could do something else. Microsoft brought Windows to the DOS world, and suddenly you could run more than one program at a time, thus enabling you to do more than one thing at a time. You were, however, the computer was not.
A CPU (Central Processing Unit) really only executes one instruction at a time, and each instruction belongs to a particular program. But, and here's where it gets interesting, the CPU does not care where the instruction comes from; it just executes it. Essentially what operating systems for UNIX, Windows, and Macintosh computers do is cleverly pass instructions to the CPU from the loaded programs so that it looks like they are all running at the same time, but in fact each of them is getting its slice of the CPU's time in a sort of round-robin fashion. This enables the programs to print or repaginate while you're off playing Solitaire or something.
There are often cases in which it is highly desirable to be able to perform many tasks within a single program. This is particularly true in graphics programs. In attempting to display multiple images, it is advantageous to be working on image 5, while image 4 is being displayed, for example. Java provides such a capability as part of its java.lang package through the medium of the Thread class.
A Java thread is very similar to an ordinary thread in a garment. It has a definite starting point, a definite endpoint, and can weave through the garment in tandem with other threads. A complete description of Java threads is well beyond the scope of this chapter. However, we can examine the general structure of a threaded Java applet. This structure is used in the Image Display applet to realize precisely the goal described previously: interleaving graphic operations and other operations. The template for a multithreaded Java applet is shown in listing 12.2.
Listing 12.2 The Structure of a Runnable Java Applet
public class MTApplet extends java.applet.Applet implements Runnable { Thread mythread = null; // the thread we will create public void init() { // init method, as before ... // initialization stuff goes here } public void start() { // start method, creates thread if ( mythread == null ) { mythread = new Thread(); mythread.start(); } } public void stop() { // stop method, stops thread if ( mythread != null ) { mythread.stop(); mythread = null; } } public void paint( Graphics g ) { // local paint method ... // custom drawing goes here } public void run() { // the work method of the thread ... // the main body of the thread } }
This template has several familiar features as well as some new wrinkles. The first thing to notice is that the class declaration for this MTApplet class not only extends java.applet.Applet, as it must, but it also implements Runnable. Runnable is a new type of Java element: a Java interface. An interface, like a superclass, expresses a set of methods. A class, such as MTApplet, which implements this interface, must also implement these methods. In particular, it must implement a run method. The purpose of the run method will become clear in a moment.
The MTApplet class has the very familiar init() method, which is used to do whatever initialization is required. This usually involves parsing user parameters accessed via the getParameter() method. If images are to be manipulated, the init() method is also a good place to begin loading those images. The paint() method is also much as before: it is used to perform our applet-specific drawing operations. These operations are now done in parallel, however, using threads.
The start() and stop() methods shown in listing 12.2 are not templates or placeholders; they are shown in their entirety. The start method examines the instance variable mythread to see if it is null (its initial value). If it is, then the start method creates a new Thread instance by invoking the new operator and sets mythread to be that instance. The effect of creating a new thread is that there is now one extra task that can be run. This new thread is not yet running, however. The final statement in the start method launches this new thread by saying mythread.start(). This calls the start method of the new thread. The new thread now runs as an independent entity within the applet.
The stop method is the mirror image of the start method. It also examines the mythread instance variable. If it is not null, then that thread is halted by calling its stop method. Cleanup is then performed by setting the mythread variable back to null. The interplay between start and stop is such that at most, one new thread will be created. If start finds that mythread is not null, it will do nothing. Also, stop ensures that the new thread will never be stopped twice. None of this yet explains how the new thread accomplishes anything, however.
The answer to this mystery is provided by the new run() method. When a class implements the Runnable interface and a new thread is created and set running by that class, then its run() method will be entered. In fact, every applet is already a thread, known as the main thread. Unless a new thread is created by instantiating the Thread class, the main thread is the only thread, so there is effectively no parallelism.
Once the second thread is activated and the run method
entered, the new thread can do one set of operations while the
main thread is doing something else. This is the key
idea behind parallelism in Java. If the run method performs
some graphical operations and ends up triggering paint(),
the actual drawing is performed in the main thread, while
the computations leading up to it are performed in the second
thread.
CAUTION |
The actual implementation of Java threads is platform dependent at this time. This is because threads require some cooperation from the underlying operating system, and different operating systems cooperate in different ways. A thread-based applet that works perfectly under Solaris may fail on Windows NT, and vice versa. Applets using threads should be thoroughly tested on all major platform types (UNIX, Windows, and Macintosh). |
The java.net Package The java.net package contains the basic classes and methods that are used for network communications. This package contains classes representing network connections (sockets), network addresses, and, most significantly, URLs. This might sound like an extremely rich source for interesting Java programming ideas, but the Java security model limits what you can do with this package quite severely. It is worthwhile to review these limitations because they have a significant effect on what is possible and what is not.
Every Java applet is activated within the context of a Web page via that page's APPLET tag. This Web page in turn was obtained from some URL and is therefore associated with a particular Web server. We will refer to the Web page that activated the applet as that applet's document and the server from which that page was obtained as the applet's server.
The first restriction on network access within Java is that it is prohibited from opening a network connection to any host other than the applet's server. This means that it is not even possible to make a network connection to the user's own host! The second restriction is that a Java applet can only access documents within the directory hierarchy rooted at the applet's document BASE. These two restrictions combined might seem quite grim because the set of documents accessible within Java is rendered very small.
Fortunately, there are no restrictions on documents that Java can ask its browser to open. This concept is one of the subtleties of Java. Java does not actually implement graphics, network connections, or anything else that impacts the external environment. It has a series of methods where it can ask its browser to do these things for it. When you create a button or open a URL in Java, it is actually the browser that is doing these things for you.
Having said all this, there is one very important class in the java.net package that you can (and will) use quite effectively: the URL class. As the name implies, this class is used to construct an abstract representation of a URL. This class has several different constructions, as follows:
URL(String)
URL(URL, String)
URL(String, String, String)
URL(String, String, int, String)
The first form takes a String, such as the literal http://home.netscape.com, and attempts to construct a URL instance from it. The second form is used to concatenate a String representing a relative pathname onto an existing URL. This form can be used to descend from the applet's document BASE to an HTML file within its tree.
The third and fourth forms are used to build a URL from its component parts. The third form takes a protocol name, such as http, a hostname, such as home.netscape.com, and a filename, such as index.html, and produces a URL from that combination. The fourth form enables you to also explicitly set the port number for those rare cases in which the protocol is not being accessed on its default port. (http is occasionally received on port 1080 or 8080 rather than its default 80, for example.)
When we review our two major Java applets later in this chapter, you will see the first two forms of the URL class constructor, and also how one politely asks one's browser to open a "foreign" URL. The discussion on the java.applet package also shows how to obtain the URL that corresponds to the applet's document BASE.
The Advanced Windowing Toolkit We have already observed that Java cannot interact directly with HTML elements, unlike JavaScript. There are no HTML FORM components within the Java Class Hierarchy. This means that Java programmers must construct their own buttons, text entry fields, and the like if they want such items to be part of their applets. The Advanced Windowing Toolkit (AWT) is Java's set of capabilities for doing this. It is contained within the package.
The classes in the AWT can be subdivided into three categories: display items (such as Button), layouts (such as FlowLayout), and overall graphics items (such as Color and Font). The first category, display items, is the largest and includes an extensive set of elements, including the following:
As you can see from this enumeration, many familiar HTML elements are also present in the AWT. As in HTML, it is quite simple to glue together a set of graphical items in a page, but it is somewhat more difficult to make the presentation attractive and crisp. HTML has a number of markup styles and directives that can be used to control the visual format of various elements, including tables and forms.
The means to control where elements are placed, how they are aligned
with one another, and how they are sized and spaced is always
an issue in graphics programming. This applies to all windowing
systems. Java is no exception. The Java AWT has chosen an approach
with several different, quite distinct layout styles. Within each
style, the display elements that you create, such as Buttons
and TextAreas, are placed according to a well-defined
system. However, it can still take time to get things looking
just the way you want, and if all else fails, you can still programmatically
position objects at specific coordinates.
TIP |
The default Java layout is FlowLayout with CENTER justification. Use this until you become more comfortable with the AWT. |
At present, there are five Java layout styles. Each has its own peculiarities, and you will almost certainly find yourself using a combination of styles once you acquire some skill with the AWT. The Java layout classes are:
The BorderLayout approach is based on the idea of placing elements at one of the four cardinal points-North, South, East, or West-or in the Center. It is often ideal for arranging items in case you would like two or three arranged in a vertical (North, Center, South) or horizontal (West, Center, East) stacking order. BorderLayout is also used with Panels for hierarchical organization of items. If you would like a top row of Buttons and perhaps a Label below, you would create two Panels, place them at the North and South locations in a BorderLayout, and then add the Buttons to the northern Panel, and a Label to the southern Panel. Listing 12.3 shows a code fragment that does just this.
Listing 12.3 An Example of Hierarchical Layout in
Java
BorderLayout bl; Button but[]; Panel nopa, sopa; Label la; bl = new BorderLayout(); // 5; create a new BorderLayout instance setLayout(bl); // 6; make it the default layout nopa = new Panel(); // 7; create two new panels sopa = new Panel(); add("North", nopa); // 9; put nopa at the North edge add("South", sopa); // 10; add sopa at the South edge but = new Button[4]; // 11; allocate space for 4 buttons but[0] = new Button("Back"); // 12; create the buttons with various labels but[1] = new Button("Forward"); but[2] = new Button("Home"); but[3] = new Button("Done"); for(int i = 0; i < 4; i++) { // 16; add the buttons to the North panel nopa.add(but[i]); // 17; it will default to a FlowLayout la = new Label("Southern Label"); // 18; create new label sopa.add(la);// 19; add to south Panel }
This example begins by allocating a new instance of the BorderLayout class (line 5) and then calling the setLayout method to make this the current layout. Remember that a Java applet is actually a subclass of a Panel, so that the bare call to setLayout on line 6 applies to the Panel containing the entire applet. The next two statements create Panel instances. Note that one can create instances of graphical items all day long, but they are not displayed until they are added to the applet.
The North and South Panels are added in lines 9 and 10
using the add method. The add method is overridden
in all the layout classes, which means that it has its own distinct
syntax for each one. In the case of a BorderLayout, the
first argument to add must be one of the five permissible
directions. We use North and South to split the applet vertically.
The next five lines create four Buttons with some text
to name them. Lines 16 and 17 then add those buttons to the North
Panel. This is accomplished by explicitly invoking the add
method of nopa, the North Panel instance. If
we had mistakenly just used add(but[i]) on line 17, this
would have attempted to add these buttons to the entire applet's
panel. Lines 18 and 19 create and add a Label to the
South Panel in a similar way.
NOTE |
At the moment, button labels must be text. It is not currently possible to put an image inside a button using the Button class. A subclass of the Button class would have to be written to do this. |
The FlowLayout class implements an approach in which elements are added incrementally across one or more rows. Elements can be justified within a given row using LEFT, CENTER (the default), or RIGHT justification. If an element does not fit on a given row, the layout wraps around to the beginning of the next row. FlowLayout is often used for rows of buttons or other components of similar size and shape. As mentioned earlier, FlowLayout is the default layout for any newly created graphical container (such as a Frame or Panel).
The other three layout types are more specialized. CardLayout is used to create slide-show-like presentations. Elements of a CardLayout are presented sequentially rather than displayed simultaneously on the screen. GridLayout lives up to its name. It enables you to position elements based on their row and column location. It is used by first specifying the number of rows and columns to be allocated, and then placing individual elements in their desired (row,column) location. GridBagLayout is a much more powerful version of GridLayout. It is also regrettably complex because it is necessary to first construct a description of the layout, using the subsidiary class GridBagConstraints, and then actually build the layout on top of that.
The final set of classes in the immense java.awt package is the classes that correspond to general graphical constructs rather than things that are actually drawn. We have already seen three examples of these classes in our tiny applets from chapter 11, "A Java Tutorial": the Color, Dimension, and Graphics classes. The Color class is usually used by invoking its static instance variables that name the primary colors (such as Color.Red), although it can also be used to construct arbitrary color values directly from red, green, and blue levels. The Dimension class is used to hold information about the size of a component. The Graphics class captures the entire graphical state of an applet. Recall that the method signature for the applet paint() method is public void paint( Graphics g ).
Within paint(), you can call a set of methods too numerous to mention to draw strings, rectangles, and other common primitive graphics operations. Some of the other important classes in this general graphics category are the following:
The Event class is extremely important because it enables
us to respond to user events, such as a button being pushed inside
our applet. The Applet class has another method, known
as action(), that is called whenever user interaction
takes place. Its method signature is public Boolean action(
Event ev, Object arg ). It is called whenever the Object
arg (a Button, for example) is pushed and generates
the Event ev. If you override the default action
method, you can control what happens when events occur, just as
in JavaScript.
CAUTION |
Java events and JavaScript events are not directly related. At present, Java cannot respond to events outside its applet. It is also not possible to install a JavaScript event handler for Events inside a Java applet. |
The Font class is used to manipulate the text appearance of any item that contains text. It can be used to load a particular font by name (such as TimesRoman or Helvetica), to set the font style (such as PLAIN, BOLD, or ITALICS), and also to set the font size. The oddly named MediaTracker class is Java's answer to the patient projectionist. It is almost always used to track the progress of images being progressively loaded over the network. You will see examples of all three of these AWT classes below.
The java.applet Package The java.applet package is quite small and has just one interesting class, Applet, with a small number of interesting methods. You have already seen the getParameter() method, which accepts a String argument giving the NAME of a PARAM, and returns the VALUE of the PARAM (or null if there is no matching name). The other three Applet methods that you will use most frequently are the following:
URL getDocumentBase();
URL getCodeBase();
AppletContext getAppletContext();
You can probably guess that the first of these methods returns
a URL instance representing the value of the BASE attribute
of the applet's document. It is the top of the document directory
tree that the applet can access on the server host. The second
of these methods is similar: it returns the URL representing the
value of the CODEBASE attribute given in the APPLET
tag, if any. This is used when all the Java class binaries are
kept in a different server directory than the HTML files. That
directory would be named in the CODEBASE attribute.
TIP |
The URLs returned by getDocumentBase() and getCodeBase() are always valid for use in Java applets as long as they are not null. |
The getAppletContext method is used to talk directly to the browser. The applet conatext really refers to the browser environment in which the applet is running. Once you have obtained the applet context, you can then use it to ask the browser to display a URL, for example. This is not a task that you can perform directly in Java because of security restrictions. You will see an example of this in the section entitled "A Pop-up Document Viewer Applet" later in this chapter.
The java.util and java.io Packages These packages are the last two on our tour of the Java Class Hierarchy. The java.util package provides various utility classes, while the java.io package handles input and output to files and streams. The java.util package contains the Date object for manipulating date items, as in JavaScript. It also contains a series of classes that can be used to manipulate structured collections of things, including the Vector, HashTable, Dictionary, and Stack classes.
One of the most useful utility classes is StringTokenizer. This class is used to solve the age-old problem of decomposing a string, such as the following:
"this,is,a,comma,separated,list"
into its individual components, which will be delimited by a separator:
"this" "is" "a" "comma" "separated" "list"
The traditional way of solving this problem would be to search for the separator character, which is the comma character (,) in this case, and keep track of the individual substrings that occurred between the separators. We would find the first comma and separate the initial string into "this" and "is,a,comma,separated,list" and then repeat the procedure until each of the individual elements was extracted. The StringTokenizer class completely automates this tedious, but extremely common parsing task.
Anyone who has ever written string manipulation code that attempts to interpret a series of separate items (tokens) will appreciate the StringTokenizer class.
There is not much to be said about the java.io class for applet developers. One of Java's security restrictions prohibits local file access of any kind inside an applet. While we can certainly ask the browser to open a document using the file: protocol, the applet cannot do so itself. This restriction may be weakened in some future version of Java, but at the moment Java cannot touch the local file system.
This section analyzes and presents a pop-up document viewer applet in Java. This applet enables the user to specify the communication protocol to be used via a pop-up menu and also permits a full document name to be entered into a text field. Once the user commits to a particular document name by pressing a button, the applet requests that the browser open that document in a new window. This applet is designed as a simple demonstration of some of the capabilities of the java.applet and java.awt packages. It also illustrates Java's variety of event handling. The code is shown in listing 12.4. It can also be found on the CD-ROM in the file SD.java.
Listing 12.4 SD.java Viewing a Document
in a New Browser Window Using Java
/** A Java Applet to launch a document in a new window Comments for "javadoc" follow. @author Mark C. Reynolds @version 1.0 */ import java.awt.*; // 1; get AWT components import java.net.*; // 2; get URL and friends import java.applet.*; // 3; get Applet class methods public class SD extends java.applet.Applet { String whatproto = "http"; // 5; initial protocol to use String prevproto = whatproto; // 6; previous protocol used Choice ch; // 7; A pop-up menu choice TextField tf; // 8; User entered document name AppletContext ac; // 9; Ask the browser public void init() { // 10; Init method FlowLayout fl; Button bu; Font fo; // create a new left-justified flowlayout with 10 pixels of spacing //on each side of each item fl = new FlowLayout(FlowLayout.LEFT, 10, 10); // 14 setLayout(fl); // 15; make it the current layout fo = new Font("TimesRoman", Font.PLAIN, 18); // 16; a fairly big font setFont(fo); // 17; make it the current font ch = new Choice(); // 18; create a Choice instance ch.setFont(fo); // 19; make this the current font ch.addItem(whatproto); // 20; add "http" as a choice ch.addItem("gopher"); // 21; add literal "gopher" as a choice ch.addItem("ftp"); ch.addItem("file"); add(ch); // 24; add the pop-up menu to our flowlayout bu = new Button("Open"); // 25; create "Open" button add(bu); // 26; add the button to our flowlayout // create a textfield of length 70, and put the string "http://" in it tf = new TextField(whatproto + "://", 70); // 27 tf.setEditable(true); // 28; enable the user to modify the field add(tf); // 29; add the text field to our flowlayout ac = getAppletContext(); // 30; discover our context } // 31; end of init method public void start() { // 32; start method does nothing } public void stop() { // 34; stop method does nothing too } // change the text entry when user changes protocol private void modifytext() { // 36; int len = prevproto.length(); // 37; string len of prev protocol String cur = tf.getText(); // 38; get the current text String left = cur.substring(len); // 39; get the document name part // new name = new proto + old document name tf.setText(whatproto + left); // 40; } // 41; end of modifytext() private method private void launchdoc() { // 42; ask browser to open a document String doc = tf.getText(); // 43; get document name URL u = null; // 44; document's URL // test to ensure that there is a doc name, more than just proto:// if ( doc.length() <= ( whatproto.length() + 3 ) ) return; try { // 46; execute something that might abort u = new URL(doc); // 47; convert doc name to URL instance } catch (MalformedURLException ue) { // 48; // if it failed then print a message indicating why System.err.println("Invalid URL: " + ue.getMessage()); // return; // 50; and give up } // 51; end of try clause // ask for the document to be opened in a new window named "New Window" ac.showDocument(u, "New Window"); // 52 } // 53; end of launchdoc public boolean action(Event ev, Object arg) { // 54; event handler if ( ev.target instanceof Choice ) { // 55; Choice event prevproto = whatproto; // 56; save prev protocol name whatproto = arg.toString(); // 57; get the choice selected modifytext(); // 58; change the text displayed return(true); // 59; indicate event handled } // 60; end of Choice event if ( ev.target instanceof Button ) { // 61; Button event // if the "Open" button was selected then... if ( arg.toString().equals("Open") ) { // 62; launchdoc(); // 63; try to launch the document return(true); // 64; event handled } // 65; end of if statement } // 66; end of Button event return(false); // 67; did not handle event } // 68; end of action method } // 69; end of SD class
The init() method for the SD (Show Document) applet begins on line 10. Its job is to construct all the graphical elements that are displayed and, in the process, to initialize various instance variables that are used in the event handling methods modifytext() and launchdoc(). It starts out by creating a FlowLayout instance on line 14. This instance is left justified so that new elements are added starting at the left edge of each row. We also indicate that we would like at least 10 pixels between each element in a row (the second argument to the constructor), and between rows (the third argument). Line 15 makes this layout the current layout. Because an applet is actually a Panel, this now applies to the entire applet.
Line 16 accesses a plain Times Roman font with 18 point type. If your system does not have this particular font, you may need to adjust this statement to choose another font name (such as Helvetica or Geneva) and perhaps another font size (such as 24 point). You can also specify the empty string "" as the first parameter to the Font constructor; this will select a default font. Line 17 makes this font the current font for the applet's panel. Now, three items are put into the flow layout beginning at line 18: a pop-up menu, a button, and a single line text field.
The pop-up menu is created on line 18. Because pop-ups have their own fonts, which may be separate from the Panel in which they reside, you must set the font of the pop-up (line 19). This pop-up presents the user with a choice of four communication protocols that will be used. These are added to the pop-up in lines 20 through 23. Note that the default item, which represents the default protocol, is the one added first. That will be the initial value of the instance variable whatproto, which is the String "http." Line 24 finally adds this pop-up to the layout.
Line 25 creates a Button whose label is "Open." This is the button that the user presses to attempt to load a new document. It is added to the layout in line 26. The third item in our layout is an editable text field, which is created in line 27. The initial String that will be displayed is "http://", obtained by concatenating the default protocol "http" with the literal delimiter "://."
Line 28 makes this text field read/write, and line 29 adds it to the layout. Because this text field is quite long, it will be added in a new row below the pop-up menu and the Open button. Finally, line 30 initializes the instance variable, ac, to the applet's context. This is used in the launchdoc() method. Figure 12.2 shows the initial appearance of the SD applet after the init() method has been executed.
You will notice immediately that the start() and stop() methods of the SD applet do absolutely nothing. All of the activity in this applet is triggered in response to user interaction. As a result, all of our code is within the action method and none in start or stop. There is also no run method in this applet because we are not implementing any threads (the next applet we consider uses threads).
There are many different ways of performing event handling in Java. For example, Java applets that desire to handle only mouse down events can override a specialized method known as mouseDown. If you were only interested in button clicks on the Open button, you could use this approach. Because we are actually interested in handling events on the pop-up menu and button clicks on Open, the SD applet uses the more general approach.
If an applet overrides the action method, this indicates that it wants to handle more than one event type. The code for the action method begins on line 54. Note that this method accepts two arguments: an Event instance indicating the type of event, and an Object instance indicating where the event occurred. The target element of an Event indicates which graphical element was associated with the event.
On line 55, the Java keyword, instanceof, is used to
ask if the event was associated with a Choice item (a
pop-up menu). If the result is true, then the code in
lines 56 through 59 is executed. This code saves the previous
choice value (line 56), stores the new choice value by extracting
the String version of the Object selected (line
57), and then invokes the modifytext() private method
to fix up the document name being displayed. It then returns true
in line 59 to indicate that this event was successfully processed.
CAUTION |
All applet event handling methods must return true to indicate that the event has been handled and false to say that it has not. Failure to do so may cause the applet (and the browser) to become horribly confused. |
To understand what is going on, consider a concrete example. Suppose that the user had typed the document name, "http://ftp.javasoft.com", in the text field and then suddenly realized that this was not going to work because it would require the FTP protocol rather than the http protocol. The user then invokes the pop-up choice menu and selects FTP.
This selection triggers the action method of the SD applet. The test on line 55 will pass; prevproto will become the string "http" and whatproto the string "ftp." The modifytext() method on line 36 is now executed. It gets the length of the prevproto string (which will be 4), and also fetches the current document string on line 38. This will be the string "http://ftp.javasoft.com". It then peels off the substring that contains everything except the protocol name in line 39.
The local variable left will be the string "://ftp.javasoft.com."
Finally, it glues
the new protocol (stored in whatproto) onto the front
of this substring and
pushes that string out to the text field in line 40. The text
now reads, "ftp://ftp.javasoft.com." You are encouraged
to perform this experiment and verify that the protocol part of
the text field changes in lockstep with the value of the choice
selected from the pop-up menu.
The action method is also equipped to handle Button events. If the test on line 61 succeeds, this indicates that some button has been pressed, and the code on line 62 will be executed. Line 62 is a bit of defensive programming in which you test to make sure that it was the Open button that was pressed.
In our example, this test is superfluous because we have only one button. This line compactly converts the arg argument to a String and then uses its equals method to test against the literal "Open." This test must pass in our case, so line 63 will be executed and the launchdoc() method invoked. When that method returns, the action method returns true to indicate that the button press was handled (line 64). If this event was neither a pop-up selection nor a button press, then the action method returns false on line 67.
The launchdoc() method is used to actually ask the browser to open a document URL. It first gets the text of the document name in line 43. It then checks to make sure that that string is long enough on line 45. If the string is just a bare protocol, such as "file://", this test fails and the method returns at that line. The extra 3 in this test accounts for the three characters ://.
We now have a string representing a URL stored in the local variable doc, say "http://home.netscape.com." We would like to convert this to a URL instance because that is what we need for the subsequent request to the browser. This is executed in the try block beginning on line 46. A try block is required whenever a method invocation might generate a Java exception. Without being too specific, we can say that an exception results when you attempt to do something and it fails in a potentially unpleasant way. The URL constructor on line 47 is such a statement.
How did we know this? Is it necessary to remember all the functions that can generate exceptions? Fortunately, the answer is no. If you had tried to write u = new URL(doc); without enclosing it in a try block, the Java compiler would thoughtfully tell you that URL constructors can generate exceptions and that you should try to catch the MalformedURLException. We have complied with this request and enclosed the ominous statement in a try block, which always takes the following form:
try { ominous statement(s) } catch (SomeException e) { do something if an exception occurs }
In our case, if doc does not correspond to a valid URL for any reason, the applet receives the MalformedURLException and the code on lines 49 and 50 (within the catch clause) is executed. This code prints out a message indicating the reasonfor the failure on line 49, and then returns. Note that all exceptions have a getMessage() method that we have used to tell the user why the URL was malformed. A URL might be malformed because it was entered incorrectly, referred to a nonexistent server, or mentioned a document that the server did not want the user to see, among other reasons.
If the URL was well formed, then the catch clause will
not be executed and the code will arrive at line 52. This is the
critical statement that actually communicates with the browser.
We use the showDocument method of the AppletContext
ac to ask it to open the URL u in a new window
whose name is "New Window." This method call can still
fail, of course, even if the URL u is well constructed.
You should experiment with this applet by typing in various valid
and invalid URLs, hitting the Open button, and observing the results.
TROUBLESHOOTING |
The modifytext() method is the workhorse that handles the event associated with changing the choice. When you clear the text field, you are wiping out the protocol part ("http" for example) of the document name. The applet does not know this, however, because it is assuming that you will only change the protocol using the Choice item. Said another way, once you have cleared the text field, the protocol part of the document name is null, but the value of the instance variable, whatproto, is still set to the last protocol used. If you are going to enable the user to change the protocol directly, then modifytext() has to become smarter. Use the following algorithm:
|
The real power of Java comes through in its capability to rapidly display multiple images, giving the appearance of true animation on a Web page. You now have enough knowledge about Java threads and also about the AWT, that you can present a simple image viewer applet in Java. This applet provides the first concrete example of something that would be extremely difficult to accomplish in JavaScript. This applet can also be used as a template for writing more sophisticated applets that use Java threads. The code for the image viewer is shown in listing 12.5. This code appears in the file Simimg.java on the CD-ROM.
Listing 12.5 Simimg.java Displaying
Multiple Images Is Easy Using Java Threads
import java.applet.*; import java.awt.*; import java.net.*; public class Simimg extends Applet implements Runnable { Image imgs[]; // 6; the images themselves int imgidx = 0; // 7; image currently being displayed int nimg = 0; // 8; total number of images Thread mythread = null; // 9; animation thread public void init() { // 10; get params and images MediaTracker mt; // 11; track image loading using this class String tmp; // 12; tmp string String imgloc; // 13; location of images URL db; // 14; Applet's document BASE imgloc = getParameter("imgloc"); // 15; locate image directory if ( imgloc == null ) return; // 16; no image directory- give up tmp = getParameter("nimg"); // 17; get number of images if ( tmp == null ) return; // 18; no images-give up nimg = Integer.parseInt(tmp); // 19; convert to integer if ( nimg <= 0 ) return; // 20; invalid image count- give up imgs = new Image[nimg]; // 21; allocate array for images // create a mediatracker for the images mt = new MediaTracker(this); // 22; db = getDocumentBase(); // 23; find Applet's doc BASE // this loop starts loading all the images for(int i = 0, j = 1; i < nimg; i++, j++) { // 24; imgs[i] = getImage(db, imgloc + j + ".gif"); // 25; // tell the MediaTracker instance to track this image as ID 0 mt.addImage(imgs[i], 0); // 26; } // 27; end of image loading loop try { mt.waitForID(0); // 28; wait for all images } catch (InterruptedException e) { nimg = 0; // 30; if it failed set # images to 0 } // 31; end of catch clause of try block } // 32; end of init method public void run() { // 33; thread's run method Thread me; // 34; current thread me = Thread.currentThread(); // 35; get current thread me.setPriority(Thread.NORM_PRIORITY-1); // 36; decrease priority while ( imgidx < nimg ) { // 37; loop over images repaint(); // 38; draw current image try { Thread.sleep(100); // 40; wait a little while } catch (InterruptedException e) {} imgidx++; // 42; update index to next image } // 43; end of while loop } // 44; end of run method public void start() { if ( mythread == null ) { mythread = new Thread(this); mythread.start(); } } public void stop() { if ( mythread != null ) { mythread.stop(); mythread = null; } } public void paint( Graphics g ) { // 57; draw the current image if ( ( imgs != null ) && ( 0 <= imgidx ) && ( imgidx < nimg ) && imgs[imgidx] != null ) { // 59; sanity check all values g.drawImage(imgs[imgidx], 0, 0, this); // 60; draw it! } } for(int i = 0, j = 1; i < nimg; i++, j++) { // 24; loop to load all images imgs[i] = getImage(db, imgloc + j + ".gif"); // 25; begin loading to for(int i = 0; i < nimg; i++) { // 24; loop to load all images imgs[i] = getImage(db, imgloc + (i + 1) + ".gif"); // 25; begin loading
The init method for the Simimg applet performs two functions: it gets user parameters and it loads the images. This applet requires two PARAM tags to be specified, indicating where the images are to be found and how many there are. On line 15, the imgloc parameter is accessed; if it is not present, the init method returns immediately (line 16). Lines 17 through 20 get the nimg parameter, convert it to an integer, and make sure that it is a positive number. If this parameter is not present or is not a valid positive number, the init method returns.
Line 21 allocates an array just large enough to hold the indicated number of images. Line 22 initializes a MediaTracker instance. This instance will be used shortly to ensure that all images are loaded before the init method completes. Line 23 uses the getDocumentBase() method from the java.applet package to discover the applet's document BASE, saving that value in the local URL variable, db.
Statement 24 sets up a for loop to load all the images into the image array imgs. Note that two iteration variables, i and j, are used. This is because the imgs array is indexed from zero, but we are assuming that the names of the images will be something like IMG1.gif, IMG2.gif, and so forth. The i iteration variable marches through the array, while the j variable is used to build the names of the successive images.
The getImage() method is used on line 25 to launch the
image loading process. It takes two arguments: a URL
specifying a server directory and a String giving the
name of the file within that directory that is to be loaded. We
are using the applet's document BASE as the first argument, and
we are constructing the successive image names using the value
of the imgloc parameter (with a numeric suffix) as the
second argument. This particular version assumes that all the
images are GIFs.
NOTE |
At present, the getImage() method only understands the GIF and JPEG image formats. Other formats will be added in the future. |
The getImage() method is slightly deceptive in that it does not guarantee that the image is actually gotten when the method returns. All it does is begin to load the image. This is the purpose of statement 26. We add the image being loaded to the MediaTracker instance mt, which indicates that we are going to subsequently watch the loading process, presumably to ensure that it is done.
The addImage method takes two arguments: an Image instance, and an integer ID. The ID is used to group images into pools. We could, for example, track the first half of the images as ID 0 and the second half as ID 1. In this way we could be displaying the completely loaded ID 0 images while the ID 1 images were still being loaded.
This applet takes a brute force approach. All images are declared to have ID 0. On line 28, we actually wait for all the ID 0 images, which are all the images, to be fully loaded. Because this method can generate an InterruptedException, it must be executed within a try block, as we have seen in the SD applet. If this exception occurs, then we set the number of images nimg to 0, ensuring that none will be displayed.
The Simimg applet, unlike the SD applet, requires PARAM tags to properly function. A sample HTML file (Simimg.html on the CD-ROM) that uses this applet is shown in listing 12.6. Note that this particular HTML file indicates that we will load 16 images, that they will be located in the subdirectory "images" of the document's base directory, and that they will have the prefix "T." This means that the applet will attempt to load 16 images named images/T1.gif, images/T2.gif, images/T16.gif. It is also worth noting that this HTML implicitly assumes that all the images will fit in a drawing area that is 300 ¥ 150.
Listing 12.6 Simimg.html HTML for the
Simimg Applet
<HTML> <HEAD> <TITLE>A Simple Image Player</TITLE> </HEAD> <BODY> <HR> <APPLET CODE="Simimg.class" WIDTH=300 HEIGHT=150> <PARAM NAME="imgloc" VALUE="images/T"> <PARAM NAME="nimg" VALUE="16"> </APPLET> <HR> The <A HREF="Simimg.java">source</A>. </BODY> </HTML>
The formal structure of this applet is exactly the same as we described earlier in our discussion of threads. The start() and stop() methods are each responsible for creating the "animation" thread and for stopping it, respectively. The actual work is done by the run() method and indirectly by the paint() method.
The run method first discovers the identity of its own thread by invoking the static method currentThread() of the Thread class on line 35. It then lowers its own priority to be just slightly less than the default priority for threads (line 36). This makes sense if we think of threads in terms of a standard multitasking operating system. Higher priority tasks get more of the real CPU and generally execute more frequently. The same model applies to Java threads. By declaring itself less important, it is implicitly declaring that the drawing activity is more important.
Line 37 is the main image loop. As long as the instance variable, imgidx, is less than the total number of images, nimg, the loop will continue. Each pass through the loop issues a call to repaint(), which results in the paint() method being executed (line 38). Each pass through the loop also puts the animation thread to sleep for 100 microseconds (line 40). This is another way to give the drawing activity even more time and to also ensure that it is actually executed.
One of the side effects of using the static sleep method of the Thread class is to ensure that other threads that are waiting to run get a chance to do so. This method can also generate an exception, which we dutifully ignore. Finally, at the end of the loop, we update imgidx to process the next image.
The paint method, which begins on line 57, is a model of defensive programming. It checks to make sure that the imgs array is not null, that imgidx is neither too small nor too large, and that the actual image in the imgs array itself is not null. If all these tests pass, then it uses the drawImage method of the Graphics instance, g, to actually draw the image (line 60). Figure 12.3 shows the result after 16 images have been loaded and successfully displayed.
Figure 12.3 : Java simplifies the tasks of image manipulation and animation.