Java Unleashed
Page 1
Chapter 1 Java Makes Executable Content Possible By the mid 1990s, the World Wide Web had transformed the online world. Through a system of hypertext, users of the Web were able to select and view information from all over the world. However, while this system of hypertext gave users a high degree of selectivity over the information they chose to view, their level of interactivity with that information was low. Hypermedia had opened up many options for new kinds of sensory input a user might receive, including access to graphics, text, or even videos. However, the Web lacked true interactivity—real-time, dynamic, and visual interaction between the user and application. Java brings this missing interactivity to the Web. With a Java-enabled Web browser, you can encounter animations and interactive applications. Java programmers can make customized media formats and information protocols that can be displayed in any Javaenabled browser. Java’s features enrich the communication, information, and interaction on the Web by enabling users to distribute executable content—rather than just HTML pages and multimedia files—to users. This ability to distribute executable content is the power of Java. With origins in Sun Microsystem’s work to create a programming language to create software that can run on many different kinds of devices, Java evolved into a language for distributing executable content through the Web. Today, Java brings new interest to Web pages through applications that can all give the user immediate feedback and accept user input continuously through mouse or keyboard entries. In this chapter, I first present a description and definition of Java and explore what Java brings to Web communication. Then I present a brief “armchair” tour of some examples of what Java can do. If you want to go directly to programming in Java, see the other parts of this book. Otherwise, read this chapter and the others in this part for a survey of the potential of Java and the basics of its technical organization. These chapters should prepare you for the more detailed look at existing Java programming in the rest of this book.
What Can Java Do? Java animates pages on the Web and makes interactive and specialized applications possible. Figure 1.1 illustrates how the software used with the Web can support a variety of communication. With hypertext, the basis for information organization on the Web, you can select what information to view. Programmers can create some interactivity through gateway programs that use files of hypertext on the Web as interfaces. When you use a Web page with such a gateway program, you can access databases or receive a customized response based on a query. Java adds to these communication possibilities by making it possible to distribute executable content. This gives Web information providers the opportunity to create a hypertext page that engages users in continuous, real-time, and complex interaction. This executable content is literally downloaded to the user’s computer. Once downloaded, the executable content might run an animation, perform computation, or guide a user through more information at remote network sites. FIGURE 1.1. The Web’s software supports selectivity, display, computation, and interactivity. *
*A METAPHOR FOR JAVA One metaphor for hypertext is that it offers a visually static page of information (which can include text, graphics, sound, and video). The hypertext page can also have “depth” where it contains hyperlinks connecting to other documents or resources. Java transforms this static page metaphor into a more dynamic one. The information on a Java page on the Web does not have to be visually static or limited to a pre-defined set of ways to interact with users. Users encountering Java programs can take part in a wider variety of interactive behavior, limited only by the imagination and skill of the Java programmer. Java thus transforms a hypertext page into a stage, complete with the chance for actors and players to appear and things to happen. And, instead of the user being in the audience, a user of a Java-enabled Web browser is actively a part of the activity on this stage, changing what transpires and reacting to it, and shaping the information content delivered on the Web. Java thus brings Web pages alive through animation and a higher degree of interaction than what is possible through gateway programming alone. *
*JAVA’S HOME
Java Unleashed
Page 2
Sun Microsystems, the developers of Java, provide a one-stop collection of information about Java on the Web at http://java.sun.com/. This site includes a full range of the latest information on Java and Java-enabled browsers. Links from this site take you to detailed announcements, release information, documentation, and links to Java demonstrations.
What Is Java? The name Java is a trademark of Sun Microsystems and refers to the programming language developed by Sun and released in public alpha and beta versions in 1995. Java is used to create executable content that can be distributed through networks. Used generically, the name Java refers to a set of software tools for creating and implementing executable content using the Java programming language. In order for users to use Java content, they must have a key piece of Java software—the Java interpreter. To view Java content on the Web, a user’s Web browser must be Java-enabled. In the alpha release of Java, available during the spring and summer of 1995, only the special browser called HotJava could interpret programs created by the Java language. HotJava was developed by Sun to showcase the capabilities of the Java programming language. Other brands of Web browsers have since been upgraded to be able to interpret Java programs, most notably, the Netscape Navigator Web browser. A Java-enabled Web browser has the same capabilities as a non-Java Web browser, but additionally has the capability to interpret and display Java’s executable content. A Web browser that is not Java-enabled does not recognize Java and thus can’t display the Java executable content. Thus, Java-enabled browsers “see” the Web plus more—applications written using Java. As described in the section on Java’s origins (Java Origins and Direction), Java capability is expected to be integrated into future versions of other Web browsers and network tools. You can download the Java Developer’s Kit (JDK), which contains Java language development tools, from Sun Microsystems. Chapter 2 describes this software as well as Java’s technical design in more detail.
What Is Executable Content? Executable content is a general term that characterizes the important difference between the content that a Java-enabled Web browser downloads and the content a non–Java-enabled browser can download. Simply put: In a non-Java Web browser, the downloaded content is defined in terms of Multipurpose Internet Mail Extensions (MIME) specifications, whichinclude a variety of multimedia document formats. This content, once downloaded by the user’s browser, is displayed in the browser. The browser may employ a helper application (such as in displaying images, sound, and video). The overall pattern for the use of this content is user choice, browser download, and browser display. A Java-enabled browser also follows this pattern, but adds another crucial step. First, the Java-enabled browser, following requests by the user, downloads content defined by MIME specifications and displays it. However, a Java-enabled browser recognizes a special hypertext tag called APPLET. When downloading a Web page containing an APPLET tag, the Java-enabled browser knows that a special kind of Java program called an applet is associated with that Web page. The browser then downloads another file of information, as named in an attribute of the APPLET tag, that describes the execution of that applet. This file of information is written in what are called bytecodes. The Java-enabled browser interprets these bytecodes and runs them as an executable program on the user’s host. The resulting execution on the user’s host then drives the animation, interaction, or further communication. This execution of content on the user’s host is what sets Java content apart from the hypertext and other multimedia content of the Web. The process of using executable content in a Java-enabled browser, for the user, is seamless. The downloading and start of the execution of content happens automatically. The user does not specifically have to request this content or start its execution. And, as will be explored more in the next chapter, this executable content is platform-independent: Java programmers need not create separate versions of the applets for different computer platforms, as long as the user has a Java interpreter (or Java-enabled browser) installed on his or her computer. Thus, when surfing the Web with a Java-enabled browser, you might find not only all the hypertext content that the pre-Java age Web offered, but also animated, executable, and distributed content. Moreover, this executable content can include instructions for handling new forms of media and new information protocols.
Java Unleashed
Page 3
How Java Changes the Web Java profoundly changes the Web because it brings a richness of interactivity and information delivery not possible using previous Web software systems. Java makes it possible for programmers to create software that can be distributed across networks and run on many different kinds of computers. The resulting executable content shifts the site of activity from the Web server to the Web client (the Java-enabled browser). Figure 1.2 illustrates the technical difference between Java’s interactivity and hypertext selectivity and gateway programming. The figure illustrates how gateway programming allows for computation and response but not in realtime. Java’s interactivity is much richer and is centered on the client rather than the server. FIGURE 1.2. Java interactivity is based on executable content downloaded to the user’s computer.
Java Origins and Direction According to Michael O’Connell’s feature article on the origins of Java in the July 7, 1995 issue of SunWorld Online (http://www.sun.com/sunworldonline/swol-07-1995/swol-07-java.html), the development of Java began at Sun Microsystems in California by a team which included Java creator James Gosling even as the World Wide Web was being developed in Switzerland in 1991. The goal of this early development team was to develop consumer electronic products that could be simple and bug-free. What was needed was a way to createplatform-independent code and thus allow the software to run on any Central Processing Unit (CPU). As a starting point for a computer language to implement this platform-independence, the development team focused first on C++. However, the team could not get C++ to do everything they wanted in order to create a system to support a distributed network of communicating heterogeneous devices. The team abandoned C++ and developed a language called Oak (later renamed Java). By the fall of 1992, the team had created a project named Star 7 (*7), which was a personal hand-held remote control. The development team was incorporated as FirstPerson, Inc., but then lost a bid to develop a television set-top box for Time-Warner. By the middle of 1994, the growth in the Web’s popularity drew the team’s attention. They decided they could build an excellent browser using Java technology. With a goal of bringing their CPU-independent, real-time programming system to the Web, they built a Web browser. The browser, called WebRunner, was written using Java and completed early in the fall of 1994. Executives at Sun Microsystems were impressed and saw the technology and commercial possibilities that could result from a new browser: tools, servers, and development environments. On May 23, 1995, Sun Microsystems, Inc. formally announced Java and HotJava at SunWorld ’95 in San Francisco. Throughout the summer of 1995, interest in Java grew rapidly. The first wave of developers downloaded and used the alpha release of Java and the HotJava browser and experimented with this new software. The alpha release of Java was the basis for the entries in the first Java contest, with prizes awarded in September 1995. In late September, the pre-beta release of Java was announced. The pre-beta release was Sun’s move toward stabilizingthe language so that programmers could begin investing their efforts into more significantapplications. By the end of 1995, Java had gained the attention of the major players in the online world. Sun licensed Java to Netscape Communications, Inc. for use in its very popular Netscape Navigator browser. In addition, other major computer software and network players announced products involving Java, including Borland, Mitsubishi Electronics, Dimension X, Adobe, Lotus, IBM, Macromedia, Natural Intelligence, Oracle, and Spyglass. Most dramatic was Microsoft’s announcement on December 7, 1995 of their intent to license Java. Microsoft’s announcement was particularly dramatic, because, during the summer and fall of 1995, Bill Gates, chairman and CEO of Microsoft, had downplayed Java’s role, calling Java “just another language.” However, Microsoft’s year-end licensing announcement clearly showed that Microsoft considers Java part of an overall Internet strategy.
Java’s Current Status and Timeline
*
Java Unleashed
Page 4 *A JAVA ONLINE BIBLIOGRAPHY
You can connect to a bibliography of online articles and key press releases tracing the history and current status of Java at http://www.december.com/works/java/bib.html. Java was essentially not a player in the online world in the spring of 1995. However, by the end of that year, it had rocketed to a (perhaps over-hyped) prominence. Along the way, it passed through its alpha and beta stages and grabbed the attention of Web information providers. At SunWorld in May 1995, Sun unveiled Java and HotJava to the world and Netscape announced that it would license Sun’s Java programming language for its Netscape Navigator browser. By summer, Java and HotJava were in alpha stages of development. The Alphas were released for Sun Solaris 2.3, 2.4 and 2.5 SPARC-based and Microsoft Windows NT. Ports were underway for Microsoft Windows 95, and MacOS 7.5 and, in third-party projects, for other platforms and operating systems, including Windows 3.1, Amiga, NeXT, Silicon Graphics, and Linux. By the end of 1995, in the wake of the splashy launch of Microsoft Windows 95, there was much debate about the possibility of a “Java terminal” or an “Internet PC” (IPC), a device which would provide an inexpensive view into the Internet. An IPC would have minimal hardware and software in it and be specifically dedicated to supporting a Java-enabled Web browser, which could be continuously upgraded. Potentially, such an IPC could be a cheap, efficient way to encounter Web information. Widespread use of such IPCs could overthrow years of “API lock” on personal computing communications based on the Microsoft Windows/Intel (“Wintel”) standards. For the most current information on Java’s software releases for different platforms, see Sun Microsystem’s Java site: http://java.sun.com/ or other Java information sources at http://www.december.com/works/java/info.html.
Java Future Possibilities Java technology is not necessarily limited only to the Web. Java technology can be deployed in embedded systems, such as handheld devices, telephones, and VCRs. Mitsubishi Electronics has been working to use Java technology in these devices. The association of Netscape and Sun Microsystems that brought Java technology into Netscape browsers by late 1995 will be sure to have significance for Net software. With Netscape Navigator’s widespread installed base, the use of Java in applications could rapidly increase. Therefore, other Web browser manufacturers might be compelled to also license Java in order to keep pace with the information environment on the Web. The market for third-party object and tool libraries for Java is also a potential bonanza. Software layers on top of “raw” Java will enable developers to use more sophisticated tools to create applications and users to more easily build and incorporate Java applets in their Web pages. Chapter 2 describes how Java’s nature as an object-oriented programming language makes it particularly amenable for creating reusable, extensible software components. By integrating Java with Virtual Reality Modeling Language (VRML) (http://www.vrml.org/), developers can create virtual worlds that are not only three-dimensional but also animated and interactive. Dimension X ( http://www.dnx.com) has developed a JavaVRML mix called Iced Java which has the potential to take Web communication and interaction to an even richer level.
Illustrations of Java’s Potential Java is a new programming language, and programmers outside of Sun Microsystems have just begun to explore its potential. Since the public release of Java in its alpha and beta versions, however, many good examples of Java have already been developed. The rest of this chapter shows you examples of the kinds of functionality that Java can support, with an emphasis on the unique way Java enables the distribution of animated, executable content. Information on developing applications which can achieve this potential of Java is in later parts of this book.*
*ALPHA, BETA, JAVA The initial, or alpha, release of Java is incompatible with later releases: the alpha bytecodes won’t run in beta or later Java-enabled browsers; also, the alpha Java language used an HTML APP tag rather than the APPLET tag of the beta and later versions of Java.
Java Unleashed
Page 5
The development sections of this book focus on the beta version of Java which is upward compatible with later versions of Java.
Animation Java’s applications put animated figures on Web pages. Figure 1.3 shows a still image of Duke, the mascot of Java, who tumbles across a Web page displayed in the browser. Duke tumbles across the page, cycling through a set of graphic images that loop while the user has this page loaded. FIGURE 1.3. Tumbling Duke, mascot of Java. (Courtesy of Arthur van Hoff, Sun Microsystems) Animation isn’t limited to cartoon figures, however. Pages can have animated logos or text that moves or shimmers across the screen. Java animations also need not just be a decorative pre-generated figure, but can be a graphic that is generated based on computation. Figure 1.4 shows a bar chart applet. FIGURE 1.4. A bar chart applet. (Courtesy of Sun Microsystems)
Interaction While the animations shown can be static images that are drawn or generated, or animated images that can behave according to a preset algorithm (such as the tumbling Duke in Figure 1.3), animation can also be made interactive, where the user has some input on its appearance. Figure 1.5 shows a three-dimensional rendering of chemical models. Using the mouse, you can spin these models and view them from many angles. Unlike the source code for the graph applet shown in Figure 1.4, of course, the source code for the chemical modeling is more complicated. To the user, however, the chemical models seem three-dimensional, giving an insight into the nature of the atomic structure of these elements as no book could. FIGURE 1.5. Three-dimensional chemical models. (Courtesy of Sun Microsystems) The chemical models in Figure 1.5 respond to user clicks of the mouse. Another variation on this animation involves providing the user with a way to interact with an interface to get feedback. The “impressionist” drawing canvas in Figure 1.6 is an excellent example of this. Paul Haeberli at Silicon graphics developed an “impressionist” Java applet at http://reality.sgi.com/grafica/impression/imppaint.html. He originally developed this technique for creating this kind of graphic in 1988 for a Silicon Graphics IRIS workstation. Later patented, this technique drives his Java applet. The result is that you can draw using various size brushes on a canvas and reveal one of several pictures. FIGURE 1.6. Interactive impressionist drawing. (Courtesy of Paul Haeberli at Silicon Graphics) Another variation on interactivity is real-time interactivity. Figure 1.7 shows an interactive application that involves moving graphics that the user manipulates. This is the game of Tetris, in which you can try to line up the falling tile shapes to completely fill the rectangle. Using designated keys for playing, you interact with the interface to steer the falling shapes. This Tetris implementation demonstrates the possibilities for arcade-like games using Java technology. FIGURE 1.7. Tetris game. (Courtesy of Nathan Williams)
Interactivity and Computation The Tetris game described in the previous section, for example, demonstrates how interactivity and animation can work together. Both applets customized their animated output based on user input, so both applets were actually performing computation. However, an example that shows this computational capability in more concrete terms is in Figure 1.8, a simple spreadsheet. This spreadsheet works in much the same manner as the other applets, but emphasizes that the computational possibilities can enable users to have an environment in which to work instead of just a puzzle to solve. The spreadsheet shown enables you to change the contents of any of the 24 cells (A1 through D6) by replacing its label, value, or formula. (Not all cells are shown in the figure.) This is just like a real spreadsheet, which is more of an environment in which the user can work than a fixed game such as the crossword puzzle. This subtle difference is a profound one: using Java, a user can obtain an entire environment for open-ended interaction rather than a fixed set of options for interaction—opening up the Web page into a Web stage. FIGURE 1.8. A simple spreadsheet. (Courtesy of Sami Shaio, Sun Microsystems) This ballistic simulator shown in Figure 1.9 (http://jersey.uoregon.edu/vlab/Cannon2/) enables you to explore how a canon operates. You can adjust the muzzle angle and velocity, gravitational field strength, wind speed, and the density of the projectile. The purpose
Java Unleashed
Page 6
of this applet is to helpstudents understand the relation between muzzle velocity and gravitational potential and drag. FIGURE 1.9. A virtual canon. (Coding by Sean Russell, Software Manager, University of Oregon; Graphic images by Amy Hulse) Just as the user can download a canon, so too can a user download a “kit” for doing almost anything. Patrick A. Worfolk of the Geometry Center, University of Minnesota) has created a simulation that users can use to discover the properties of Lorenz equations (http://www.geom.umn.edu/~worfolk/apps/Lorenz/). The user can see the results of the numerical integration (the equations in the bottom of Figure 1.10) as well as graphical representations of their numerical solution. FIGURE 1.10. Numerical Simulation of the Lorenz Equations. (Courtesy of The Geometry Center, University of Minnesota)
Communication The preceding examples demonstrate many informational, animation, and computational applications of Java. Another application area is communication among people. Paul Burchard has created a system for users to share “chats” over the Web using a Java applet ( http://www.cs.princeton.edu/~burchard/www/interactive/chat/express.html). Not only do users see each other’s text, but they can follow each other on tours of the Web. Figure 1.11 shows this “chat touring” applet in action. FIGURE 1.11 Of course, communication takes place all the time on nearly all Web pages through text or other media. But a Java-enabled browser can also display multimedia. Figure 1.12 illustrates a player piano applet—you see the keyboard play and hear the music at the same time. FIGURE 1.12 Java can also be used to support mass communication in new ways. The Nando Times is a Web-based news service that has been very innovative in news delivery on the Web. Using Java, this news agency now provides a tickertape of headlines across its front page. The text under the Nando banner in Figure 1.13 scrolls continuously to show the world, national, sports, and political top stories at the moment. The four pictures under the labels for these categories also change, giving a “slide show” that is very effective in displaying new information without requiring the user to select it for viewing. This transforms the Web into something people can watch to get new information. FIGURE 1.13 Similarly, Figure 1.14 shows how current information feeds can act as surveillance for specific activities. The figure shows an applet from The Sports Network (www.sportsnetwork.com). This provides you with a life sportswire pop-up window. You can follow NFL and NHL action live, as it happens. As the scores change, this display changes, so that the sports-minded can keep up with the current games and scores. Like the Nando Times news feed, this sports feed changes the Web into something to watch in addition to something to interact with.
Applications and Handlers In addition to applets like the ones shown here, Java programmers can also create applications, or standalone programs, that don’t require the Java-enabled browser to run. (The HotJava browser itself is such an application, written using Java.) Applications could thus conceivably be new browsers or interfaces that interact with other network or local resources. FIGURE 1.14 Another kind of software program available with Java is a handler. A protocol handler enables a Java programmer to specify how a Java browser should interpret a particular type of protocol. The HotJava browser knows how to interpret the Internet protocols such as HTTP, FTP, Gopher, and others because of the browser distribution code. But if new protocols are invented, a Java programmer can specify how they should be handled by creating a protocol handler. Another type of handler is a content handler. This handler translates a particular specification for a file type based on Multipurpose Internet Mail Extensions (MIME). This content handler will specify how the HotJava browser should handle a particular type of file type. By creating a specification in a content handler, all Java-enabled browsers will be able to view this special format. The handlers and applications that Java makes possible have the potential to dramatically extend what can be browsed on the Web. No longer will information developers have to be concerned about making sure their users have the proper software to view a particular type of file or handle a new kind of protocol. The protocol and content handlers, like the executable content Java makes possible as applets, can be distributed as needed to requesting Java-enabled browsers.
Java Unleashed
Page 7
What Java Might Make Possible The previous examples illustrate only some of the potential of Java. A few of these examples are “toy” demonstrations meant to show the possibilities of Java. What kind of communication might Java foster? The Nando Times example shows an innovative application for providing information in a way that lets you to sit back and observe rather than selecting hypertext links. Java opens up a new degree of interactivity and customizability of interaction for the Web. Earlier Web development techniques of creating pages and linking them together will still be necessary in a Java-flavored Web. However, Java creates possibilities for richer kinds of content to be developed. The user can interact with and change the appearance of a Web page along with the state of a database using a Java-enabled browser. Thus, Java profoundly changes the texture of the Web in the following ways:
Java creates places to stop on the paths of the Web: A well-done Java application on a single hypertext page can engage a user for a long time. Rather than just text, sound, images, or videos to observe, a Java page can offer a place to play, learn, or communicate and interact with others in a way that isn’t necessarily based on going somewhere else on the Web through hyperlinks. If the hypertext links of the Web are like paths, the Java pages are like the towns, villages, and cities to stop on these paths and do something other than just observe or “surf.” Java increases the dynamism and competitiveness of the Web: Just as new browser technology prompted Web developers to create still more applications and pages to exploit these features, so too does Java technology promise a new round of content development on the Web. Java enriches the interactivity of the Web: Java’s interactivity is far richer, more immediate, and more transparent than the interactivity possible through gateway programming. Gateway programming still should have a role in Web applications, just as page design and multimedia presentation will still play a role. However, Java’s interactivity brings new possibilities of what can happen on the Web. With Java, transactions on the Web can be more customized, with immediate and ongoing feedback to the user. Java transforms the Web into a software delivery system: Java’s essential design as a language to deliver executable content makes it possible for programmers to create software of any kind and deliver it to users of Java-enabled browsers. Rather than having to focus on the interface, the Java programmer focuses on the interaction desired and lets the built-in features of the graphics take care of the rest of the implementation. The result is that very simple programs like the drawing and spreadsheet applications can be created quickly and distributed worldwide. The true potential of Java to transform the Web is still in its initial stages. New potential applications for commerce, information delivery, and user interaction still await the imagination and skill of future Java developers.
Summary Java is a programming language designed to deliver executable content over networks. A user or programmer should know what kinds of interaction Java can make possible and what its true potential can be: enlivening the Web, enriching the display of information in the form of animation and interactive applications.
Java enriches the interactivity possible on the Web. Rather than making just informational content possible, Java can support interactive content in the form of software that can be downloaded and run on any computer host with the Java interpretation environment installed. Java developed from ideas about platform-independent executable code. Sun Microsystems researchers have developed Java to be a powerful programming and information delivery system for use with the Web.
Java Unleashed
Page 8
Java makes animation, interaction, computation, distributed applications, and new forms of communication possible. Through protocol and content handlers, Java has the potential to make new formats and new protocols available for use on the Web. Java transforms the Web into a software delivery system where users have things to do rather than just places to go. Java may change the surfing behavior of Web users into playing and learning behavior in new interactive environments.
Java Unleashed
Page 9
Chapter 2 Java’s Design Is Flexible and Dynamic The Java programming language is uniquely suited for distributing executable content over networks. Java also offers a set of functions similar to many other programming languages. This chapter presents an overview of the technical design of Java. I begin with a minimal example of a “hello world” Java program. This should help you understand how Java and HTML connect. Using this information, you can then try out some of the Java programs shown in later parts of this book. Java also has specialized characteristics. In the second part of this chapter, I discuss in more technical detail how Java supports executable, distributed applications.
A Hello to Java The first part of understanding the technical details of Java is learning how Java interacts with the Web’s hypertext. The example shown in this section demonstrates how a special tag of the hypertext markup language (HTML) associates a Java program called an applet to a page on the Web. Viewed through a Java-enabled Web browser, a page with a Java applet can come alive with animation or interaction.
Java’s Connection to the Web As a language for delivering information on the Web, Java connects to the Web’s hypertext markup language (HTML) using a special tag called APPLET. Figure 2.1 summarizes this connection:
I.
In response to a request from a user of a Web browser, a document on a Web server written in HTML is downloaded to the user’s browser. II. If the HTML document contains an APPLET tag and the user’s Web browser is Java-enabled, the browser looks for the value of the Code attribute which identifies the Java bytecodes defining the applet. III. The applet bytecodes are downloaded from the Web server (or possibly some other Web server or network site identified by attributes of the APPLET tag) and placed on the user’s host computer. IV. The user’s Java-enabled browser interprets these bytecodes and runs the applet in the user’s browser. The applet commonly will provide a visual indication that it is operating and possibly accept input from some combination of the user’s cursor position, mouse buttons, or keyboard. Once the applet is downloaded, it need not be downloaded again, even if the applet code defines repeated loops or other interaction. The user might use a downloaded applet several times over the course of an online session without any more network retrievals. FIGURE 2.1. Java’s connection to the Web through the APPLET tag. A technical understanding of Java also requires a familiarity with HTML. HTML is the markup language used to create the documents displayed in Web browsers. HTML is not a layout language for describing how a page of hypertext should look (although there are many features of HTML that can be used to manipulate a page’s appearance). Rather, HTML tags the structure of a document and the meaning of text, so that a browser can display it in a scheme based on that browser’s design and the user’s preferences for the font size, style, and other features. An HTML document consists of text and tags that mark the structure of the document. Tags in an HTML document are delimited by the brackets < and >. Some tags always appear in a pair, as a start and end tag. For example, you can identify the title of an HTML document by placing the tags <TITLE> and around the text of the document’s title. Other tags don’t require a corresponding ending tag. For example, you can identify a paragraph start using the
tag.
Some tags have attributes, which qualify the tag’s meaning. For example, the APPLET tag has the attributes Code as well as Height and Width.
Java Unleashed
Page 10
Here is a simple HTML document: <TITLE>Example HTML Document
This is the body of the document.
This is the first item in an ordered list. This is the second item. When a Web browser interprets these HTML tags and text, it displays the document without the brackets < and >. A text-only browser renders this simple HTML example as Example HTML Document This is the body of the document. 1. This is the first item in an ordered list. 2. This is the second item. The document http://www.december.com/works/wdg/quickref.html contains HTML tags presented in a reference table, showing many more features of HTML that are available. The simple HTML example shown here is recognized by Sun’s HotJava and other Java-enabled browsers and should be enough to get you started in understanding how HTML connects to Java and testing simple applets.
A Simple Java Program The APPLET tag in an HTML document identifies the name of a Java program called an applet to be included in a Web page. The name of the applet is called its class name. This name is associated with the executable bytecodes that run the applet. For example, the following HTML example demonstrates how you can include an applet in a Web document. If you want to test this, put the following lines in a file called HelloWorld.html: <TITLE>HelloWorld ”This is it!” <APPLET Code=”HelloWorld.class” Width=”600" Height=”300"> Note that there is an open APPLET tag, <APPLET>, and a close APPLET tag, . The attributes shown here are Code, to identify the class file which contains the Java bytecodes and the Width and Height attributes, measured in pixels, to describe how much room should be reserved on the Web page for the applet.
*
*THE APPLET TAG SYNTAX Java uses an APPLET tag to place executable content in an HTML document.
Java Unleashed
Page 11
General Format <APPLET Codebase = “path to directory containing class files” Code = “name of class file” Width = “width of applet in pixels” Height = “height of applet in pixels”> The parameter values are given to the applet for use in its computations. Here is a sample use of the APPLET tag: <APPLET Codebase = “http://java.sun.com/applets/applets/TumblingDuke/” Code = “TumbleItem.class” Width = “400” Height = “95”> Of course, you need to create the Java source code for the applet named HelloWorld. You can find more details on programming in Java in Chapter 12, “Java Language Fundamentals.” For now, here is a minimal Java applet as a simple demonstration: import java.awt.Graphics; /** A first hello. */ public class HelloWorld extends java.applet.Applet { public void init() { resize(600, 300); } public void paint(Graphics context) { context.drawString(“Hello, world!”, 50, 100); } }
* *THE HelloWorld JAVA SOURCE CODE The source code for HelloWorld is on the CD-ROM that accompanies this book. I also provide the source code for the HelloWorld and other introductory Java applets at my book support Web page for Presenting Java at http://www.december.com/works/java.html. You can place Java code in a file named HelloWorld.java. Next, you have to compile the Java source code using the Java compiler, javac. At the operating system prompt ($), enter: $ javac HelloWorld.java If there are no errors, the compiler will create a file named HelloWorld.class that contains the bytecodes for the HelloWorld applet.
Java Unleashed
Page 12
So at this point, you have the following:
A file called HelloWorld.html. This is the hypertext markup language (HTML) source file. A file called HelloWorld.java. This is the Java language source file. A file called HelloWorld.class. This is the Java bytecode file. Figure 2.2 summarizes the Java source code and compilation relationships. If you have a Java-enabled browser, you can test this applet. Use the browser to open the file HelloWorld.html. Alternatively, you can also use the applet viewer supplied with the Java Developer’s Kit (JDK) to view applets without having to make an HTML page to reference them. Figure 2.3 shows what this example looks like in Netscape Navigator. FIGURE 2.2. Java source code and compilation relationships.
FIGURE 2.3.
Java browser display of the HelloWorld applet.
Java Technical Overview The preceding example concretely demonstrates the connection of Java applets to the Web through the APPLET tag. But this is only a view of Java from a very beginning perspective. To help you understand Java’s design and potential, this section provides a technical and conceptual overview of the language and its role in online communication. Java is an object-oriented programming language that is used in conjunction with Java-enabled Web browsers. These browsers can interpret the bytecodes created by the Java language compiler. The technical design of Java is architecture neutral. The term architecture in this sense refers to computer hardware. For example, your computer’s architecture could be an IBM personal computer with an Intel 386 chip. Programmers can create Java programs without having to worry about this underlying architecture of a user’s computer. Instead, the HotJava browser is customized to the user’s architecture. The HotJava browser interprets the bytecodes for the particular architecture of the user. This is a key characteristic of Java’s technical design.
The Network Communication Support Ring Around Java Java’s technical characteristics also place it within the larger context of online communication. We can step back from the Java source and bytecode files and look at the “big picture” of how Java fits into cyberspace. The operation of Java and Java-enabled browsers on the Web requires the interoperation of a variety of network systems. Of course, you don’t have to understand the interoperation of all of these systems to use Java or a Java-enabled browser. But, stepping back a bit from the applet-scale view of Java, we can look at its place in a “support ring” of networks and applications. The goal of Java is to bring executable content to the Web. When installed, a Java-enabled browser can provide an interface to animated and interactive applications. To view and interact with these applications, you must have a computer with a Java-enabled browser installed. If you want to download content from all over the Web, of course you also must have an Internet connection. Beginning with the widest context for the operation of the Java technology, let’s take a look at the systems necessary to support Java when delivering information globally (again, Java can be used on local networks not requiring the Internet, collapsing the set of support rings described here considerably):
I.
Cyberspace is the mental model people have for communicating or interacting online or through computers. Cyberspace activity includes variety of information, communication, and interaction. Cyberspace can be thought of as consisting of non-networked and networked regions. The networked region in cyberspace includes activity on connected local, regional, and global computer networks. The non-networked region might be standalone personal computer applications like word processors or CDROMs that contain no network references. II. The Internet computer network serves as a vehicle for data communication for many information dissemination protocols. Through gateways, many other networks in cyberspace can exchange data with
Java Unleashed
Page 13
the Internet. Because of this and also because of the large amount of information available on it, the Internet serves as a common ground for the networked region of cyberspace. III. The Web is an application that relies on a client/server model for data communication for distributing hypermedia. While the Web can operate on local networks that have no connection to the Internet, the Web is popularly known for its collection of information that is available globally through the Internet. IV. A Web client, known as a browser, is a software program that interprets and displays information disseminated using a variety of Internet information protocols. A Web browser is a user’s interface into the Web. A pre-Java Age (Mosaic class) browser usually operates in conjunction with a variety of helper applications to display multimedia. A Java-enabled browser can dynamically learn new protocols and media content types, so that it need not rely on these helper applications. However, a Netscape 2.0 browser, while Java-enabled, still makes use of helper applications, because the entire content of the Web isn’t Java-ized. V. HTML is used to create hypertext for the Web and marks the semantic structure of Web documents. HTML consists of tags and entities that identify the structure and meaning of text in documents. Documents contain references to other resources using a system of Uniform Resource Locators (URLs). VI. The HTML APPLET tag associates Java applications with HTML documents. This tag occurs in an HTML document and identifies a Java applet that will be placed in that document. VII. A Java programmer prepares a file of human-readable Java source code. This source code defines an applet, which is a class in the hierarchy of classes that make up the Java language. VIII. A Java programmer compiles a Java source code and makes the resulting bytecodes available for use through a reference to them in an APPLET tag in an HTML document. IX. HotJava, or any other Java-enabled browser, downloads hypertext as well as the executable bytecodes of the applet. The browser interprets and displays the applet, allowing a user to view or interact with the applet. Figure 2.4 summarizes the support rings for Java as it is used for worldwide distribution of information.
FIGURE 2.4.
The support ring of systems around Java. Again, you don’t have to know how to set up the entire range of networks, software, and equipment in Java’s “support ring.” All you need is to install a Java-enabled browser on your Internet-accessible system. From your point of view as a user, your main focus is your browser, or the interior fourth ring, of Figure 2.4. A Java programmer, in contrast, inhabits the seventh ring, and tries to meld the user’s experience of the Web’s hypertext with the specialized content Java makes possible. You can use Figure 2.4 to help place yourself in cyberspace as you fulfill different roles as an information user or producer.
Characteristics of Java as a Programming Language While users may want to have some awareness of how Java fits into online communication, programmers need to understand more specific technical characteristics of Java. The description in this section introduces many terms programmers should learn. According to the information provided by Sun Microsystems (http://java.sun.com/), Java is a “ …simple, object-oriented, distributed, interpreted, robust, secure, architecture neutral, portable, high-performance, multithreaded, and dynamic language.” This characterization identifies the key technical features of Java as shown in the following sections.
Java Unleashed
Page 14
Simple The developers of Java based it on the C++ programming language, but removed many of the language features that are rarely used or often used poorly. C++ is a language for object-oriented programming and offers very powerful features. However, as is the case with many languages designed to have power, some features often cause problems. Programmers can create code that contains errors in logic or is incomprehensible to other programmers trying to read it. Because the majority of the cost of software engineering is often code maintenance rather than code creation, this shift to understandable code rather than powerful but poorly understood code can help reduce software costs. Specifically, Java differs from C++ (and C) in these ways:
I. II. III. IV. V. VI.
Java does not support the struct, union, and pointer data types. Java does not support typedef or #define. Java differs in its handling of certain operators and does not permit operatoroverloading. Java does not support multiple inheritance. Java handles command-line arguments differently than C or C++. Java has a String class as part of the java.lang package. This differs from the null-terminated array of characters as used in C and C++. VII. Java has an automatic system for allocating and freeing memory (garbage collection), so it is unnecessary to use memory allocation and de-allocation functions as in C and C++. Object-Oriented Like C++, Java can support an object-oriented approach to writing software. Ideally, object-oriented design can permit the creation of software components that can be reused. Object-oriented programming is based upon modeling the world in terms of software components called objects. An object consists of data and operations that can be performed on that data called methods. These methods can encapsulate, or protect, an object’s data because programmers can create objects in which the methods are the only way to change the state of the data. Another quality of object-orientation is inheritance. Objects can use characteristics of other objects without having to reproduce the functionality in those objects that supports those characteristics. Inheritance thus helps in software re-use, because programmers can create methods that do a specific job exactly once. Another benefit of inheritance is software organization and understandability. By havingobjects organized according to classes, each object in a class inherits characteristics from parent objects. This makes the job of documenting, understanding, and benefiting from previous generations of software easier, because the functionality of the software has incrementally grown as more objects are created. Objects at the end of a long inheritance chain can be very specialized and powerful. Figure 2.5 summarizes the general qualities of data encapsulation, methods, and inheritance of an object-oriented language. Technically, Java’s object-oriented features are those of C++ with extensions from Objective C for dynamic method resolution.
Distributed Unlike the languages C++ and C, Java is specifically designed to work within a networked environment. Java has a large library of classes for communicating using the Internet’s TCP/IP protocol suite, including protocols such as HTTP and FTP. Java code can manipulate resources via URLs as easily as programmers are used to accessing a local file system using C or C++.
Interpreted When the Java compiler translates a Java class source file to bytecodes, this bytecode class file can be run on any machine that runs a Java interpreter or Java-enabled browser. This allows the Java code to be written independently of the users’ platforms. Interpretation also eliminates the compile and run cycle for the client because the bytecodes are not specific to a given machine but interpreted.
Java Unleashed
Page 15
Robust Robust software doesn’t “break” easily because of programming bugs or logic errors in it. A programming language that encourages robust software often places more restrictions on the programmer when he or she is writing the source code. These restrictions include those on data types and the use of pointers. The C programming language is notoriously lax in its checking of compatible data types during compilation and runtime. C++ was designed to be more strongly typed than C; however, C++ retains some of C’s approach toward typing. In Java, typing is more rigorous: a programmer cannot turn an arbitrary integer into a pointer by casting, for example. Also, Java does not support pointer arithmetic but has arrays instead. These simplifications eliminate some of the “tricks” that C programmers could use to access arbitrary areas of memory. In particular, Java does not allow the programmer to overwrite memory and corrupt other data through pointers. In contrast, a C programmer often can accidentally (or deliberately) overwrite or corrupt data.
Secure Because Java works in networked environments, the issue of security is one that should be of concern to developers. Plans are in the works for Java to use public-key encryption techniques to authenticate data. In its present form, Java puts limits on pointers so that developers cannot forge access to memory where not permitted. These aspects of Java enable a more secure software environment. The last section of this chapter outlines the layers of Java’s security in more detail.
Architecture Neutral The Java compiler creates bytecodes that are sent to the requesting browser and interpreted on the browser’s host machine, which has the Java interpreter or a Java-enabled browser installed.
Portable The quality of being architecture neutral allows for a great deal of portability. However, another aspect of portability is how the hardware interprets arithmetic operations. In C and C++, source code may run slightly differently on different hardware platforms because of how these platforms implement arithmetic operations. In Java, this has been simplified. An integer type in Java, int, is a signed, two’s complement 32-bit integer. A real number, float, is always a 32-bit floating-point number defined by the IEEE 754 standard. These consistencies make it possible to have the assurance that any result on one computer with Java can be replicated on another.
High-Performance Although Java bytecodes are interpreted, the performance sometimes isn’t as fast as direct compilation and execution on a particular hardware platform. Java compilation includes an option to translate the bytecodes into machine code for a particular hardware platform. This can give the same efficiency as a traditional compile and load process. According to Sun Microsystems testing, performance of this bytecode to machine code translation is “almost indistinguishable” from direct compilation from C or C++ programs.
Multithreaded Java is a language that can be used to create applications in which several things happen at once. Based on a system of routines that allow for multiple “threads” of events based on C. A. R. Hoare’s monitor and condition paradigm, Java presents the programmer with a way to support real-time, interactive behavior in programs.
Dynamic Unlike C++ code, which often requires complete recompilation if a parent class is changed, Java uses a method of interfaces to relieve this dependency. The result is that Java programs can allow for new methods and instance variables in objects in a library without affecting their dependent client objects. FIGURE 2.5. Object-orientation in software.
HotJava Is a New Kind of Web Browser The HotJava browser that showcases Java marks the start of a new generation of smart browsers for the Web. Not constrained to a fixed set of functionality, the HotJava browser can adjust and learn new protocols and formats dynamically. Developers of Web information using Java need no longer be constrained to the text, graphics, and relatively low-quality multimedia of the fixed set
Java Unleashed
Page 16
available for Web browsers in the pre-Java age. Instead, the HotJava browser opens possibilities for new protocols and new media formats never before seen on the Web. Through the past half-decade of development of the World Wide Web, new browser technologies have often altered the common view of what the Web and online communication could be. When the Mosaic browser was released in 1993, it rocketed the Web to the attention of the general public because of the graphical, seamless appearance it gave to the Web. Instead of a disparate set of tools to access a variety of information spaces, Mosaic dramatically and visually integrated Internet information. Its point-and-click operation changed ideas about what a Web browser could be, and its immediate successor, Netscape, has likewise grown in popularity and continued to push the bounds of what is presented on the Web. HotJava, however, marks a new stage of technological evolution of browsers. HotJava breaks the model of Web browsers as only filters for displaying network information; rather, a Java-age browser acts more like an intelligent interpreter of executable content and a displayer for new protocol and media formats. The 2.0 release and above of Netscape Communications’ Navigator browser is Java-enabled. Netscape justifiably characterizes their browser as a platform for development and applications rather than just a Web browser.
Pre-Java Browsers The earliest browser of the Web was the line-mode browser from CERN. The subsequent Mosaic-class browsers (Mosaic and Netscape from 1993 to mid-1995) dramatically opened the graphical view of the Web. However, the Mosaic-type browsers acted as an information filter to Internet-based information. Encoded into these browsers was knowledge of the fundamental Internet protocols and media formats (such as HTTP, NNTP, Gopher, FTP, HTML, GIF). The browsers matched this knowledge with the protocols and media formats found on the Net, and then displayed the results. Figure 2.6 illustrates this operation as the browser finds material on the Net and interprets it according to its internal programming for protocols or common media formats. These browsers also used helper applications to display specialized media formats such as movies or sound. FIGURE 2.6. Pre-Java browsers acted as filters. A pre-Java browser was very knowledgeable about the common protocols and media formats about the network (and therefore very “bulky”). Unfortunately, a pre-Java browser could not handle protocols for which it had not been programmed or media formats for which it did not have a helper application available. These are the technical shortcomings that a Java-age browser addresses.
Java-Age Browsers A Java-age browser is lightweight because it actually has no pre-defined protocols or media formats programmed into its core functionality; instead the core functionality of a HotJava browser consists of the capability to learn how to interpret any protocol or media format. Of course, the HotJava browser is told about the most common protocols and formats as part of its distribution package. In addition, any new format or protocol that a Java programmer might devise, a HotJava browser can learn. As Figure 2.7 shows, a Java-age browser is “lightweight,” not coming with a monolithic store of knowledge of the Web, but with the most important capbility of all—the ability to learn. FIGURE 2.7. The Java-age browser can learn.
Java in Operation Another way to put the Java language, a Java-enabled browser, and the larger context of online communications into perspective is to review the processes that occur when a user with a Java-enabled browser requests a page containing a Java applet. Figure 2.8 shows this process.
I. II.
The user sends a request for an HTML document to the information provider’s server. The HTML document is returned to the user’s browser. The document contains the APPLET tag, which identifies the applet. III. The corresponding applet bytecode is transferred to the user’s host. This bytecode had been previously created by the Java compiler using the Java source code file for that applet. IV. The Java-enabled browser on the user’s host interprets the bytecodes and provides display.
Java Unleashed V.
Page 17
The user may have further interaction with the applet but with no further downloading from the provider’s Web server. This is because the bytecode contains all the information necessary to interpret the applet.
FIGURE 2.8. Java operation within a Web page.
Java Software Components Another aspect of the technical make-up of the Java environment is the software components that comprise its environment. See the Sun Microsystems Java site (http://java.sun.com/) for complete details on obtaining the Java Developer’s Kit (JDK). Programmers need to learn the vocabulary of the pieces of the JDK as well as terms for what can be created with it.
Java Language Constructs Java is the programming language used to develop executable, distributed applications for delivery to a Java-enabled browser or the Java Interpreter. A Java programmer can create the following:
applets: Programs that are referenced in HTML pages through the APPLET tag and displayed in a Javaenabled browser. The simple “hello world” program shown at the start of this chapter is an applet. applications: Standalone programs written in Java and executed independently of a browser. This execution is done using the Java interpreter, java, included in the Java code distribution. The input and output of these applications need not be through the command line or text only. The HotJava browser itself is a Java application. protocol handlers: Programs that are loaded into the user’s HotJava browser and interpret a protocol. These protocols include standard ones such as HTTP orprogrammer-defined protocols. content handlers: A program loaded into the user’s HotJava browser, which interprets files of a type defined by the Java programmer. The Java programmer provides the necessary code for the user’s HotJava browser to display/interpret this special format. native methods: Methods that are declared in a Java class but implemented in C. These native methods essentially allow a Java programmer to access C code from Java. Java Distribution Software The Java Development Kit available from Sun Microsystems includes the following pieces:
Java Applet Viewer. This lets you run and test applets without having to create an HTML page to refer to it. Note that the beta release of the JDK included an applet viewer instead of an updated HotJava browser. Java Compiler. This is the software used to translate the human-readable Java source code to machinereadable bytecodes. The Java compiler is invoked using javac command. Java Language Runtime. This is the environment for interpreting Java applications. Java Debugger API and Prototype Debugger. This is a command-line debugger that uses this API. The Java Application Programming Interface (API) The Java Application Programming Interface (API) is a set of classes that are distributed with the JDK and which programmers can use in Java applications. The documentation of the API that is provided online is key reference material for Java programmers. The API consists of the packages in the Java language. The API documentation includes a list of
Java Unleashed
Page 18
All packages. These include: java.applet java.awt java.awt.image java.awt.peer java.io java.lang java.net java.util All classes in a package. At the package level, information available includes: Interfaces Classes Exceptions Documentation on each class. This includes: Variables Constructors Methods The Java Virtual Machine Specification A document available from the Sun Microsystems Java site (http://java.sun.com/) called “The Java Virtual Machine,’ specifies how the Java language is designed to exchange executable content across networks. The aim of this specification is to describe Java as a non-proprietary, open language that may be implemented by many companies and sold as a package. The Java Virtual Machine specification describes in abstract terms how Java operates. This leaves the details of implementation up to the programmers who creates Java interpreters and compilers. The Java Virtual Machine specification also concretely defines the specific interchange format for Java code. This is called “The Java Interchange Specification.” The other part of the Virtual Machine specification defines the abstractions that can be left to the implementor. These abstractions are not related to the interchange of Java code. These include, for example, management of runtime data areas, garbage collection algorithms, the implementation of the compiler and other Java environment software, and optimization algorithms on compiled Java code.
Java Security Because a HotJava browser downloads code across the network and then executes it on the user’s host, security is a major concern for Java-enabled browser users and Java programmers. HotJava includes several layers of security, including the following:
The Java language itself includes tight restrictions on memory access very different from the memory model used in the C language. These restrictions include removal of pointer arithmetic and removal of illegal cast operators. A bytecode verification routine in the Java interpreter verifies that bytecodes don’t violate any language constructs (which might happen if an altered Java compiler were used). This verification routine checks to make sure the code doesn’t forge pointers, access restricted memory, or access objects other than according to their definition. This check also ensures that method calls include the correct number of arguments of the right type, and that there are no stack overflows.
Java Unleashed
Page 19
A verification of class name and access restrictions during loading. An interface security system that enforces security policies at many levels. At the file access level, if a bytecode attempts to access a file to which it has no permissions, a dialog box will pop up enabling the user to continue or stop the execution. At the network level, future releases will have facilities to use public-key encryption and other cryptographic techniques to verify the source of the code and its integrity after having passed through the network. This encryption technology will be the key to secure financial transactions across the network. At runtime, information about the origin of the bytecode can be used to decide what that code can do. The security mechanism can tell if a bytecode originated from inside a firewall or not. You can set a security policy that restricts code that you don’t trust.
Summary The Java programming language is uniquely designed to deliver executable content across networks. As a language, it flexibly offers features for programmers to create a variety of software. Java also assures interoperability among platforms as well as security:
The Java programming language works in conjunction with a special kind of browser and bytecode interpreter. Java can exist within the context of World Wide Web communication and therefore “sits on top of” a set of applications on networks for data communications to support information retrieval. The Java language is object-oriented and specially designed to support distributed, executable applications. In operation, the Java language compiler creates bytecodes that are downloaded across the network to a user’s computer. The user’s computer runs these bytecodes. Components of Java software include the HotJava browser, the Java interpreter, the Java compiler, and tools for developing Java applications. Java’s designs for security are tailored for distributing executable content on networks.
Java Unleashed
Page 20
Chapter 3 Java Transforms the World Wide Web The World Wide Web has dramatically changed the online world and continues to grow in popularity. As a communication system, the Web can give information providers the ability to distribute and collect information globally and instantly. For users, the Web is a dynamic view into the works and ideas of millions of people and organizations worldwide. With origins in ideas about nonlinear thinking, the Web is an information integrator on the Internet and plays a major role in online cyberspace. What Java brings to the Web is a new way of communicating. Instead of relying on the Web servers to provide information and functionality, Java’s executable content makes Java-enabled Web browsers “smart.” This chapter briefly explores how Java transforms the World Wide Web. The Web supports a range of communication, information, and interaction using hypertext for organizing information. Multimedia used with hypertext, called hypermedia, can enrich the Web’s information. Special programming techniques used with the Web’s hypertext, such as gateway programming or languages such as Java or Virtual Reality Modeling Language, can expand the Web’s possibilities for interactivity, information delivery, and communication. To learn Java’s power as it can be used for the global distribution of information, you should first understand what the Web is and the significance of Java’s changes to it. If you are a seasoned Web user, you probably have already realized from the previous two chapters how Java extends the Web’s potential; you might want to skip to Chapter 4 to begin looking at specifics. This chapter takes a close look at the Web and Java’s part in it.
Overview of the Web The World Wide Web was originally developed to meet the information needs of researchers in the high-energy physics community. Today, the World Wide Web offers a system for distributing hypermedia information locally or globally. Technically, the World Wide Web enables a seamless, global system of multimedia communication. This information is organized associatively and delivered according to user requests. This section briefly surveys the historical origins of the Web and how the confluence of ideas in network technology has reached fruition in the global Web of today. Java is just the latest installment of a series of innovations in hypertext and Web communication.
Ideas Leading to the Web Vannevar Bush described a system for associatively linking information in his July 1945 article in The Atlantic Monthly, “As We May Think.” (This article is available on the Web at http://www.isg.sfu.ca/~duchier/misc/vbush/.)
The Origins of Hypertext Bush called his system a memex (memory extension), and proposed it as a tool to help the human mind cope with information. Having observed that previous inventions had expanded human abilities for dealing with the physical world, Bush wanted his memex to expand human knowledge in a way that took advantage of the associative nature of human thought. In 1965, Ted Nelson coined the term hypertext to describe text that closely followed Bush’s model, in that Nelson’s text was not constrained to be sequential. Hypertext, as Nelson described, links documents to form a web of relationships that draw on the possibilities for extending and augmenting the meaning of a “flat” piece of text with links to other texts. Hypertext is more than just footnotes that serve as commentary or further information about a text; rather, hypertext extends the structure of ideas by making “chunks of” ideas or information available for inclusion in many parts of multiple texts. Nelson also coined the term hypermedia, which is hypertext not constrained to be text. Hypermedia can include expressions of multimedia—pictures, graphics, sound, and movies.
The Origins of the Web Vannevar Bush’s and Ted Nelson’s ideas about information systems showed up in another project in the late 1980s. In March 1989, Tim Berners-Lee, a researcher at the Conseil European pour la Recherche Nucleaire (CERN) European Laboratory for Particle Physics in Geneva, Switzerland, proposed a hypertext system to enable efficient information-sharing for members of the high-energy physics community. Berners-Lee had a background in text processing, real-time software, and communications, and had previously
Java Unleashed
Page 21
developed a hypertext system he called “Enquire” in 1980. Berners-Lee’s 1989 proposal, called “HyperText and CERN,” circulated for comment. The following were important components of the proposal:
A user interface that would be consistent across all platforms and that would enable users to access information from many different computers A scheme for this interface to access a variety of document types and information protocols A provision for “universal access,” which would enable any user on the network to access any information By late 1990, an operating prototype of the World Wide Web ran on a NeXT computer, and a line-mode user interface (called “WWW”) was completed. The essential pieces of the Web were in place, although not widely available for network use. Throughout the early 1990s, interest in the Web grew and spread worldwide. In March 1991, the WWW interface was used on a local network, and by May of that year, it was made available on central CERN machines. On January 15, 1992, the WWW interface became publicly available from CERN, and the CERN team demonstrated the Web to researchers internationally throughout the rest of the year.
Mosaic: The First “Killer” App In 1993, interest in the Web grew very rapidly. A young undergraduate who was then at the University of Illinois at UrbanaChampaign named Marc Andreessen worked on a project for the National Center for Supercomputing Applications (NCSA), and lead a team that developed a browser for the Web called Mosaic. The group released an alpha version of Mosaic for the X Window System in February 1993 that was among the first crop of graphical interfaces to the Web. Mosaic, with its fresh look and graphical interface presenting the Web using a point-and-click design, fueled great interest in the Web and online information. By the end of 1993, attendees at the Internet World conference and exposition in New York City were eager to learn about graphical interfaces to the Web. The New York Times hailed Mosaic as the Internet’s “killer application.” In 1994, more commercial players got into the Web game. Companies announced commercial versions of Web browser software, including Spry, Inc. Marc Andreessen and colleagues left NCSA in March to form, with Jim Clark (former chairman of Silicon Graphics), a company that later became known as Netscape Communications Corporation ( http://home.netscape.com/). By May 1994, interest in the Web was so intense that the first international conference on the World Wide Web, held in Geneva, overflowed with attendees. By June 1994, there were 1,500 known public Web servers. By mid-1994, it was clear to the original developers of the Web at CERN that the stable development of the Web should fall under the guidance of an international organization. In July, the Massachusetts Institute of Technology (MIT) and CERN announced the formation of the World Wide Web Consortium, or W3C.
The Web Today Today, the W3C ( http://www.w3.org/hypertext/WWW/Consortium/) guides the technicaldevelopment and standards for the evolution of the Web. The W3C is a consortium of universities and private industries, run by the Laboratory for Computer Science (LCS) at MIT collaborating with CERN ( http://www.cern.ch/), and Institut National de Recherche en Informatique et en Automatique (INRIA), a French research institute in computer science (http://www.inria.fr/). In 1995, the development of the Web was marked by rapid commercialization and technical change. Netscape Communication’s Mozilla browser continued to include more extensions of the HyperText Markup Language (HTML), and issues of security for commercial cash transactions garnered much attention. By May 1995, there were more than 15,000 known public Web servers, a tenfold increase over the number from a year before. Many companies had joined the W3C by 1995, including among others, AT&T, Digital Equipment Corporation, Enterprise Integration Technologies, FTP Software, Hummingbird Communication, IBM, MCI, NCSA, Netscape Communications, Novell, Open Market, O’Reilly & Associates, Spyglass, and Sun Microsystems. By mid-1995, the emergence of the Java and Virtual Reality Modeling Language (VRML) technologies placed the Web at the start of another cycle of rapid change and alteration. Java, in development for several years at Sun Microsystems, promises to make the Web far more interactive than ever before possible. (See Chapter 1, “Java Makes Executable Content Possible.”) Virtual Reality Modeling Language, which can allow developers to model three-dimensional scenes for delivery through special Web browsers, may also dramatically change what the Web has to offer. For more information on VRML, see Chapter 34, “VRML and Java.”
Java Unleashed
Page 22
A Definition of the World Wide Web Despite its rapid growth and technical developments, the Web in 1996 retains the essential functional components it had in its 1990 form. Its popularity as a view of the Internet, however, has muddied popular understanding of it, because the Web is sometimes viewed as equivalent to the Internet and browsers are sometimes thought of as equivalent to the Web rather than a view into it. However, the Web is a very distinct system from the Internet and its browsers. First, the Web is not a network, but an application system (a set of software programs). Second, the World Wide Web can be deployed and used on many different kinds of networks (not necessarily just Internet networks) and it can even be used on no network at all or on a local network unconnected to any other. *
*A METAPHOR FOR THE WEB Imagine a library in which all the spines of the books have been removed and the gravity in the building has been turned off, allowing the pages to float freely. If people could connect one page to another using very light threads taped to the pages, this would be similar to the way the Web’s hypertext is arranged. Pages free-float, so that users might encounter a work from any page within it, and reach other works by following the threads leading off a page. Here is a more technical definition of the Web: The World Wide Web is a hypertext information and communication system popularly used on the Internet computer network with data communications operating according to a client/server model. Web clients (browsers) can access multiprotocol and hypermedia information. Figure 3.1 summarizes the technical organization of the Web based on this definition.
FIGURE 3.1.
The technical organization of the Web.
How Does Java Transform the Web? Java changes the Web by bringing more “intelligence” to Web browsers. Although Java-enabled browsers have user interfaces that are much the same as many other Web browsers, their technical operation marks a significant shift in focus. Java’s executable content requires Java-enabled browsers to be smart; that is, they must be able to interpret executable content.
Java Supports Client-Side Interactivity A client-server model for networked computer systems involves three components: the client, the server, and the network. A client is a software application that most often runs on the end-user’s computer host. A server is a software application that most often runs on the information provider’s computer host. Client software can be customized to the user’s hardware system and it acts as an interface from that system to information provided on the server. The user can initiate a request for information or action through the client software. This request travels over the network to the server. The server interprets the request and takes some desired action. This action might include a database lookup or a change in recorded database information. The results of the requested transaction (if any) are sent back to the client for display to the user. All client/server communication follows a set of rules, or protocols, which are defined for the client/server system. Figure 3.2 summarizes these relationships, showing the flow of a request from a client to a server and the passing back of information from a server to a client. A client might access many servers employing the protocols both the server and client understand. The distributed form of “request” and “serve” activities of the client/server model allows for many efficiencies. Because the client software interacts with the server according to a predefined protocol, the client software can be customized for the user’s particular computer host. (The server doesn’t have to worry about the hardware particularities of the client software.) Forexample, a Web client (a browser) can be developed for Macintosh computers that can access any Web server. This same Web server might be accessed by a Web browser written for a UNIX workstation running the X Window system. This makes it easier to develop information, because there is a clear demarcation of duties between the client and the server. Separate versions of the information need not be developed for any particular hardware platform, because the customizations necessary are written into client software for each platform. An analogy to the client/server model is the television broadcast system. A customer can buy any kind of television set (client) to view broadcasts from any over-the-air broadcast tower (server). Whether the user has a wristband TV or a projection screen TV, the set receives information from the broadcast station in a standard format and displays it appropriate to the user’s TV set. Separate TV programming need not be created for each kind of set, such as for color or black-and-white sets or different size sets. New television stations that are created will be able to send signals to all the currently in-use television sets. FIGURE 3.2.
Java Unleashed
Page 23
A client/server model for data communication. Java brings another dimension to the client/server model. Of course, Java does follow the basic model: A Java-enabled browser is a client that sends requests to Web servers for information. The Java-enabled browser interprets and displays the information sent from the server. This information includes both the hypertext as well as any bytecodes. These bytecodes are Java’s new twist on this model. The Java clients execute the content distributed from the servers. These bytecodes, as described in Chapter 2, are also architectureneutral, just like the other information sent from the Web server.
Java Can Eliminate the Need for Helper Applications Helper applications include software that a (non-Java-enabled) Web browser invokes to display multimedia information to the user. For example, in order for the user to view movies, the Web browser must have movie display software installed and available. To display inline graphical images in an HTML document, the Web browser must be graphical—that is, employ a system such as X Window system, Macintosh Operating System, or Microsoft Windows as a graphical user interface. Instead of relying on helper applications, programmers developing applets for Java-enabled browsers can create content handlers to handle media formats.
Java Adds to the Web’s Communication Contexts and Potential The Java language and its browsers are part of the larger context for communication on the Web. Whether you write and distribute applets or just observe them, you take part in communication activities and traditions that have been developing on the Web for many years. Because Java is still so new, it has not yet appeared in all Web communication contexts. You’ll see more specific examples of Java used on the Web in later chapters of this book. This subsection briefly reviews the Web’s context and potential and how Java can be a part of it.
Java and Communication Contexts on the Web Communication on the Web can take many forms and take place in many contexts. Genres, or traditional ways for communicating, have evolved on the Web. These genres correspond, in many ways, to offline human communication contexts:
Interpersonal:The Web provides a way for users to create a home page, which typically conveys personal or professional information. The practice of creating a home page emerged from the technical necessity of defining the “default” page that a Web browser displays when requesting information from a Web server when only the host name or a host and directory name is given. Home pages are thus traditionally the top-level page for a server, organization, or individual. When created by individuals, home pages often reveal detailed personal information about their authors and are often listed in directories of home pages. Also, individuals often follow the tradition of linking to colleagues’ or friends’ pages, creating electronic tribes. (Mathematically, these electronic tribes are defined by the cliques of home pages in the directed graph describing the Web.) When used interpersonally, personal home pages offer one-to-one communication, although the technical operation of all pages on the Web is one-to-many. “applets” have not yet become prominent, but Java may enable individuals to create an executable “persona” with which other Web users can interact. Group: As described in the interpersonal definition, cliques of personal pages can define a particular Web tribe or group. Similarly, people can form associations on the Web that are independent of geography and focused on interest in a common topic. Subject-tree breakdowns of information on the Web often evolve from collaborative linking and the development of resource lists and original material describing a subject. (See the following section’s discussion about locating subject-based information on the Web.) Similarly, groups of people associate on the Web based on common interests in communication.
Java Unleashed
Page 24
Organizational: Many of the initial Web servers appearing on the Web belong to an organization, not individuals, so the home page for a server often identifies the institution or organization that owns the server. In this way, the genre of the Campus-Wide Information System (CWIS) evolved on Web servers of educational institutions. Similarly, commercial, governmental, and non-governmental organizations have followed the pattern established by CWISs to a large degree. organizations now use Java in their Web pages to add interest and provide service to users. You will see examples of these pages in the next chapter. Mass: Just as other media have been used for one-to-many dissemination of information (newspapers, radio, television), so too is the Web used for mass communication. Many commercial and noncommercial magazines and other publications are distributed through the Web. Moreover, as noted previously, all publicly available Web pages are potentially readable to anyone using the Web, and are thus potentially one-to-many communication. is being used actively for mass communication, as shown in the example from the Nando Times in Chapter 1. The key concept to understand is that the Web as a communication system can be flexibly used to communicate in a variety of ways. The classification of the communication (in the categories listed) depends on who is taking part in the communication. The exact classification of any expression on the Web can be blurred by the potentially global reach of any Web page. Thus, a personal home page may be used interpersonally, but it may be accessed far more times on the Web than a publication created and intended for mass consumption. Java’s capability for delivering interactive content adds new possibilities to each of these categories.
Java and the Web’s Potential The Web is a flexible system for communication that can be used in many contexts, ranging from individual communication on home pages through group communication and mass communication. In addition to these contexts, the Web also serves the following functions:
Information Delivery: A Web browser provides the user with a “viewer” to look into FTP space, Gopherspace, or hypertext information on the Web. The structure of hypertext enables user selectivity because of the many ways a user can choose to follow links in hypertext. Java adds the potential for new protocol handlers and content handlers. Communication: People can use Web hypertext to create forums for sharing information, discussion, and helping group members make contact with each other. Java’s executable content introduces new forms of more interactive communication. Interaction: Using gateway programming, a Web developer can build some degree of interactivity into an application, providing the user with a way to receive customized information based on queries. Gateway programs can also enable a user to change or add to an information structure. A higher degree of interactivity is possible using Java because of its executable content. (Chapter 1 surveys Java’s unique contribution to the Web’s interactivity.) Computation: Using gateway programming, the Web can be used to provide an interface to other applications and programs for information processing. Based on user selections, a Web application can return a computed or customized result through a gateway program. Java programmers can create software for computation that can be distributed and executed. Figure 3.3 shows the important distinction between selectivity and gateway programming interactivity. When the user accesses the Web server on the left, content is presented using hypertext. The links in the hypertext pages give the user a great deal of choice, or selectivity, for encountering information in the database. However, no information is customized to user inputs or computed based on
Java Unleashed
Page 25
user requests. Although this server offers the user great flexibility in information retrieval because of the hypertext design of its pages, this server is not interactive. The key to the level of interactivity, as shown in the server on the right, is that the executable program accepts input from the user through a Web page. Based on these user inputs, this executable can compute a result and (possibly, also using information from the database) return this customized information result to the user. Moreover, the executable program also enables the user to (possibly) change the contents of the database, or make some other change in the database or files on the server. These changes might include altering the structure or contents of hypertext or the contents of other files. The construction of this executable program requires skills in gateway programming. Java adds still another level of interactivity. Instead of the server computing a result, the Java-enabled browser is the mechanism for computation. FIGURE 3.3. Web selectivity and gateway interactivity.
Summary The Web emerged from ideas about the associative, nonlinear organization of information. Java is another step in this evolution. The Web is a hypertext information and communication system popularly used on the Internet in a client/server model, offering hypermedia display capabilities through appropriate browsers, some of which require helper applications. Java-enabled browsers bring client-side interactivity and computation to the Web and can eliminate the need for helper applications. Communication on the Web can assume many forms and take place in many contexts, ranging from individual communication to group and mass communication. Java can potentially augment the Web’s communication contexts and functions.
Java Unleashed
Page 26
Chapter 4 Java Animates Web Pages The Java language and Java-enabled browsers allow a more visually dynamic Web than possible before. Instead of hypertext pages containing only still images with helper applications to display video, Java Web pages can include animated graphics, text, and any moving visual elements a Java programmer can dream up. This chapter surveys several Java applets that implement animation. In some cases, the chapter also includes key portions of the source code to demonstrate how these applets are made. If you want to understand these code portions in more detail, you can read more about Java programming basics in later parts of this book. If not, you can skip over the programming sections for now and return to them later. If you’d like to try out the applets described here, you should be familiar with Java’s connection with HTML as described in Chapter 2. The purpose of this chapter is to familiarize you with the many types of animation possible using applets. If you are ready to place applets on your Web pages, this chapter will also be invaluable to you; it contains instructions for including some publicly available demonstration applets that you can customize and include on a hypertext page. *
*A TREASURE TROVE OF JAVA APPLETS Visit the Gamelan web site at http://www.gamelan.com/ to connect to a well-organized, frequently updated, and very comprehensive registry of Java applets, demonstrations, and documentation. This collection includes pointers to many of the demonstrations discussed in this book.
Applets in Motion If you are a new user of a Java-enabled browser, you will immediately notice that some Java pages contain moving text, figures, and animations. These moving images are made possible by Java applets that implement Java’s Runnable interface. These applets don’t just display static text or graphics; they can execute their content continuously.
NervousText One example of animated text is the NervousText applet. NervousText was originally developed by Daniel Wyszynski at the Center for Applied Large-Scale Computing. Wyszynski’s NervousText applet displays HotJava! in jostling on-screen letters. David Leach modified this applet so that it can display any programmer-defined string. Figure 4.1 shows both Wyszynski’s and David Leach’s NervousText applets on a Web page. FIGURE 4.1. The Nervous Text applet. The NervousText applet is a good demonstration of how an applet can be included on any Web page, not just Web pages created by the applet’s developer. You are not limited to using only applets that you write. You can modify and use other developer’s applets from their sites, just as you link to hypertext pages at other sites. In fact, sharing applets across the Net is what Java’s capability to distribute executable content is all about. You use the APPLET tag in HTML to place a publicly available applet in a Web page. The Codebase attribute identifies the path (using a Uniform Resource Locator, or URL) to a Java class anywhere on a publicly available server on the Net. The Code attribute then specifies the applet’s class name. In general, the APPLET tag on an HTML page works like this:
Java Unleashed
Page 27
<APPLET Codebase = “path (URL) of directory containing class files” Code = “name of class file” Width = “width of applet in pixels” Height = “height of applet in pixels”> In Figure 4.1, Leach’s modification uses a parameter called msg to set the value of the message that the applet displays. You can include a beta version of a NervousText applet in your page like this: <APPLET Codebase=”http://www.javasoft.com/JDK-prebeta1/applets/NervousText/” Code=”NervousText.class” Width=”200" Height=”50"> Note that the parameters use the PARAM tag in HTML, and that these parameter tags occur between the opening <APPLET> tag and closing tag. When the Java-enabled browser reads the PARAM attributes Name and Value, it passes these values to the applet. *
*USING JAVA APPLETS WITHOUT JAVA You can put together a Web page that includes applets you didn’t create or at any location using the APPLET element. You don’t have to have a Java-enabled browser or the Java compiler to serve applets. You need only a reference to the class file of the applet. If you use applets that are at remote locations, you need to identify where on the Net the class file for the applet exists. To do so, use the Codebase attribute of the APPLET tag. Of course, users who do not have a Java-enabled browser cannot observe the applets. If you use a remote applet in this way, consider downloading and serving a copy of the class file from your own site. Before taking this step, however, check with the information provider. And, of course, check out the applet’s behavior—it is executable content and runs on the computer of anyone requesting to view it. David Leach’s modification of NervousText demonstrates the programming technique of passing values to the applet with parameters. In the Java applet code, David uses the getAttribute method to find out the value of the parameter msg passed from the HTML tags to the Java applet. Leach’s class definition includes the data string userString; and the init() method includes this line: userString = getAttribute(“msg”); David uses this string in the paint() method to derive the characters that draw the applet. The trick of making the letters “nervous” is to vary their coordinates in the paint() method by using a random number generator for their X and Y coordinates: x_coord = (int) (Math.random()*10+15*i); y_coord = (int) (Math.random()*10+36);
TickerTape Similar to the NervousText applet, another good demonstration of Java’s animation capabilities is TickerTape. This applet was originally developed by Sven Heinicke at HotWired and later modified by David Leach and John Stone at the University of MissouriRolla. Many others have subsequently created variations on the TickerTape applet. *
Java Unleashed
Page 28 *ARE JAVA USERS WASTING BANDWIDTH?
After a Java applet’s bytecodes have been downloaded across the network, the user’s host is the processor that interpets them. The information provider’s host works only to distribute the bytecodes. Users of applets, therefore, might typically use far less bandwidth and far less time on the information provider’s computer than might Web surfers. Also, class files containing bytecodes aren’t all that large. For example, the TickerTape applet (see Figure 4.2) is 3,186 bytes—easily smaller than many graphics files routinely downloaded from the HotWired server. Therefore, although users may see more action with applets, they are not necessarily using more bandwidth on the Web. Of course, leaving a browser on autopilot (such as in the Surf-o-Matic applet in Chapter 6) and walking away would cause a browser to use much bandwidth for downloading Web pages. Information providers must be very careful about the size and processing power required by their applets; a CPU-intensive applet could bring the user’s computer to its knees. Figure 4.2 shows the display of the TickerTape applet. The text in the lines scrolls continuously to the left; with the bottom ticker line moving very rapidly. FIGURE 4.2. TickerTape applet eaxample. The TickerTape applet uses a key programming trick to cause the letters to move. The code changes the X position of the string by an amount equal to the speed attribute prior to repainting the string in each cycle. Here’s the code to do this: xpos -= speed; This line of code subtracts the value of speed from the current horizontal position of the string. The line is a quick way of writing the equivalent xpos = xpos - speed. You can include a beta version of a more elaborate kind of ticker tape on a Web page like this: <APPLET Codebase = “http://www.digitalfocus.com/digitalfocus/faq/” Code = “reloadImage.class” Width=”600" Height=”70"> >>> < > =
Java Unleashed
Page 82
== != & ^ && || ?: = In this list of operators, all of the operators in a particular row have equal precedence. The precedence level of each row decreases from top to bottom. This means that the [] operator has a higher precedence than the * operator, but the same precedence as the () operator. Expression evaluation still moves from right to left, but only when dealing with operators that have the same precedence. Otherwise, operators with a higher precedence are evaluated before operators with a lower precedence. Knowing this, take a look at the same equation again: x = 2 * 5 + 12 / 4 Before using the right-to-left evaluation of the expression, first look to see if any of the operators have differing precedence. Indeed they do! The multiplication (*) and division (/) operators both have the highest precedence, followed by the addition operator (+), and then theassignment operator (=). Because the multiplication and division operators share the same precedence, evaluate them from right to left. Doing this, you perform the division operation 12 / 4 first, resulting in 3. You then perform the multiplication operation 2 * 5, which results in 10. After performing these two operations, the expression looks like this: x = 10 + 3; Because the addition operator has a higher precedence than the assignment operator, you perform the addition 10 + 3 next, resulting in 13. Finally, the assignment operation x = 13 is processed, resulting in the number 13 being assigned to the variable x. As you can see, evaluating the expression using operator precedence yields a completely different result. Just to get the point across, take a look at another expression that uses parentheses for grouping purposes: x = 2 * (11 - 7); Without the grouping parentheses, you would perform the multiplication first and then the subtraction. However, referring back to the precedence list, the () operator comes before all other operators. So, the subtraction 11 - 7 is performed first, yielding 4 and the following expression: x = 2 * 4; The rest of the expression is easily resolved with a multiplication and an assignment to yield a result of 8 in the variable x.
Integer Operators There are three types of operations that can be performed on integers: unary, binary, and relational. Unary operators act only on single integer numbers, and binary operators act on pairs of integer numbers. Both unary and binary integer operators return integer results. Relational operators, on the other hand, act on two integer numbers but return a Boolean result rather than an integer. Unary and binary integer operators typically return an int type. For all operations involving the types byte, short, and int, the result is always an int. The only exception to this rule is if one of the operands is a long, in which case the result of the operation also will be of type long.
Java Unleashed
Page 83
Unary Unary integer operators act on a single integer. Table 13.1 lists the unary integer operators. Table 13.1. The unary integer operators.*
*Description Increment .Decrement Negation Bitwise complement
*Operator ++ -~
The increment and decrement operators (++ and --) increase and decrease integer variables by one. Similar to their complements in C and C++, these operators can be used in either prefix or postfix form. A prefix operator takes effect prior to the evaluation of the expression it is in, and a postfix operator takes effect after the expression has been evaluated. Prefix unary operators are placed immediately before the variable and postfix unary operators are placed immediately following the variable. Following is an example of each type of operator: y = ++x; z = x--; In the first example, x is prefix incremented, which means that it is incremented before being assigned to y. In the second example, x is postfix decremented, which means that it is decremented after being assigned to z. In the latter case, z is assigned the value of x prior to x being decremented. Listing 13.1 contains the IncDec program, which uses both types of operators. Please note that the IncDec program is actually implemented in the Java class IncDec. This is a result of the object-oriented structure of Java, which requires programs to be implemented as classes. So, when you see a reference to a Java program, keep in mind that it is really referring to a Java class.
Listing 13.1. The IncDec class. class IncDec { public static void main (String args[]) { int x = 8, y = 13; System.out.println(“x = “ + x); System.out.println(“y = “ + y); System.out.println(“++x = “ + ++x); System.out.println(“y++ = “ + y++); System.out.println(“x = “ + x); System.out.println(“y = “ + y); } }
The IncDec program produces the following results: x = y = ++x y++ x = y =
8 13 = 9 = 13 9 14
The negation unary integer operator (-) is used to change the sign of an integer value. This operator is as simple as it sounds, as indicated by the following example:
Java Unleashed
Page 84
x = 8; y = -x; In this example, x is assigned the literal value 8 and then is negated and assigned to y. The resulting value of y is -8. To see this code in a real Java program, check out the Negation program in Listing 13.2.
Listing 13.2. The Negation class. class Negation { public static void main (String args[]) { int x = 8; System.out.println(“x = “ + x); int y = -x; System.out.println(“y = “ + y); } }
The last Java unary integer operator is the bitwise complement operator (~), which performs a bitwise negation of an integer value. Bitwise negation means that each bit in the number is toggled. In other words, all of the binary zeros become ones and all the binary ones become zeros. Take a look at an example very similar to the one for the negation operator: x = 8; y = ~x; In this example x is assigned the literal value 8 again, but it is bitwise complemented before being assigned to y. What does this mean? Well, without getting into the details of how integers are stored in memory, it means that all of the bits of the variable x are flipped, yielding a decimal result of -9. This result has to do with the fact that negative numbers are stored in memory using a method known as two’s complement (see the following note). If you’re having trouble believing any of this, try it yourself with the BitwiseComplement program shown in Listing 13.3. *
*NOTE Integer numbers are stored in memory as a series of binary bits that can each have a value of 0 or 1. A number is considered negative if the highest-order bit in the number is set to 1. Because a bitwise complement flips all the bits in a number, including the high-order bit, the sign of a number is reversed. Listing 13.3. The BitwiseComplement class. class BitwiseComplement { public static void main (String args[]) { int x = 8; System.out.println(“x = “ + x); int y = ~x; System.out.println(“y = “ + y); } }
Binary Binary integer operators act on pairs of integers. Table 13.2 lists the binary integer operators. Table 13.2. The binary integer operators.*
*Description Addition
*Operator +
Java Unleashed
Page 85
Subtraction Multiplication Division Modulus Bitwise AND Bitwise OR Bitwise XOR Left Shift Right Shift Zero-Fill Right Shift
* / % & | ^ > >>>
The addition, subtraction, multiplication, and division operators (+, -, *, and /) all do what you would expect them to. An important thing to note is how the division operator works; because you are dealing with integer operands, the division operator returns an integer divisor. In cases where the division results in a remainder, the modulus operator (%) can be used to get the remainder value. Listing 13.4 contains the Arithmetic program, which shows how the basic binary integer arithmetic operators work.
Listing 13.4. The Arithmetic class. class Arithmetic { public static void main int x = 17, y = 5; System.out.println(“x System.out.println(“y System.out.println(“x System.out.println(“x System.out.println(“x System.out.println(“x System.out.println(“x } }
(String args[]) { = = + * / %
“ “ y y y y y
+ + = = = = =
x); y); “ + “ + “ + “ + “ +
(x (x (x (x (x
+ * / %
y)); y)); y)); y)); y));
The results of running the Arithmetic program follow: x y x x x x x
= = + * / %
17 5 y = y = y = y = y =
22 12 85 3 2
These results shouldn’t surprise you too much. Just notice that the division operation x / y, which boils down to 17 / 5, yields the result 3. Also notice that the modulus operation x % y, which is resolved down to 17 % 5, ends up with a result of 2, which is the remainder of the integer division. Mathematically, a division by zero results in an infinite result. Because representing infinite numbers is a big problem for computers, division or modulus operations by zero result in an error. To be more specific, a runtime exception is thrown. You’ll learn a lot more about exceptions in Chapter 16, “Exception Handling.” The bitwise AND, OR, and XOR operators (&, |, and ^) all act on the individual bits of an integer. These operators sometimes are useful when an integer is being used as a bit field. An example of this is when an integer is used to represent a group of binary flags. An int is capable of representing up to 32 different flags, because it is stored in 32 bits. Listing 13.5 contains the program Bitwise, which shows how to use the binary bitwise integer operators.
Listing 13.5. The Bitwise class.
Java Unleashed class Bitwise { public static void main int x = 5, y = 6; System.out.println(“x System.out.println(“y System.out.println(“x System.out.println(“x System.out.println(“x } }
Page 86
(String args[]) { = = & | ^
“ “ y y y
+ + = = =
x); y); “ + (x & y)); “ + (x | y)); “ + (x ^ y));
The output of running Bitwise follows: x y x x x
= = & | ^
5 6 y = 4 y = 7 y = 3
To understand this output, you must first understand the binary equivalents of each decimal number. In Bitwise, the variables x and y are set to 5 and 6, which correspond to the binary numbers 0101 and 0110. The bitwise AND operation compares each bit of each number to see if they are the same. It then sets the resulting bit to 1 if both bits being compared are 1, and 0 otherwise. The result of the bitwise AND operation on these two numbers is 0100 in binary, or decimal 4. The same logic is used for both of the other operators, except that the rules for comparing the bits are different. The bitwise OR operator sets the resulting bit to 1 if either of the bits being compared is 1. For these numbers, the result is 0111 binary, or 7 decimal. Finally, the bitwise XOR operator sets resulting bits to 1 if exactly one of the bits being compared is 1, and 0 otherwise. For these numbers, the result is 0011 binary, or 3 decimal. The left-shift, right-shift, and zero-fill-right-shift operators (, and >>>) shift the individual bits of an integer by a specified integer amount. The following are some examples of how these operators are used: x > 7; z >>> 2; In the first example, the individual bits of the integer variable x are shifted to the left three places. In the second example, the bits of y are shifted to the right seven places. Finally, the third example shows z being shifted to the right two places, with zeros shifted into the two leftmost places. To see the shift operators in a real program, check out Shift in Listing 13.6.
Listing 13.6. The Shift class. class Shift { public static void main int x = 7; System.out.println(“x System.out.println(“x System.out.println(“x System.out.println(“x } }
The output of Shift follows:
(String args[]) { = “ + x); >> 2 = “ + (x >> 2)); > 1 = “ + (x >>> 1));
Java Unleashed x x x x
Page 87
= 7 >> 2 = 1 >> 1 = 3
The number being shifted in this case is the decimal 7, which is represented in binary as 0111. The first right-shift operation shifts the bits two places to the right, resulting in the binary number 0001, or decimal 1. The next operation, a left shift, shifts the bits one place to the left, resulting in the binary number 1110, or decimal 14. Finally, the last operation is a zero-fill right shift, which shifts the bits 1 place to the right, resulting in the binary number 0011, or decimal 3. Pretty simple, huh? And you probably thought it was difficult working with integers at the bit level! Based on these examples, you may be wondering what the difference is between the right-shift (>>) and zero-fill-right-shift operators (>>>). The right-shift operator appears to shift zeros into the leftmost bits, just like the zero-fill-right-shift operator, right? Well, when dealing with positive numbers, there is no difference between the two operators; they both shift zeros into the upper bits of a number. The difference arises when you start shifting negative numbers. Remember that negative numbers have the high-order bit set to 1. The right-shift operator preserves the high-order bit and effectively shifts the lower 31 bits to the right. This behavior yields results for negative numbers similar to those for positive numbers. That is, -8 shifted right by one will result in -4. The zero-fill-right-shift operator, on the other hand, shifts zeros into all the upper bits, including the high-order bit. When this shifting is applied to negative numbers, the high-order bit becomes 0 and the number becomes positive.
Relational The last group of integer operators is the relational operators, which all operate on integers but return a type boolean. Table 13.3 lists the relational integer operators. Table 13.3. The relational integer operators.*
*Description Less Than Greater Than Less Than Or Equal To Greater Than Or Equal To Equal To Not Equal To
*Operator < > = == !=
These operators all perform comparisons between integers. Listing 13.7 contains the Relational program, which demonstrates the use of the relational operators with integers.
Listing 13.7. The Relational class. class Relational { public static void main (String args[]) { int x = 7, y = 11, z = 11; System.out.println(“x = “ + x); System.out.println(“y = “ + y); System.out.println(“z = “ + z); System.out.println(“x < y = “ + (x < y)); System.out.println(“x > z = “ + (x > z)); System.out.println(“y = y)); System.out.println(“y == z = “ + (y == z)); System.out.println(“x != y = “ + (x != z)); } }
The output of running Relational follows:
Java Unleashed x y z x x y x y x
Page 88
= 7 = 11 = 11 < y = true > z = false = y = false == z = true != y = true
As you can see, the println method is smart enough to print Boolean results correctly as true and false.
Floating-Point Operators Similar to integer operators, there are three types of operations that can be performed onfloating-point numbers: unary, binary, and relational. Unary operators act only on single floating-point numbers, and binary operators act on pairs of floating-point numbers. Both unary and binary floating-point operators return floating-point results. Relational operators, however, act on two floating-point numbers but return a Boolean result. Unary and binary floating-point operators return a float type if both operands are of type float. If one or both of the operands is of type double, however, the result of the operation is of type double.
Unary The unary floating point operators act on a single floating-point number. Table 13.4 lists the unary floating-point operators. Table 13.4. The unary floating-point operators.*
*
*Operator
Description Increment Decrement
++ --
As you can see, the only two unary floating point operators are the increment and decrement operators. These two operators respectively add and subtract 1.0 from their floating-point operand.
Binary The binary floating-point operators act on a pair of floating-point numbers. Table 13.5 lists the binary floating-point operators. Table 13.5. The binary floating-point operators.*
*
*Operator
Description Addition Subtraction Multiplication Division Modulus
+ * / %
The binary floating-point operators consist of the four traditional binary operations (+, -, *, ), along with the modulus operator (%). You might be wondering how the modulus operator fits in here, considering that its usage as an integer operator relied on an integer division. If you recall, the integer modulus operator returned the remainder of an integer division of the two operands. But a floatingpoint division never results in a remainder, so what does a floating-point modulus do? The floating-point modulus operator returns the floating-point equivalent of an integer division. What this means is that the division is carried out with both floating-point operands, but the resulting divisor is treated as an integer, resulting in a floating-point remainder. Listing 13.8 contains the FloatMath program, which shows how the floating-point modulus operator works along with the other binary floating-point operators.
Java Unleashed
Page 89
Listing 13.8. The FloatMath class. class FloatMath { public static void main (String args[]) { float x = 23.5F, y = 7.3F; System.out.println(“x = “ + x); System.out.println(“y = “ + y); System.out.println(“x + y = “ + (x + y)); System.out.println(“x - y = “ + (x - y)); System.out.println(“x * y = “ + (x * y)); System.out.println(“x / y = “ + (x / y)); System.out.println(“x % y = “ + (x % y)); } }
The output of FloatMath follows: x y x x x x x
= = + * / %
23.5 7.3 y = 30.8 y = 16.2 y = 171.55 y = 3.21918 y = 1.6
The first four operations no doubt performed as you expected, taking the two floating-point operands and yielding a floating-point result. The final modulus operation determined that 7.3 divides into 23.5 an integral amount of 3 times, leaving a remaining result of 1.6.
Relational The relational floating-point operators compare two floating-point operands, leaving a Boolean result. The floating-point relational operators are the same as the integer relational operators listed in Table 13.3, except that they work on floating-point numbers.
Boolean Operators Boolean operators act on Boolean types and return a Boolean result. The Boolean operators are listed in Table 13.6. Table 13.6. The Boolean operators.*
*
*Operator
Description Evaluation AND Evaluation OR Evaluation XOR Logical AND Logical OR Negation Equal To Not Equal To Conditional
& | ^ && D="I228" NAME="I228"> || ! == != ?:
The evaluation operators (&, |, and ^) evaluate both sides of an expression before determining the result. The logical operators (&& and ||) avoid the right-side evaluation of the expression if it is not needed. To better understand the difference between these operators, take a look at the following two expressions:
Java Unleashed
Page 90
boolean result = isValid & (Count > 10); boolean result = isValid && (Count > 10); The first expression uses the evaluation AND operator (&) to make an assignment. In this case, both sides of the expression always are evaluated, regardless of the values of the variables involved. In the second example, the logical AND operator (&&) is used. This time, the isValid Boolean value is first checked. If it is false, the right side of the expression is ignored and the assignment is made. This is more efficient because a false value on the left side of the expression provides enough information to determine the false outcome. Although the logical operators are more efficient, there still may be times when you want to use the evaluation operators to ensure that the entire expression is evaluated. The following code shows how the evaluation AND operator is necessary for the complete evaluation of an expression: while ((++x < 10) && (++y < 15)) { System.out.println(x); System.out.println(y); } In this example, the second expression (++y > 15) is evaluated after the last pass through the loop because of the evaluation AND operator. If the logical AND operator had been used, the second expression would not have been evaluated and y would not have been incremented after the last time around. The Boolean operators negation, equal-to, and not-equal-to (!, ==, and !=) perform exactly as you might expect. The negation operator toggles the value of a Boolean from false to true or from true to false, depending on the original value. The equal-to operator simply determines whether two Boolean values are equal (both true or both false). Similarly, the not-equal-to operator determines whether two Boolean operands are unequal. The conditional Boolean operator (?:) is the most unique of the Boolean operators, and is worth a closer look. This operator also is known as the ternary operator because it takes three items: a condition and two expressions. The syntax for the conditional operator follows: Condition ? Expression1 : Expression2 The Condition, which itself is a Boolean, is first evaluated to determine whether it is true or false. If Condition evaluates into a true result, Expression1 is evaluated. If Condition ends up being false, Expression2 is evaluated. To get a better feel for the conditional operator, check out the Conditional program in Listing 13.9.
Listing 13.9. The Conditional class. class Conditional { public static void main (String args[]) { int x = 0; boolean isEven = false; System.out.println(“x = “ + x); x = isEven ? 4 : 7; System.out.println(“x = “ + x); } }
The results of the Conditional program follow: x = 0 x = 7
Java Unleashed
Page 91
The integer variable x is first assigned a value of 0. The Boolean variable isEven is assigned a value of false. Using the conditional operator, the value of isEven is checked. Because it is false, the second expression of the conditional is used, which results in the value 7 being assigned to x.
String Operators Along with integers, floating-point numbers, and Booleans, strings also can be manipulated with operators. Actually, there is only one string operator: the concatenation operator (+). The concatenation operator for strings works very similarly to the addition operator for numbers—it adds strings together. The concatenation operator is demonstrated in the Concatenation program shown in Listing 13.10.
Listing 13.10. The Concatenation class. class Concatenation { public static void main (String args[]) { String firstHalf = “What “ + “did “; String secondHalf = “you “ + “say?”; System.out.println(firstHalf + secondHalf); } }
The output of Concatenation follows: What did you say? In the Concatenation program, literal strings are concatenated to make assignments to the two string variables, firstHalf and secondHalf, upon creation. The two string variables are then concatenated within the call to the println method.
Assignment Operators One final group of operators that you haven’t seen yet is the assignment operators. Assignment operators actually work with all of the fundamental data types. Table 13.7 lists the assignment operators. Table 13.7. The assignment operators.*
*
*Operator
Description Simple Addition Subtraction Multiplication Division Modulus AND OR XOR
= += -= *= /= %= &= |= ^=
With the exception of the simple assignment operator (=), the assignment operators function exactly like their nonassignment counterparts, except that the resulting value is stored in the operand on the left side of the expression. Take a look at the following examples: x += 6; x *= (y - 3);
Java Unleashed
Page 92
In the first example, x and 6 are added and the result stored in x. In the second example, 3 is subtracted from y and the result multiplied by x. The final result is then stored in x.
Control Structures Although performing operations on data is very useful, it’s time to move on to the issue of program flow control. The flow of your programs is dictated by two different types of constructs: branches and loops. Branches enable you to selectively execute one part of a program instead of another. Loops, on the other hand, provide a means to repeat certain parts of a program. Together, branches and loops provide you with a powerful means to control the logic and execution of your code.
Branches Without branches or loops, Java code executes in a sequential fashion, as shown in Figure 13.1. In Figure 13.1, each statement is executed sequentially. What if you don’t always want every single statement executed? Then you use a branch. Figure 13.2 shows how a conditional branch gives the flow of your code more options. FIGURE 13.1. A program executing sequentially. FIGURE 13.2. A program executing with a branch. By adding a branch, you’ve given the code two optional routes to take, based on the result of the conditional expression. The concept of branches might seem trivial, but it would be difficult if not impossible to write useful programs without them. Java supports two types of branches: if-else branches and switch branches.
if-else The if-else branch is the most commonly used branch in Java programming. It is used to select conditionally one of two possible outcomes. The syntax for the if-else statement follows: if (Condition) Statement1 else Statement2 If the Boolean Condition evaluates to true, Statement1 is executed. Likewise, if the Condition evaluates to false, Statement2 is executed. The following example should make it a little more clear: if (isTired) timeToEat = true; else timeToEat = false; If the Boolean variable isTired is true, the first statement is executed and timeToEat is set to true. Otherwise, the second statement is executed and timeToEat is set to false. You might have noticed that the if-else branch works very similarly to the conditional operator (?:) you saw earlier. In fact, you can think of the if-else branch as an expanded version of the conditional operator. One significant difference between the two is that you can include compound statements in an if-else branch. *
*NOTE Compound statements are blocks of code surrounded by curly braces {} that appear as a single, or simple, statement to an outer block of code. If you have only a single statement that you need to execute conditionally, you can leave off the else part of the branch, as shown in the following example: if (isThirsty) pourADrink = true;
Java Unleashed
Page 93
On the other hand, if you need more than two conditional outcomes, you can string together a series of if-else branches to get the desired effect. The following example shows multiple if-else branches used to switch between different outcomes: if (x == 0) y = 5; else if (x == 2) y = 25; else if (x >= 3) y = 125; In this example, three different comparisons are made, each with its own statement that is executed upon a true conditional result. Notice, however, that subsequent if-else branches are in effect nested within the prior branch. This ensures that at most one statement is executed. The last important topic to cover in regard to if-else branches is compound statements. As mentioned earlier, a compound statement is a block of code surrounded by curly braces that appears to an outer block as a single statement. The following is an example of a compound statement used with an if branch: if (performCalc) { x += y * 5; y -= 10; z = (x - 3) / y; } Sometimes, when nesting if-else branches, it is necessary to use curly braces to distinguish which statements go with which branch. The following example illustrates the problem: if (x != 0) if (y < 10) z = 5; else z = 7; In this example, the style of indentation indicates that the else branch belongs to the first (outer) if. However, because there was no grouping specified, the Java compiler assumes that the else goes with the inner if. To get the desired results, you need to modify the code as follows: if (x != 0) { if (y < 10) z = 5; } else z = 7; The addition of the curly braces tells the compiler that the inner if is part of a compound statement, and more importantly, it completely hides the else branch from the inner if. Based on what you learned from the discussion of blocks and scope in the last chapter, you can see that code within the inner if has no way of accessing code outside its scope, including the else branch. Listing 13.11 contains the source code for the IfElseName class, which uses a lot of what you’ve learned so far.
Listing 13.11. The IfElseName class.
Java Unleashed
Page 94
class IfElseName { public static void main (String args[]) { char firstInitial = (char)-1; System.out.println(“Enter your first initial:”); try { firstInitial = (char)System.in.read(); } catch (Exception e) { System.out.println(“Error: “ + e.toString()); } if (firstInitial == -1) System.out.println(“Now what kind of name is that?”); else if (firstInitial == ‘j’) System.out.println(“Your name must be Jules!”); else if (firstInitial == ‘v’) System.out.println(“Your name must be Vincent!”); else if (firstInitial == ‘z’) System.out.println(“Your name must be Zed!”); else System.out.println(“I can’t figure out your name!”); }
When typing the letter v in response to the input message, IfElseName yields the following results: Your name must be Vincent! The first thing in IfElseName you probably are wondering about is the read method. The read method simply reads a character from the standard input stream (System.in), which is typically the keyboard. Notice that a cast is used because read returns an int type. Once the input character has been successfully retrieved, a succession of if-else branches are used to determine the proper output. If there are no matches, the final else branch is executed, which notifies users that their names could not be determined. Notice that the value read is checked to see if it is equal to –1. The read method returns –1 if it has reached the end of the input stream. *
*NOTE You may have noticed that the call to the read method in IfElseName is enclosed within a try-catch clause. The try-catch clause is part of Java’s support for exception handling and is used in this case to trap errors encountered while reading input from the user. You’ll learn more about exceptions and the try-catch clause in Chapter 16, “Exception Handling.” Switch Similar to the if-else branch, the switch branch specifically is designed to conditionally switch among multiple outcomes. The syntax for the switch statement follows: switch (Expression) { case Constant1: StatementList1 case Constant2: StatementList2 … default: DefaultStatementList } The switch branch evaluates and compares Expression to all of the case constants and branches the program’s execution to the matching case statement list. If no case constants match Expression, the program branches to the DefaultStatementList, if one has been supplied (the DefaultStatementList is optional). You might be wondering what a statement list is. A statement list is simply a
Java Unleashed
Page 95
series, or list, of statements. Unlike the if-else branch, which directs program flow to a simple or compound statement, the switch branch directs the flow to a list of statements. When the program execution moves into a case statement list, it continues from there in a sequential manner. To better understand this, take a look at Listing 13.12, which contains a switch version of the name program that you developed earlier with if-else branches.
Listing 13.12. The SwitchName1 class. class SwitchName1 { public static void main (String args[]) { char firstInitial = (char)-1; System.out.println(“Enter your first initial:”); try { firstInitial = (char)System.in.read(); } catch (Exception e) { System.out.println(“Error: “ + e.toString()); } switch(firstInitial) { case (char)-1: System.out.println(“Now what kind of name is that?”); case ‘j’: System.out.println(“Your name must be Jules!”); case ‘v’: System.out.println(“Your name must be Vincent!”); case ‘z’: System.out.println(“Your name must be Zed!”); default: System.out.println(“I can’t figure out your name!”); } } }
When typing the letter v in response to the input message, SwitchName1 produces the following results: Your name must be Vincent! Your name must be Zed! I can’t figure out your name! Hey, what’s going on here? That output definitely does not look right. The problem lies in the way the switch branch controls program flow. The switch branch matched the v entered with the correct case statement, as shown in the first string printed. However, the program continued executing all of the case statements from that point onward, which is not what you wanted. The solution to the problem lies in the break statement. The break statement forces a program to break out of the block of code it is currently executing. Check out the new version of the program in Listing 13.13, with break statements added where appropriate.
Listing 13.13. The SwitchName2 class.
Java Unleashed
Page 96
class SwitchName2 { public static void main (String args[]) { char firstInitial = (char)-1; System.out.println(“Enter your first initial:”); try { firstInitial = (char)System.in.read(); } catch (Exception e) { System.out.println(“Error: “ + e.toString()); } switch(firstInitial) { case (char)-1: System.out.println(“Now what kind of name is that?”); break; case ‘j’: System.out.println(“Your name must be Jules!”); break; case ‘v’: System.out.println(“Your name must be Vincent!”); break; case ‘z’: System.out.println(“Your name must be Zed!”); break; default: System.out.println(“I can’t figure out your name!”); } } }
When you run SwitchName2 and enter v, you get the following output: Your name must be Vincent! That’s a lot better! You can see that placing break statements after each case statement kept the program from falling through to the next case statements. Although you will use break statements in this manner the majority of the time, there might still be some situations where you will want a case statement to fall through to the next one.
Loops When it comes to program flow, branches really only tell half of the story; loops tell the other half. Put simply, loops enable you to execute code repeatedly. There are three types of loops in Java: for loops, while loops, and do-while loops. Just as branches alter the sequential flow of programs, so do loops. Figure 13.3 shows how a loop alters the sequential flow of a Java program. FIGURE 13.3. A program executing with a loop.
for The for loop provides a means to repeat a section of code a designated number of times. The for loop is structured so that a section of code is repeated until some limit has been reached. The syntax for the for statement follows: for (InitializationExpression; LoopCondition; StepExpression) Statement The for loop repeats the Statement the number of times that is determined by the InitializationExpression, the LoopCondition, and the StepExpression. The InitializationExpression is used to initialize a loop control variable. The LoopCondition compares the loop
Java Unleashed
Page 97
control variable to some limit value. Finally, the StepExpression specifies how the loop control variable should be modified before the next iteration of the loop. The following example illustrates how a for loop can be used to print the numbers from one to ten: for (int i = 1; i < 11; i++) System.out.println(i); First, i is declared as an integer. The fact that i is declared within the body of the for loop might look strange to you at this point. Don’t despair—this is completely legal. i is initialized to 1 in the InitializationExpression part of the for loop. Next, the conditional expressioni < 11 is evaluated to see if the loop should continue. At this point, i is still equal to 1, so LoopCondition evaluates to true and the Statement is executed (the value of i is printed to standard output). i is then incremented in the StepExpression part of the for loop, and the process repeats with the evaluation of LoopCondition again. This continues until LoopCondition evaluates to false, which is when x equals 11 (ten iterations later). Listing 13.14 shows the ForCount program, which shows how to use a for loop to count a user-entered amount of numbers.
Listing 13.14. The ForCount class. class ForCount { public static void main (String args[]) { char input = (char)-1; int numToCount; System.out.println(“Enter a number to count to between 0 and 10:”); try { input = (char)System.in.read(); } catch (Exception e) { System.out.println(“Error: “ + e.toString()); } numToCount = Character.digit(input, 10); if ((numToCount > 0) && (numToCount < 10)) { for (int i = 1; i 0) && (numToCount < 10)) { int i = 1; while (i 0) { . . . crucialValue += 1; }
// think about what to do
Remember that any number of threads may be calling upon this part of the system at once. The disaster occurs when two threads have both executed the if test before either has incremented the crucialValue. In that case, the value is clobbered by them both with the same crucialValue + 1, and one of the increments has been lost. This may not seem so bad to you, but imagine instead that the crucial value affects the state of the screen as it is being displayed. Now, unfortunate ordering of the threads can cause the screen to be updated incorrectly. In the same way, mouse or keyboard events can be lost, databases can be inaccurately updated, and so forth. This disaster is inescapable if any significant part of the system has not been written with threads in mind. Therein lies the barrier to a mainstream threaded environment—the large effort required to rewrite existing libraries for thread safety. Luckily, Java was written from scratch with this is mind, and every Java class in its library is thread-safe. Thus, you now have to worry only about your own synchronization and thread-ordering problems, because you can assume that the Java system will do the right thing.*
*NOTE Some readers may wonder what the fundamental problem really is. Can’t you just make the . . . area in the example smaller and smaller to reduce or eliminate the problem? Without atomic operations, the answer is no. (Atomic operations are a series of instructions that cannot be interrupted by another thread. They can be thought of as operations that appear to happen “all at once.”) Even if the . . . took zero time, you must first look at the value of some variable to make any decision and then change something to reflect that decision. These two steps can never be made to happen at the same time without an atomic operation. Unless you’re given one by the system, it’s literally impossible to create your own. Even the one line crucialValue += 1 involves three steps: Get the current value, add one to it, and store it back. (Using ++crucialValue doesn’t help either.) All three steps need to happen “all at once” (atomically) to be safe. Special Java primitives, at the lowest levels of the language, provide you with the basic atomic operations you need to build safe, threaded programs.
Thinking Multithreaded Getting used to threads takes a little while and a new way of thinking. Rather than imagining that you always know exactly what’s happening when you look at a method you’ve written, you have to ask yourself some additional questions. What will happen if more
Java Unleashed
Page 120
than one thread calls into this method at the same time? Do you need to protect it in some way? What about your class as a whole? Are you assuming that only one of its methods is running at the same time? Often you make such assumptions, and a local instance variable will be messed up as a result. Let’s make a few mistakes and then try to correct them. First, the simplest case: public class ThreadCounter { int crucialValue; public void countMe() { crucialValue += 1; } public int howMany() { return crucialValue; } } This code suffers from the most pure form of the “synchronization problem:” the += takes more than one step, and you may miscount the number of threads as a result. (Don’t be too concerned about the specifics of how threads are created yet; just imagine that a whole bunch of them are able to call countMe(), at once, at slightly different times.) Java enables you to fix this: public class SafeThreadCounter { int crucialValue; public synchronized void countMe() { crucialValue += 1; } public int howMany() { return crucialValue; } } The synchronized keyword tells Java to make the block of code in the method thread-safe. Only one thread will be allowed inside this method at once, and others have to wait until the currently running thread is finished with it before they can begin running it. This implies that synchronizing a large, long-running method is almost always a bad idea. All your threads would end up stuck at this bottleneck, waiting single file to get their turn at this one slow method. It’s even worse than you might think for unsynchronized variables. Because the compiler can keep them around in registers during computations, and a thread’s registers can’t be seen by other threads (especially if they’re on another processor in a true multiprocessor computer), a variable can be updated in such a way that no possible order of thread updates could haveproduced the result. This is completely incomprehensible to the programmer. To avoid this bizarre case, you can label a variable volatile, meaning that you know it will be updated asynchronously by multiprocessor-like threads. Java then loads and stores it each time it’s needed and does not use registers.*
*NOTE In earlier releases, variables that were safe from these bizarre effects were labeled threadsafe. Because most variables are safe to use, however, they are now assumed to be thread-safe unless you mark them volatile. Using volatile is an extremely rare event. In fact, in the 1.0 release, the Java class library does not use volatile anywhere.
Points About Points The method howMany() in the last example doesn’t need to be synchronized, because it simply returns the current value of an instance variable. Someone higher in the call chain may need to be synchronized, though—someone who uses the value returned from the method. Here’s an example:
Java Unleashed
Page 121
public class Point { // redefines class Point from package java.awt private float x, y; // OK since we’re in a different package here public float x() { // needs no synchronization return x; } public float y() { // ditto return y; } . . . // methods to set and change x and y } public class UnsafePointPrinter { public void print(Point p) { System.out.println(“The point’s x is “ + p.x() + “ and y is “ + p.y() + “.”); } } The analogous methods to howMany() are x() and y(). They need no synchronization because they just return the values of instance variables. It is the responsibility of the caller of x() and y() to decide whether it needs to synchronize itself—and in this case, it does. Although the method print() simply reads values and prints them out, it reads two values. This means that there is a chance that some other thread, running between the call to p.x() and the call to p.y(), could have changed the value of x and y stored inside the Point p. Remember, you don’t know how many other threads have a way to reach and call methods in this Point object! “Thinking multithreaded” comes down to being careful any time you make an assumption that something has not happened between two parts of your program (even two parts of the same line, or the same expression, such as the string + expression in this example).
TryAgainPointPrinter You could try to make a safe version of print() by simply adding the synchronized keyword modifier to it, but instead, let’s try a slightly different approach: public class TryAgainPointPrinter { public void print(Point p) { float safeX, safeY; synchronized(this) { safeX = p.x(); // these two lines now safeY = p.y(); // happen atomically } System.out.print(“The point’s x is “ + safeX + “ y is “ + safeY); } } The synchronized statement takes an argument that says what object you would like to lock to prevent more than one thread from executing the enclosed block of code at the same time. Here, you use this (the instance itself), which is exactly the object that would have been locked by the synchronized method as a whole if you had changed print() to be like your safe countMe() method. You have an added bonus with this new form of synchronization: You can specify exactly what part of a method needs to be safe, and the rest can be left unsafe. Notice how you took advantage of this freedom to make the protected part of the method as small as possible, while leaving the String creations, concatenations, and printing (which together take a small but nonzero amount of time) outside the “protected” area. This is both good style (as a guide to the reader of your code) and more efficient, because fewer threads get stuck waiting to get into protected areas.
SafePointPrinter The astute reader, though, may still be worried by the last example. It seems as if you made sure that no one executes your calls to x() and y() out of order, but have you prevented the Point p from changing out from under you? The answer is no, you still have not solved the problem. You really do need the full power of the synchronized statement:
Java Unleashed
Page 122
public class SafePointPrinter { public void print(Point p) { float safeX, safeY; synchronized(p) { // no one can change p safeX = p.x(); // while these two lines safeY = p.y(); // are happening atomically } System.out.print(“The point’s x is “ + safeX + “ y is “ + safeY); } } Now you’ve got it. You actually needed to protect the Point p from changes, so you lock it by giving it as the argument to your synchronized statement. Now when x() and y() happen together, they can be sure to get the current x and y of the Point p, without any other thread being able to call a modifying method between. You’re still assuming, however, that the Point p has properly protected itself. (You can always assume this about system classes—but you wrote this Point class.) You can make sure by writing the only method that can change x and y inside p yourself: public class Point { private float x, y; . . . // the x() and y() methods public synchronized void setXAndY(float x = newX; y = newY; } }
newX,
float
newY) {
By making the only “set” method in Point synchronized, you guarantee that any other thread trying to grab the Point p and change it out from under you has to wait: You’ve locked the Point p with your synchronized(p) statement, and any other thread has to try to lock the same Point p via the implicit synchronized(this) statement p now executes when entering setXAndY(). Thus, at last, you are thread-safe.*
*NOTE By the way, if Java had some way of returning more than one value at once, you could write a synchronized getXAndY() method for Points that returns both values safely. In the current Java language, such a method could return a new, unique Point to guarantee to its callers that no one else has a copy that might be changed. This sort of trick can be used to minimize the parts of the system that need to be concerned with synchronization. ReallySafePoint An added benefit of the use of the synchronized modifier on methods (or of synchronized(this) {. . .}) is that only one of these methods (or blocks of code) can run at once. You can use that knowledge to guarantee that only one of several crucial methods in a class will run at once:
Java Unleashed
Page 123
public class ReallySafePoint { private float x, y; public synchronized Point getUniquePoint() { return new Point(x, y); // can be a less safe Point } // because only the caller has it public synchronized void setXAndY(float newX, float newY) { x = newX; y = newY; } public synchronized void scale(float scaleX, float scaleY) { x *= scaleX; y *= scaleY; } public synchronized void add(ReallySafePoint aRSP) { Point p = aRSP.getUniquePoint(); x += p.x(); y += p.y(); } // Point p is soon thrown away by GC; no one else ever saw it } This example combines several of the ideas mentioned previously. To avoid a caller’s having to synchronize(p) whenever getting your x and y, you give them a synchronized way to get a unique Point (like returning multiple values). Each method that modifies the object’s instance variables is also synchronized to prevent it from running between the x and y references in getUniquePoint() and from stepping on one another as they modify the local x and y. Note that add() itself uses getUniquePoint() to avoid having to say synchronized(aRSP). Classes that are this safe are a little unusual; it is more often your responsibility to protect yourself from other threads’ use of commonly held objects (such as Points). You can fully relax when you know for certain that you’re the only one that knows about an object. Of course, if you created the object yourself and gave it to no one else, you can be that certain.
Protecting a Class Variable Finally, suppose you want a class variable to collect some information across all a class’s instances: public class StaticCounter { private static int crucialValue; public synchronized void countMe() { crucialValue += 1; } } Is this safe? If crucialValue were an instance variable, it would be. Because it’s a class variable, however, and there is only one copy of it for all instances, you can still have multiple threads modifying it by using different instances of the class. (Remember, the synchronized modifier locks the object this—an instance.) Luckily, you already know the tools you need to solve this: public class StaticCounter { private static int crucialValue; public void countMe() { synchronized(getClass()) { // can’t directly name StaticCounter crucialValue += 1; // the (shared) class is now locked } } } The trick is to “lock” on a different object—not on an instance of the class, but on the class itself. Because a class variable is “inside” a class, just as an instance variable is inside an instance, this shouldn’t be all that unexpected. In a similar way, classes can provide global resources that any instance (or other class) can access directly by using the class name, and lock by using that same class name.
Java Unleashed
Page 124
In the last example, crucialValue was used from within an instance of StaticCounter, but if crucialValue were declared public instead, from anywhere in the program, it would be safe to say the following: synchronized(Class.for.Name(“StaticCounter”)) { StaticCounter.crucialValue += 1; }
* *NOTE The direct use of another class’s (or object’s) variable is really bad style—it’s used here simply to demonstrate a point quickly. StaticCounter would normally provide a countMe()-like class method of its own to do this sort of dirty work. You can now begin to appreciate how much work the Java team has done for you by thinking all these hard thoughts for each and every class (and method!) in the Java class library.
Creating and Using Threads Now that you understand the power (and the dangers) of having many threads running at once, how are those threads actually created?*
*CAUTION The system itself always has a few so-called daemon threads running, one of which is constantly doing the tedious task of garbage collection for you in the background. There is also a main user thread that listens for events from your mouse and keyboard. If you’re not careful, you can sometimes lock up this main thread. If you do, no events are sent to your program and it appears to be dead. A good rule of thumb is that whenever you’re doing something that can be done in a separate thread, it probably should be. Threads in Java are relatively cheap to create, run, and destroy, so don’t use them too sparingly. Because there is a class java.lang.Thread, you might guess that you could create a thread of your own by subclassing it—and you are right: public class MyFirstThread extends Thread { // a.k.a., java.lang.Thread public void run() { . . . // do something useful } } You now have a new type of Thread called MyFirstThread, which does something useful (unspecified) when its run() method is called. Of course, no one has created this thread or called its run() method, so it does absolutely nothing at the moment. To actually create and run an instance of your new thread class, you write the following: MyFirstThread aMFT.start();
aMFT = new MyFirstThread(); // calls our run() method
What could be simpler? You create a new instance of your thread class and then ask it to start running. Whenever you want to stop the thread, you use this: aMFT.stop(); Besides responding to start() and stop(), a thread can also be temporarily suspended and later resumed: Thread t = new Thread(); t.suspend(); . . . // do something special while t isn’t running t.resume();
Java Unleashed
Page 125
A thread will automatically suspend() and then resume() when it’s first blocked at a synchronized point and then later unblocked (when it’s that thread’s “turn” to run).
The Runnable Interface This is all well and good if every time you want to create a thread you have the luxury of being able to place it under the Thread class in the single-inheritance class tree. What if it more naturally belongs under some other class, from which it needs to get most of its implementation? Interfaces come to the rescue: public class MySecondThread extends ImportantClass implements Runnable { public void run() { . . . // do something useful } } By implementing the interface Runnable, you declare your intention to run in a separate thread. In fact, the class Thread itself implements this. As you also might guess from the example, the interface Runnable specifies only one method: run(). As in MyFirstThread, you expect someone to create an instance of a thread and somehow call your run() method. Here’s how this is accomplished: MySecondThread aMST = new MySecondThread(); Thread aThread = new Thread(aMST); aThread.start(); // calls our run() method, indirectly First, you create an instance of MySecondThread. Then, by passing this instance to the constructor making the new Thread, you make it the target of that Thread. Whenever that new Thread starts up, its run() method calls the run() method of the target it was given (assumed by the Thread to be an object that implements the Runnable interface). When start() is called, aThread (indirectly) calls your run() method. You can stop aThread with stop(). If you don’t need to talk to the Thread explicitly or to the instance of MySecondThread, here’s a one line shortcut: new Thread(new MySecondThread()).start();
* *NOTE As you can see, the class name, MySecondThread, is a bit of a misnomer—it does not descend from Thread, nor is it actually the thread that you start() and stop(). It probably should have been called MySecondThreadedClass or ImportantRunnableClass. ThreadTester Here’s a longer example:
Java Unleashed
Page 126
public class SimpleRunnable implements Runnable { public void run() { System.out.println(“in thread named ‘“ + Thread.currentThread().getName() + “‘“); } // any other methods run() calls are in current thread as well } public class ThreadTester { public static void main(String argv[]) { SimpleRunnable aSR = new SimpleRunnable(); while (true) { Thread t = new Thread(aSR); System.out.println(“new Thread() “ + (t == null ? “fail” : “succeed”) + “ed.”); t.start(); try { t.join(); } catch (InterruptedException ignored) {} // waits for thread to finish its run() method } } }
* *NOTE You may be worried that only one instance of the class SimpleRunnable is created, but many new Threads are using it. Don’t they get confused? Remember to separate in your mind the aSR instance (and the methods it understands) from the various threads of execution that can pass through it. aSR’s methods provide a template for execution, and the multiple threads created are sharing that template. Each remembers where it is executing and whatever else it needs to make it distinct from the other running threads. They all share the same instance and the same methods. That’s why when adding synchronization, you need to be so careful to imagine numerous threads running rampant over each of your methods. The class method currentThread() can be called to get the thread in which a method is currently executing. If the SimpleRunnable class were a subclass of Thread, its methods would know the answer already (it is the thread running). Because SimpleRunnable simply implements the interface Runnable, however, and counts on someone else (ThreadTester’s main()) to create the thread, its run() method needs another way to get its hands on that thread. Often, you’ll be deep inside methods called by your run() method when suddenly you need to get the current thread. The class method shown in the example works, no matter where you are.*
*CAUTION You can do some reasonably disastrous things with your knowledge of threads. For example, if you’re running in the main thread of the system and, because you think you are in a different thread, you accidentally say the following: Thread.currentThread().stop(); it has unfortunate consequences for your (soon-to-be-dead) program! The example then calls getName() on the current thread to get the thread’s name (usually something helpful, such as Thread-23) so it can tell the world in which thread run() is running. The final thing to note is the use of the method join(), which, when sent to a thread, means “I’m planning to wait forever for you to finish your run() method.” You don’t want to do this lightly: If you have anything else important you need to get done in your thread any time soon, you can’t count on how long the join()ed thread may take to finish. In the example, its run() method is short and finishes quickly, so each loop can safely wait for the previous thread to die before creating the next one. (Of course, in this example, you didn’t have anything else you wanted to do while waiting for join() anyway.) Here’s the output produced:
Java Unleashed
Page 127
new Thread() succeeded. in thread named ‘Thread-1’ new Thread() succeeded. in thread named ‘Thread-2’ new Thread() succeeded. in thread named ‘Thread-3’ ^C Ctrl+C was pressed to interrupt the program, because it otherwise would continue forever.
NamedThreadTester If you want your threads to have particular names, you can assign them yourself by using a two-argument form of Thread’s constructor public class NamedThreadTester { public static void main(String argv[]) { SimpleRunnable aSR = new SimpleRunnable(); for (int i = 1; true; ++i) { Thread t = new Thread(aSR, “” + (100 - i) + “ threads on the wall...”); System.out.println(“new Thread() “ + (t == null ? “fail” : “succeed”) + “ed.”); t.start(); try { t.join(); } catch (InterruptedException ignored) {} } } } which takes a target object, as before, and a String, which names the new thread. Here’s the output: new Thread() succeeded. in thread named ’99 threads on the wall...’ new Thread() succeeded. in thread named ’98 threads on the wall...’ new Thread() succeeded. in thread named ’97 threads on the wall...’ ^C Naming a thread is one easy way to pass it some information. This information flows from the parent thread to its new child. It’s also useful, for debugging purposes, to give threads meaningful names (such as network input) so that when they appear during an error— in a stack trace, for example—you can easily identify which thread caused the problem. You might also think of using names to help group or organize your threads, but Java actually provides you with a ThreadGroup class to perform this function. A ThreadGroup allows you to group threads, to control them all as a unit, and to keep them from being able to affect other threads (useful for security).
Knowing When a Thread Has Stopped Let’s imagine a different version of the last example, one that creates a thread and then hands the thread off to other parts of the program. Suppose it would then like to know when that thread dies so that it can perform some cleanup operation. If SimpleRunnable were a subclass of Thread, you might try to catch stop() whenever it’s sent—but look at Thread’s declaration of the stop() method: public final void
stop() { . . . }
The final here means that you can’t override this method in a subclass. In any case, SimpleRunnable is not a subclass of Thread, so how can this imagined example possibly catch the death of its thread? The answer is to use the following magic:
Java Unleashed
Page 128
public class SingleThreadTester { public static void main(String argv[]) { Thread t = new Thread(new SimpleRunnable()); try { t.start(); someMethodThatMightStopTheThread(t); } catch (ThreadDeath aTD) { . . . // do some required cleanup throw aTD; // re-throw the error } } } All you need to know is that if the thread created in the example dies, it throws an error of class ThreadDeath. The code catches that error and performs the required cleanup. It then rethrows the error, allowing the thread to die. The cleanup code is not called if the thread exits normally (its run() method completes), but that’s fine; you posited that the cleanup was needed only when stop() was used on the thread.*
*NOTE Threads can die in other ways—for example, by throwing exceptions that no one catches. In these cases, stop() is never called, and the previous code is not sufficient. (If the cleanup always has to occur, even at the normal end of a thread’s life, you can put it in a finally clause.) Because unexpected exceptions can come out of nowhere to kill a thread, multithreaded programs that carefully catch and handle all their exceptions are more predictable, robust, and easier to debug.
Thread Scheduling You might wonder exactly what order your threads will be run in, and how you can control that order. Unfortunately, the current implementations of the Java system cannot precisely answer the former, though with a lot of work, you can always do the latter.
* *NOTE The part of the system that decides the real-time ordering of threads is called the scheduler.
Preemptive Versus Nonpreemptive Normally, any scheduler has two fundamentally different ways of looking at its job: nonpreemptive scheduling and preemptive timeslicing. *
*NOTE With nonpreemptive scheduling, the scheduler runs the current thread forever, requiring that thread explicitly to tell it when it is safe to start a different thread. With preemptive time-slicing, the scheduler runs the current thread until it has used up a certain tiny fraction of a second, and then “preempts” it, suspend()s it, and resume()s another thread for the next tiny fraction of a second. Nonpreemptive scheduling is very courtly, always asking for permission to schedule, and is quite valuable in extremely time-critical, real-time applications where being interrupted at the wrong moment, or for too long, could mean crashing an airplane. Most modern schedulers use preemptive time-slicing, because except for a few time-critical cases, it has turned out to make writing multithreaded programs much easier. For one thing, it does not force each thread to decide exactly when it should “yield” control to another thread. Instead, every thread can just run blindly on, knowing that the scheduler will be fair about giving all the other threads their chance to run. It turns out that this approach is still not the ideal way to schedule threads. You’ve given a little too much control to the scheduler. The final touch many modern schedulers add is to enable you to assign each thread a priority. This creates a total ordering of all threads, making some threads more “important” than others. Being higher priority often means that a thread gets run more often (or
Java Unleashed
Page 129
gets more total running time), but it always means that it can interrupt other, lower-priority threads, even before their “time-slice” has expired. The current Java release does not precisely specify the behavior of its scheduler. Threads can be assigned priorities, and when a choice is made between several threads that all want to run, the highest-priority thread wins. However, among threads that are all the same priority, the behavior is not well-defined. In fact, the different platforms on which Java currently runs have different behaviors—some behaving more like a preemptive scheduler, and some more like a nonpreemptive scheduler.*
*NOTE This incomplete specification of the scheduler is terribly annoying and, presumably, will be corrected in later releases. Not knowing the fine details of how scheduling occurs is perfectly all right, but not knowing whether equal priority threads must explicitly yield or face running forever is not all right. For example, all the threads you have created so far are equal priority threads, so you don’t know their basic scheduling behavior! Testing Your Scheduler To find out what kind of scheduler you have on your system, try the following: public class RunnablePotato implements Runnable { public void run() { while (true) System.out.println(Thread.currentThread().getName()); } } public class PotatoThreadTester { public static void main(String argv[]) { RunnablePotato aRP = new RunnablePotato(); new Thread(aRP, “one potato”).start(); new Thread(aRP, “two potato”).start(); } } For a nonpreemptive scheduler, this prints the following one one one . .
potato potato potato .
forever, until you interrupt the program. For a preemptive scheduler that time-slices, it repeats the line one potato a few times, followed by the same number of two potato lines, over and over one one ... one two two ... two . .
potato potato potato potato potato potato .
until you interrupt the program. What if you want to be sure the two threads will take turns, no matter what the system scheduler wants to do? You rewrite RunnablePotato as follows:
Java Unleashed
Page 130
public class RunnablePotato implements Runnable { public void run() { while (true) { System.out.println(Thread.currentThread().getName()); Thread.yield(); // let another thread run for a while } } }
* *TIP Normally you would have to say Thread.currentThread().yield() to get your hands on the current thread, and then call yield(). Because this pattern is so common, however, the Thread class provides a shortcut. The yield() method explicitly gives any other threads that want to run a chance to begin running. (If there are no threads waiting to run, the thread that made the yield() simply continues.) In our example, there’s another thread that’s just dying to run, so when you now execute the class ThreadTester, it should output the following: one two one two one two . .
potato potato potato potato potato potato .
even if your system scheduler is nonpreemptive, and would never normally run the second thread.
PriorityThreadTester To see whether priorities are working on your system, try this: public class PriorityThreadTester { public static void main(String argv[]) { RunnablePotato aRP = new RunnablePotato(); Thread t1 = new Thread(aRP, “one potato”); Thread t2 = new Thread(aRP, “two potato”); t2.setPriority(t1.getPriority() + 1); t1.start(); t2.start(); // at priority Thread.NORM_PRIORITY + 1 } }
* *TIP The values representing the lowest, normal, and highest priorities that threads can be assigned are stored in class variables of the Thread class: Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, and Thread.MAX_PRIORITY. The system assigns new threads, by default, the priority Thread.NORM_PRIORITY. Priorities in Java are currently defined in a range from 1 to 10, with 5 being normal, but you shouldn’t depend on these values; use the class variables, or tricks like the one shown in the preceding example. If one potato is the first line of output, your system does not preempt using priorities. Why? Imagine that the first thread (t1) has just begun to run. Even before it has a chance to print anything, along comes a higherpriority thread (t2) that wants to run right away. That higher-priority thread should preempt (interrupt) the first, and get a chance to print twopotato before t1 finishes printing anything. In fact, if you use the RunnablePotato class that never yield()s, t2 stays in control
Java Unleashed
Page 131
forever, printing two potato lines, because it’s a higher priority than t1 and it never yields control. If you use the latest RunnablePotato class (with yield()), the output is alternating lines of one potato and two potato as before, but starting with two potato. Here’s a good, illustrative example of how complex threads behave: public class ComplexThread extends Thread { private int delay; ComplexThread(String name, float seconds) { super(name); delay = (int) seconds * 1000; // delays are in milliseconds start(); // start up ourself! } public void run() { while (true) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(delay); } catch (InterruptedException e) { return; } } } public static void main(String argv[]) { new ComplexThread(“one potato”, 1.1F); new ComplexThread(“two potato”, 1.3F); new ComplexThread(“three potato”, 0.5F); new ComplexThread(“four”, 0.7F); } } This example combines the thread and its tester into a single class. Its constructor takes care of naming (itself) and of starting (itself), because it is now a Thread. The main() method creates new instances of its own class, because that class is a subclass of Thread. run() is also more complicated because it now uses, for the first time, a method that can throw an unexpected exception. The Thread.sleep() method forces the current thread to yield() and then waits for at least the specified amount of time to elapse before allowing the thread to run again. Another thread, however, might interrupt the sleeping thread. In such a case, it throws an InterruptedException. Now, because run() is not defined as throwing this exception, you must “hide” the fact by catching and handling it yourself. Because interruptions are usually requests to stop, you should exit the thread, which you can do by simply returning from the run() method. This program should output a repeating but complex pattern of four different lines, where every once in a great while you see the following: . . . one potato two potato three potato four . . . You should study the pattern output to prove to yourself that true parallelism is going on inside Java programs. You may also begin to appreciate that, if even this simple set of four threads can produce such complex behavior, many more threads must be capable of producing near chaos if not carefully controlled. Luckily, Java provides the synchronization and thread-safe libraries you need to control that chaos.
Java Unleashed
Page 132
Summary This chapter showed that parallelism is desirable and powerful, but introduces many new problems—such as methods and variables now need to be protected from thread conflicts—that can lead to chaos if not carefully controlled. By “thinking multithreaded,” you can detect the places in your programs that require synchronized statements (or modifiers) to make them thread-safe. A series of Point examples demonstrated the various levels of safety you can achieve, while ThreadTesters showed how subclasses of Thread, or classes that implement the Runnable interface, are created and run() to generate multithreaded programs. You also learned how to yield(), how to start(), stop(), suspend(), and resume() your threads, and how to catch ThreadDeath whenever it happens. Finally, you learned about preemptive and nonpreemptive scheduling, both with and without priorities, and how to test your Java system to see which of them your scheduler is using. This wraps up the description of threads. You now know enough to write the most complex of programs: multithreaded ones. As you get more comfortable with threads, you may begin to use the ThreadGroup class or to use the enumeration methods of Thread to get your hands on all the threads in the system and manipulate them. Don’t be afraid to experiment; you can 92't permanently break anything, and you only learn by trying.
Java Unleashed
Page 133
Chapter 16 Exception handling This chapter covers exceptional conditions in Java. You learn how to handle them, how to create them, and how your code is limited—yet made more robust—by them. Let’s begin by discussing why new ways of handling exceptions were invented. Programming languages have long labored to solve the following common problem: int status = callSomethingThatAlmostAlwaysWorks(); if (status == FUNNY_RETURN_VALUE) { . . . // something unusual happened, handle it switch(someGlobalErrorIndicator) { . . . // handle more specific problems } } else { . . . // all is well, go your merry way } Somehow this seems like a lot of work to do to handle a rare case. What’s worse, if the function called returns an int as part of its normal answer, you must distinguish one special integer (FUNNY_RETURN_VALUE) to indicate an error. What if that function really needs all the integers? You must do something even uglier. Even if you manage to find a distinguished value (such as NULL in C for pointers, -1 for integers, and so forth), what if there are multiple errors that must be produced by the same function? Often, some global variable is used as an error indicator. The function stores a value in it and prays that no one else changes it before the caller gets to handle the error. Multiple errors propagate badly, if at all, and there are numerous problems with generalizing this to large programs, complex errors, and so forth. Luckily, there is an alternative: using exceptions to help you handle abnormal conditions in your program, making the normal, nonexceptional code cleaner and easier to read. *
*NOTE An exception is any object that is an instance of the class Throwable (or any of its subclasses).
Programming in the Large When you begin to build complex programs in Java, you discover that after designing the classes and interfaces, and their methods’ descriptions, you still have not defined all the behavior of your objects. After all, an interface describes the normal way to use an object and doesn’t include any strange, exceptional cases. In many systems, the documentation takes care of this problem by explicitly listing the distinguished values used in “hacks” like the previous example. Because the system knows nothing about these hacks, it cannot check them for consistency. In fact, the compiler can do nothing at all to help you with these exceptional conditions, in contrast to the helpful warnings and errors it produces if a method is used incorrectly. More importantly, you have not captured in your design this important aspect of your program. Instead, you are forced to make up a way to describe it in the documentation and hope you have not made any mistakes when you implement it later. What’s worse, everyone else makes up a different way of describing the same thing. Clearly, you need some uniform way of declaring the intentions of classes and methods with respect to these exceptional conditions. Java provides just such a way: public class MyFirstExceptionalClass { public void anExceptionalMethod() throws MyFirstException { . . . } }
Java Unleashed
Page 134
Here, you warn the reader (and the compiler) that the code . . . may throw an exception called MyFirstException. You can think of a method’s description as a contract between the designer of that method (or class) and you, the caller of the method. Usually, this description tells the types of a method’s arguments, what it returns, and the general semantics of what it normally does. You are now being told, as well, what abnormal things it can do. This is a promise, just like the method promises to return a value of a certain type, and you can count on it when writing your code. These new promises help to tease apart and make explicit all the places where exceptional conditions should be handled in your program, and that makes large-scale design easier. Because exceptions are instances of classes, they can be put into a hierarchy that can naturally describe the relationships among the different types of exceptions. In fact, if you take amoment to glance in Appendix B, “Class Hierarchy Diagrams,” at the diagrams for java.lang errors and java.lang exceptions, you’ll see that the class Throwable actually has two large hierarchies of classes beneath it. The roots of these two hierarchies are subclasses of Throwable called Exception and Error. These hierarchies embody the rich set of relationships that exist between exceptions and errors in the Java run-time environment. When you know that a particular kind of error or exception can occur in your method, you are supposed to either handle it yourself or explicitly warn potential callers about the possibility using the throws clause. Not all errors and exceptions must be listed; instances of either class Error or RuntimeException (or any of their subclasses) do not have to be listed in your throws clause. They get special treatment because they can occur anywhere within a Java program and are usually conditions that you, as the programmer, did not directly cause. One good example is the OutOfMemoryError, which can happen anywhere, at any time, and for any number of reasons. *
*NOTE You can, of course, choose to list these errors and run-time exceptions if you like, but the callers of your methods will not be forced to handle them; only in your throws clause non-run-time exceptions must be handled. Whenever you see the word “exception” by itself, it almost always means “exception or error” (that is, an instance of Throwable). The previous discussion makes it clear that Exceptions and Errors actually form two separate hierarchies, but except for the throws clause rule, they act exactly the same. If you examine the diagrams in Appendix B more carefully, you’ll notice that there are only six types of exceptions (in java.lang) that must be listed in a throws clause (remember that all Errors and RuntimeExceptions are exempt):
ClassNotFoundException CloneNotSupportedException IllegalAccessException InstantiationException InterruptedException NoSuchMethodException Each of these names suggests something that is explicitly caused by the programmer, not some behind-the-scenes event such as OutOfMemoryError. If you look further in Appendix B, near the bottom of the diagrams for java.util and java.io, you’ll see that each package adds some new exceptions. The former is adding two exceptions somewhat akin to ArrayStoreException and IndexOutOfBoundsException, and so decides to place them under RuntimeException. The latter is adding a whole new tree of IOExceptions, which are more explicitly caused by the programmer, and so they are rooted under Exception. Thus, IOExceptions must be described in throws clauses. Finally, package java.awt (in diagram java.awt-components) defines one of each style, implicit and explicit. The Java class library uses exceptions everywhere, and to good effect. If you examine the detailed API documentation in your Java release, you see that many of the methods in the library have throws clauses, and some of them even document (when they believe it will make something clearer to the reader) when they may throw one of the implicit errors or exceptions. This is just a nicety on the documenter’s part, because you are not required to catch conditions like that. If it wasn’t obvious that such a condition could happen there, and for some reason you really cared about catching it, this would be useful information.
Java Unleashed
Page 135
Programming in the Small Now that you have a feeling for how exceptions can help you design a program and a class library better, how do you actually use exceptions? Let’s try to call anExceptionalMethod() defined in this chapter’s first example: public void anotherExceptionalMethod() throws MyFirstException { MyFirstExceptionalClass aMFEC = new MyFirstExceptionalClass(); aMFEC.anExceptionalMethod(); } Let’s examine this example more closely. If you assume that MyFirstException is a subclass of Exception, it means that if you don’t handle it in anotherExceptionalMethod()’s code, you must warn your callers about it. Because your code simply calls anExceptionalMethod() without doing anything about the fact that it may throw MyFirstException, you must add that exception to your throws clause. This is perfectly legal, but it does defer to your caller something that perhaps you should be responsible for doing yourself. (It depends on the circumstances, of course.) Suppose that that you feel responsible today and decide to handle the exception. Because you’re now declaring a method without a throws clause, you must “catch” the expected exception and do something useful with it: public void responsibleMethod() { MyFirstExceptionalClass aMFEC = new MyFirstExceptionalClass(); try { aMFEC.anExceptionalMethod(); } catch (MyFirstException m) { . . . // do something terribly significant and responsible } } The try statement says basically: “Try running the code inside these braces, and if there are exceptions thrown, I will attach handlers to take care of them.” You can have as many catch clauses at the end of a try as you need. Each allows you to handle any and all exceptions that are instances of the class listed in parentheses, of any of its subclasses, or of a class that implements the interface listed in parentheses. In the catch in this example, exceptions of the class MyFirstException (or any of its subclasses) are being handled. What if you want to combine both the approaches shown so far? You’d like to handle the exception yourself, but also reflect it up to your caller. This can be done by explicitly rethrowing the exception: public void responsibleExceptionalMethod() throws MyFirstException { MyFirstExceptionalClass aMFEC = new MyFirstExceptionalClass(); try { aMFEC.anExceptionalMethod(); } catch (MyFirstException m) { . . . // do something responsible throw m; // re-throw the exception } } This works because exception handlers can be nested. You handle the exception by doing something responsible with it, but decide that it is too important to not give an exception handler that might be in your caller a chance to handle it as well. Exceptions float all the way up the chain of method callers this way (usually not being handled by most of them) until at last, the system itself handles any uncaught ones by aborting your program and printing an error message. In a stand-alone program, this is not such a bad idea; but in an applet, it can cause the browser to crash. Most browsers protect themselves from this disaster by catching all exceptions themselves whenever they run an applet, but you can never tell. If it’s possible for you to catch an exception and do something intelligent with it, you should. Let’s see what throwing a new exception looks like. Let’s flesh out this chapter’s first example:
Java Unleashed
Page 136
public class MyFirstExceptionalClass { public void anExceptionalMethod() throws MyFirstException { . . . if (someThingUnusualHasHappened()) { throw new MyFirstException(); // execution never reaches here } } }
* *NOTE throw is a little like a break statement—nothing “beyond it” is executed. This is the fundamental way that all exceptions are generated; someone, somewhere, has to create an exception object and throw it. In fact, the whole hierarchy under the class Throwable would be worth much less if there were not throw statements scattered throughout the code in the Java library at just the right places. Because exceptions propagate up from any depth down inside methods, any method call you make might generate a plethora of possible errors and exceptions. Luckily, only the ones listed in the throws clause of that method need be thought about; the rest travel silently past on their way to becoming an error message (or being caught and handled “higher up” in the system). Here’s an unusual demonstration of this, where the throw, and the handler that catches it, are very close together: System.out.print(“Now “); try { System.out.print(“is “); throw new MyFirstException(); System.out.print(“a “); } catch (MyFirstException m) { System.out.print(“the “); } System.out.print(“time.\n”); It prints out Now is the time. Exceptions are really a quite powerful way of partitioning the space of all possible error conditions into manageable pieces. Because the first catch clause that matches is executed, you can build chains such as the following: try { someReallyExceptionalMethod(); } catch (NullPointerException n) { . . . } catch (RuntimeException r) { . . . } catch (IOException i) { . . . } catch (MyFirstException m) { . . . } catch (Exception e) { . . . } catch (Throwable t) { . . . // Errors, plus anything }
// a subclass of RuntimeException // a subclass of Exception // a subclass of Exception // our subclass of Exception // a subclass of Throwable not caught above are caught here
By listing subclasses before their parent classes, the parent catches anything it would normally catch that’s also not one of the subclasses above it. By juggling chains like these, you can express almost any combination of tests. If there’s some really obscure case you can’t handle, perhaps you can use an interface to catch it instead. That would enable you to design your (peculiar) exceptions
Java Unleashed
Page 137
hierarchy using multiple inheritance. Catching an interface rather than a class can also be used to test for a property that many exceptions share but that cannot beexpressed in the single-inheritance tree alone. Suppose, for example, that a scattered set of your exception classes require a reboot after being thrown. You create an interface called NeedsReboot, and all these classes implement the interface. (None of them needs to have a common parent exception class.) Then, the highest level of exception handler simply catches classes that implement NeedsReboot and performs a reboot: public interface NeedsReboot { } // needs no contents at all try { someMethodThatGeneratesExceptionsThatImplementNeedsReboot(); } catch (NeedsReboot n) { // catch an interface . . . // cleanup SystemClass.reboot(); // reboot using a made-up system class } By the way, if you need really unusual behavior during an exception, you can place the behavior into the exception class itself! Remember that an exception is also a normal class, so it can contain instance variables and methods. Although using them is a little unusual, it might be valuable on a few occasions. Here’s what this might look like: try { someExceptionallyStrangeMethod(); } catch (ComplexException e) { switch (e.internalState()) { // probably returns an instance variable value case e.COMPLEX_CASE: // a class variable of the exception’s class e.performComplexBehavior(myState, theContext, etc); break; . . . } }
The Limitations Placed on the Programmer As powerful as all this sounds, isn’t it a little limiting, too? For example, suppose you want to override one of the standard methods of the Object class, toString(), to be smarter about how you print yourself: public class MyIllegalClass { public String toString() { someReallyExceptionalMethod(); . . . // returns some String } } Because the superclass (Object) defined the method declaration for toString() without a throws clause, any implementation of it in any subclass must obey this restriction. In particular, you cannot just call someReallyExceptionalMethod(), as you did previously, because it will generate a host of errors and exceptions, some of which are not exempt from being listed in a throws clause (such as IOException and MyFirstException). If all the exceptions thrown were exempt, you would have no problem, but because some are not, you have to catch at least those few exceptions for this to be legal Java:
Java Unleashed
Page 138
public class MyLegalClass { public String toString() { try { someReallyExceptionalMethod(); } catch (IOException e) { } catch (MyFirstException m) { } . . . // returns some String } } In both cases, you elect to catch the exceptions and do absolutely nothing with them. Although this is legal, it is not always the right thing to do. You may need to think for a while to come up with the best, nontrivial behavior for any particular catch clause. This extra thought and care makes your program more robust, better able to handle unusual input, and more likely to work correctly when used by multiple threads. MyIllegalClass’s toString() method produces a compiler error to remind you to reflect on these issues. This extra care will richly reward you as you reuse your classes in later projects and in larger and larger programs. Of course, the Java class library has been written with exactly this degree of care, and that’s one of the reasons it’s robust enough to be used in constructing all your Java projects.
The finally Clause Finally, for finally. Suppose there is some action that you absolutely must do, no matter what happens. Usually, this is to free some external resource after acquiring it, to close a file after opening it, or something similar. To be sure that “no matter what” includes exceptions as well, you use a clause of the try statement designed for exactly this sort of thing, finally: SomeFileClass f = new SomeFileClass(); if (f.open(“/a/file/name/path”)) { try { someReallyExceptionalMethod(); } finally { f.close(); } } This use of finally behaves very much like the following SomeFileClass f = new SomeFileClass(); if (f.open(“/a/file/name/path”)) { try { someReallyExceptionalMethod(); } catch (Throwable t) { f.close(); throw t; } } except that finally can also be used to clean up not only after exceptions but after return, break, and continue statements as well. Here’s a complex demonstration:
Java Unleashed
Page 139
public class MyFinalExceptionalClass extends ContextClass { public static void main(String argv[]) { int mysteriousState = getContext(); while (true) { System.out.print(“Who “); try { System.out.print(“is “); if (mysteriousState == 1) return; System.out.print(“that “); if (mysteriousState == 2) break; System.out.print(“strange “); if (mysteriousState == 3) continue; System.out.print(“but kindly “); if (mysteriousState == 4) throw new UncaughtException(); System.out.print(“not at all “); } finally { System.out.print(“amusing man?\n”); } System.out.print(“I’d like to meet the man “); } System.out.print(“Please tell me.\n”); } } Here is the output produced depending on the value of mysteriousState: 1 2 3 4 5
Who is amusing man? Who is that amusing man? Please tell me Who is that strange amusing man? ... Who is that strange but kindly amusing man? Who is that strange but kindly not at all amusing man? I’d like to meet the man Who is that strange...? ...
* *NOTE In cases 3 and 5, the output never ends until you press Ctrl+C. In 4, an error message generated by the UncaughtException is also printed.
Summary This chapter discussed how exceptions aid your program’s design, robustness, and multithreading capability. You also learned about the vast array of exceptions defined and thrown in the Java class library, and how to try methods while catching any of a hierarchically ordered set of possible exceptions and errors. Java’s reliance on strict exception handling does place some restrictions on the programmer, but you learned that these restrictions are light compared to the rewards. Finally, the finally clause was discussed, which provides a fool-proof way to be certain that something is accomplished, no matter what.
Java Unleashed
Page 140
Chapter 17 Overview of the class libraries Code reuse is one of the most significant benefits of using object-oriented design practices. Creating reusable, inheritable classes can save amazing amounts of time and energy, which in turn greatly boosts productivity. Java itself takes code reuse to heart in its implementation of a wide variety of standard objects that are available to Java programmers. The standard Java objects are known collectively as the Java class libraries. The Java class libraries are implemented as packages, which contain groups of related classes. Along with classes, the standard Java packages also include interfaces, exception definitions, and error definitions. Java is composed of three class libraries, or packages: the language package, the utilities package, and the I/O package. In this chapter, you learn what these packages are and what classes and interfaces comprise each.*
*NOTE There actually are three other standard packages that are important in Java programming, but they aren’t technically considered part of the basic Java class libraries. You’ll learn about these packages in Part V, “Applet Programming,” and Part VI, “Network Programming.”
The Language Package The Java language package, which is also known as java.lang, provides classes that make up the core of the Java language. The language package contains classes at the lowest level of the Java class libraries. For example, the Object class, which all classes are derived from, is located in the language package. It’s impossible to write a Java program without dealing with at least a few of the elements of the language package. You’ll learn much more about the inner workings of the language package in the next chapter. The most important classes contained in the language package follow:
The Object Class Data Type Wrapper Classes The Math Class String Classes System and Runtime Classes Thread Classes Class Classes Exception Handling Classes Process Classes The Object Class The Object class is the superclass for all classes in Java. Because all classes are derived from Object, the methods defined in Object are shared by all classes. This results in a core set of methods that all Java classes are guaranteed to support. Object includes methods for making copies of an object, testing objects for equality, and converting the value of an object to a string.
Data Type Wrapper Classes The fundamental data types (int, char, float, and so on) in Java are not implemented as classes. Many times it is useful, however, to know more information about a fundamental type than just its value. By implementing class wrappers for the fundamental types, additional information can be maintained, as well as methods defined that act on the types. The data type wrapper classes serve as
Java Unleashed
Page 141
class versions of the fundamental data types, and are named similarly to the types they wrap. For example, the type wrapper for int is the Integer class. Following are the Java data type wrapper classes:
Boolean Character Double Float Integer Long Type wrappers are also useful because many of Java’s utility classes require classes as parameters, not simple types. It is worth pointing out that type wrappers and simple types are not interchangeable. However, you can get a simple type from a wrapper through a simple method call, which you’ll learn about in the next chapter.
The Math Class The Math class serves as a grouping of mathematical functions and constants. It is interesting to note that all the variables and methods in Math are static, and the Math class itself is final. This means you can’t derive new classes from Math. Additionally, you can’t instantiate the Math class. It’s best to think of the Math class as just a conglomeration of methods and constants for performing mathematical computations. The Math class includes the E and PI constants, methods for determining the absolute value of a number, methods for calculating trigonometric functions, and minimum and maximum methods, among others.
String Classes For various reasons (mostly security related), Java implements text strings as classes, rather than forcing the programmer to use character arrays. The two Java classes that represent strings are String and StringBuffer. The String class is useful for working with constant strings that can’t change in value or length. The StringBuffer class is used to work with strings of varying value and length.
The System and Runtime Classes The System and Runtime classes provide a means for your programs to access system and runtime environment resources. Like the Math class, the System class is final and is entirely composed of static variables and methods. The System class basically provides a system-independent programming interface to system resources. Examples of system resources include the standard input and output streams, System.in and System.out, which typically model the keyboard and monitor. The Runtime class provides direct access to the runtime environment. An example of a run-time routine is the freeMemory method, which returns the amount of free system memory available.
Thread Classes Java is a multithreaded environment and provides various classes for managing and working with threads. Following are the classes and interfaces used in conjunction with multithreaded programs:
Thread ThreadDeath ThreadGroup Runnable The Thread class is used to create a thread of execution in a program. The ThreadDeath class is used to clean up after a thread has finished execution. As its name implies, the ThreadGroup class is useful for organizing a group of threads. Finally, the Runnable
Java Unleashed
Page 142
interface provides an alternate means of creating a thread without subclassing the Thread class. Threads and multithreading are covered in detail in Chapter 15, “Threads and Multithreading.”
Class Classes Java provides two classes for working with classes: Class and ClassLoader. The Class class provides runtime information for a class, such as the name, type, and parent superclass. Class is useful for querying a class for runtime information, such as the class name. The ClassLoader class provides a means to load classes into the runtime environment. ClassLoader is useful for loading classes from a file or for loading distributed classes across a network connection.
Error-Handling Classes Runtime error handling is a very important facility in any programming environment. Java provides the following classes for dealing with runtime errors:
Throwable Exception Error The Throwable class provides low-level error-handling capabilities such as an execution stack list. The Exception class is derived from Throwable and provides the base level of functionality for all the exception classes defined in the Java system. The Exception class is used for handling normal errors. The Error class is also derived from Throwable, but it is used for handling abnormal errors that aren’t expected to occur. Very few Java programs worry with the Error class; most use the Exception class to handle runtime errors. Error handling with exceptions is covered in detail in Chapter 16, “Exception Handling.”
Process Classes Java supports system processes with a single class, Process. The Process class represents generic system processes that are created when you use the Runtime class to execute system commands.
The Utilities Package The Java utilities package, which is also known as java.util, provides various classes that perform different utility functions. The utilities package includes a class for working with dates, a set of data structure classes, a class for generating random numbers, and a string tokenizer class, among others. You’ll learn much more about the classes that make up the utilities package in Chapter 19, “The Utilities Package.” The most important classes contained in the utilities package follow:
The Date Class Data Structure Classes The Random Class The StringTokenizer Class The Properties Class The Observer Interface The Enumeration Interface The Date Class The Date class represents a calendar date and time in a system-independent fashion. The Date class provides methods for retrieving the current date and time as well as computing days of the week and month.
Data Structure Classes The Java data structure classes and interfaces implement popular data structures for storing data. The data structure classes and interfaces are as follows:
Java Unleashed
Page 143
BitSet Dictionary Hashtable Properties Vector Stack Enumeration The BitSet class represents a set of bits, which is also known as a bitfield. The Dictionary class is an abstract class that provides a lookup mechanism for mapping keys to values. The Hashtable class derives from Dictionary and provides additional support for working with keys and values. The Properties class is derived from Hashtable and provides the additional functionality of being readable and writable to and from streams. The Vector class implements an array that can dynamically grow. The Stack class derives from Vector and implements a classic stack of last-in-first-out (LIFO) objects. Finally, the Enumeration interface specifies a set of methods for counting (iterating) through a set of values.
The Random Class Many programs, especially programs that model the real world, require some degree of randomness. Java provides randomness by way of the Random class. The Random class implements a random-number generator by providing a stream of pseudo-random numbers. A slot machine program is a good example of one that would make use of the Random class.
The StringTokenizer Class The StringTokenizer class provides a means of converting text strings into individual tokens. By specifying a set of delimiters, you can parse text strings into tokens using the StringTokenizer class. String tokenization is useful in a wide variety of programs, from compilers to text-based adventure games.
The Observer Classes The model-view paradigm is becoming increasingly popular in object-oriented programming. This model breaks a program down into data and views on the data. Java supports this model with the Observable class and the Observer interface. The Observable class is subclassed to define the observable data in a program. This data is then connected to one or more observer classes. The observer classes are implementations of the Observer interface. When an Observable object changes state, it notifies all of its observers of the change.
The I/O Package The Java I/O package, also known as java.io, provides classes with support for reading and writing data to and from different input and output devices, including files. The I/O package includes classes for inputting streams of data, outputting streams of data, working with files, and tokenizing streams of data. You’ll learn a lot more about the classes that make up the I/O package in Chapter 20, “The I/O Package.” The most important classes contained in the I/O package follow:
Input Stream Classes Output Stream Classes File Classes The StreamTokenizer Class Input Stream Classes Java uses input streams to handle reading data from an input source. An input source can be a file, a string, memory, or anything else that contains data. The input stream classes follow:
InputStream
Java Unleashed
Page 144
BufferedInputStream ByteArrayInputStream DataInputStream FileInputStream FilterInputStream LineNumberInputStream PipedInputStream PushbackInputStream SequenceInputStream StringBufferInputStream The InputStream class is an abstract class that serves as the base class for all input streams. The InputStream class defines an interface for reading streamed bytes of data, finding out the number of bytes available for reading, and moving the stream position pointer, among other things. All the other input streams provide support for reading data from different types of input devices.
Output Stream Classes Output streams are the counterpart to input streams and handle writing data to an output source. Similar to input sources, output sources include files, strings, memory, and anything else that can contain data. The output stream classes defined in java.io follow:
OutputStream BufferedOutputStream ByteArrayOutputStream DataOutputStream FileOutputStream FilterOutputStream PipedOutputStream PrintStream The OutputStream class is an abstract class that serves as the base class for all output streams. OutputStream defines an interface for writing streamed bytes of data to an output source. All the other output streams provide support for writing data to different output devices. Data written by an output stream is formatted to be read by an input stream.
File Classes Files are the most widely used method of data storage in computer systems. Java supports files with two different classes: File and RandomAccessFile. The File class provides an abstraction for files that takes into account system-dependent features. The File class keeps up with information about a file including the location where it is stored and how it can be accessed. The File class has no methods for reading and writing data to and from a file; it is only useful for querying and modifying the attributes of a file. In actuality, you can think of the File class data as representing a filename, and the class methods as representing operating system commands that act on filenames. The RandomAccessFile class provides a variety of methods for reading and writing data to and from a file. RandomAccessFile contains many different methods for reading and writing different types of information, namely the data type wrappers.
Java Unleashed
Page 145
The StreamTokenizer Class The StreamTokenizer class provides the functionality for converting an input stream of data into a stream of tokens. StreamTokenizer provides a set of methods for defining the lexical syntax of tokens. Stream tokenization can be useful in parsing streams of textual data.
Summary This chapter provided a thumbnail sketch of the contents of the three Java class libraries: the language package, the utilities package, and the I/O package. Although you didn’t learn a lot of gritty details, or how to use any classes in a real program, you hopefully now have a broad picture in your mind of what these packages can do. The Java class libraries provide a rich set of classes for overcoming a wide variety of programming obstacles. A common problem when using programming environments with a lot of support libraries is knowing what functionality is provided and what functionality you must write yourself. This chapter hopefully has given you an idea of what standard classes you can reuse in your own Java programs, and what classes you will have to implement yourself. Having seen what each package in the Java class libraries contain, you’re probably eager to get busy learning how to use the classes in each. The next three chapters each focus on one of the three packages that make up the Java class libraries.
Java Unleashed
Page 146
Chapter 18 The language package The Java language package is at the heart of the Java language. In this chapter you learn more about some of the classes that make up the language package (java.lang). You’ll find many of the classes in the language package to be indispensable in writing Java programs. The language package contains many classes, each with a variety of member variables and methods. You won’t learn about every class and every method in this chapter, as it would simply be too much material to cover in a single chapter. Rather, you will focus on the most important classes in the language package; classes that will come in the most useful as you begin developing your own Java classes. Please note that although the multithreading and error-handling classes are part of the language package, they aren’t covered in this chapter. This is because Chapters 15 and 16 are devoted to them.
The Object Class The Object class is probably the most important of all Java classes, simply because it is the superclass of all Java classes. It is important to have a solid understanding of the Object class, as all the classes you develop will inherit the variables and methods of Object. The Object class implements the following important methods:
Object clone() boolean equals(Object obj) int hashCode() final Class getClass() String toString() The Object clone() creates a clone of the object it is called on. clone creates and allocates memory for the new object that is being copied to. clone actually creates a new object and then copies the contents of the calling object to the new object. An example of using the clone method follows: Circle circle1 = new Circle(1.0, 3.5, 4.2); Circle circle2 = circle1.clone(); In this example, the circle1 object is created, but the circle2 object is only declared. circle2 is not created by using the new operator; it is created by circle1 calling the clone method to create a clone of itself. The equals method compares two objects for equality. equals is only applicable when both objects have been stored in a Hashtable. The hashCode method returns the hashcode value for an object. Hashcodes are integers that uniquely represent objects in the Java system. The getClass method returns the runtime class information for an object in the form of a Class object. If you recall, the Class object keeps up with runtime class information, such as the name of a class and the parent superclass. The toString method returns a string representing the value of an object. Because the value of an object varies depending on the class type, it is assumed that each class will override the toString method to display information specific to that class. The information returned by toString could be very valuable for determining the internal state of an object when debugging.
Data Type Wrapper Classes The data type wrapper classes serve to provide object versions of the fundamental Java data types. Type wrapping is important because many Java classes and methods operate on classes rather than fundamental types. Furthermore, by creating object versions of the simple data types, it is possible to add useful member functions for each. Following are the type wrapper classes supported by Java:
Java Unleashed
Page 147
Boolean Character Double Float Integer Long Although each wrapper implements methods specific to each data type, there are a handful of methods applicable to all the wrappers. These methods follow:
ClassType(type) type typeValue() int hashCode() String toString() boolean equals(Object obj) static boolean valueOf(String s) * *NOTE Actually, the valueOf method isn’t implemented in the Character class, but it is implemented in all the other wrapper classes. The ClassType method is actually the creation method for each class. The wrapper creation methods take as their only parameter the type of data they are wrapping. This enables you to create a type wrapper from a fundamental type. For example, you would use the creation method for the Character class like this: Character c1 = new Character(‘x’); The typeValue method is used to get the fundamental type back from a wrapper. typeValue returns a value of the same type as the fundamental type it wraps. Following is an example of how a fundamental type can be extracted from a wrapper object: char c2 = c1.charValue();
* *NOTE Remember, fundamental types are not represented in Java by classes or objects. Data type wrapper classes provide a means of representing a fundamental type as an object, which is often useful. Wrapper classes are different from other Java classes in that their only purpose is to allow fundamental types to be represented as objects. The hashCode method returns the hashcode for a type wrapper object. This hashCode method is simply an overridden version of the hashCode method contained in the Object class. The toString method is used to get a string representation of the internal state of an object. toString is typically overridden in each class so as to reflect unique state implementations. Following is an example of how you might output the state of a wrapper variable using toString: System.out.println(c1.toString());
Java Unleashed
Page 148
The equals method is used to test for equality between two wrapper objects. This is the same equals method implemented in Object and inherited by all other objects in Java. The valueOf method is implemented in all of the type wrappers except Character. valueOf, which is static, is used to convert a string to a value of a particular wrapper type. valueOf parses the String parameter s and returns the value of it. Now that you have an idea of what functionality all of the wrapper classes share, it’s time to take a look at some of the specifics of each class.
The Boolean Class The Boolean class wraps the boolean fundamental data type. Boolean implements only one method in addition to the common wrapper methods already mentioned: static boolean getBoolean(String name) getBoolean returns a type boolean that represents the boolean property value of the String parameter name. The name parameter refers to a property name that represents a boolean property value. Because getBoolean is static, it is typically meant to be used without actually instantiating a Boolean object.*
*NOTE Java properties are system variables that define the characteristics of the Java runtime environment. For example, there is a property called os.name, which specifies the name of the operating system the Java runtime is executing in. In my case, os.name is set to “Windows 95”. The Boolean class also includes two final static (constant) data members: TRUE and FALSE. TRUE and FALSE represent the two possible states that the Boolean class can represent. It is important to note the difference between true and false, and Boolean.TRUE and Boolean.FALSE. The first pair applies to boolean fundamental types, while the second pair applies to Boolean classes; they cannot be interchanged.
The Character Class The Character class wraps the char fundamental type and provides some useful methods for manipulating characters. The methods implemented by Character, beyond the common wrapper methods, follow:
static boolean isLowerCase(char ch) static boolean isUpperCase(char ch) static boolean isDigit(char ch) static boolean isSpace(char ch) static char toLowerCase(char ch) static char toUpperCase(char ch) static int digit(char ch, int radix) static char forDigit(int digit, int radix) All these methods are static, which means that they can be used without instantiating a Character object. The isLowerCase and isUpperCase methods return whether or not a character is an uppercase or lower case character. An example of using the isLowerCase method follows: Character c = new Character(‘g’); boolean isLower = Character.isLowerCase(c); In this case, the boolean variable isLower is set to true, because ‘g’ is a lowercase character. The isDigit method simply returns whether or not a character is a digit (0-9). Following is an example of how to use the isDigit method:
Java Unleashed
Page 149
boolean isDigit = Character.isDigit(‘7’); The boolean variable isDigit is set to true here because ‘7’ is in fact a numeric digit. The isSpace method returns whether or not a character is whitespace. (Whitespace is defined as any combination of the space, tab, newline, carriage return, or linefeed characters.) Following is an example of how to use isSpace: boolean isSpace = Character.isSpace(‘\t’); In this example, the isSpace boolean variable is set to true because the tab character is considered whitespace. The toLowerCase and toUpperCase methods convert a character to a lower or uppercase character. If a character is already lowercase and toLowerCase is called, the character is not changed. Similarly, toUpperCase does nothing to uppercase characters. Following are a few examples of using these methods: char c1 = Character.toUpperCase(‘g’); char c2 = Character.toLowerCase(‘M’); In the first example, c1 is converted from ‘g’ to ‘G’ because of the call to the toUpperCase method. In the second example, c2 is converted from ‘M’ to ‘m’ because of the call to toLowerCase. The digit method returns the numeric (integer) value of a character digit in base 10. The radix parameter specifies the base of the character digit for conversion. If the character is not a valid digit, -1 is returned. Following are a few examples of using the digit method: char c1 = ‘4’; char c2 = ‘c’; int four = Character.digit(c1, 10); int twelve = Character.digit(c2, 16); In the first example, the character ‘4’ is converted to the integer number 4 using the digit method. In the second example, the hexadecimal number represented by the character ‘c’ is returned as the base 10 integer number 12. The forDigit method performs the reverse of the digit method; it returns the character representation of an integer digit. Once again, radix specifies the base of the integer number. Following is an example of how to use forDigit: int i = 9; char c = Character.forDigit(i, 10); In this example, the integer number 9 is converted to the character ‘9’ by the forDigit method. The Character class provides two final static data members for specifying the radix limits for conversions: MIN_RADIX and MAX_RADIX. MIN_RADIX specifies the minimum radix (2) for performing numeric to character conversions and vice-versa. Likewise, MAX_RADIX specifies the maximum radix (36) for conversions.
Integer Classes The Integer and Long classes wrap the fundamental integer types int and long and provide a variety of methods for working with integer numbers. The methods implemented by Integer follow:
static int parseInt(String s, int radix) static int parseInt(String s) long longValue() float floatValue()
Java Unleashed
Page 150
double doubleValue() static Integer getInteger(String name) static Integer getInteger(String name, int val) static Integer getInteger(String name, Integer val) The parseInt methods parse strings for an integer value, and return the value as an int. The version of parseInt with the radix parameter enables you to specify the base of the integer; the other version of parseInt assumes a base of 10. The longValue, floatValue, and doubleValue methods return the values of an integer converted to the appropriate type. For example, the following code shows how to convert an Integer to a double: Integer i = new Integer(17); float f = i.floatValue(); In this example, the value of the Integer variable i is converted to a float value and stored in the float variable f. The result is that the integer value 17 is converted to the float value 17.0. The getInteger methods return an integer property value specified by the String property name parameter name. Notice that all three of the getInteger methods are static, which means you don’t need to instantiate an Integer object to use these methods. The differences between these methods is what happens if the integer property isn’t found. The first version returns 0 if the property isn’t found, the second version returns the int parameter val, and the last version returns the Integer value val. The Integer class also includes two final static (constant) data members: MINVALUE and MAXVALUE. MINVALUE and MAXVALUE specify the smallest and largest numbers that can be represented by an Integer object. The Long object is very similar to the Integer object except it wraps the fundamental type long. Long actually implements similar methods as Int, with the exception that they act on long type numbers rather than int type numbers.
Floating Point Classes The Float and Double classes wrap the fundamental floating point types float and double. These two classes provide a group of methods for working with floating point numbers. The methods implemented by the Float class follow:
boolean isNaN() static boolean isNaN(float v) boolean isInfinite() static boolean isInfinite(float v) int intValue() long longValue() double doubleValue() static int floatToIntBits(float value) static float intBitsToFloat(int bits) The isNaN methods return whether or not the Float value is the special not-a-number (NaN) value. The first version of isNaN operates on the value of the calling Float object. The second version is static and takes the float to test as its parameter, v. The isInfinite methods return whether or not the Float value is infinite, which is represented by the special NEGATIVE_INFINITY and POSITIVE_INFINITY final static member variables. Like the isNaN methods, isInfinity comes in two versions: a class value version and a static version that takes a float as an argument.
Java Unleashed
Page 151
The intValue, longValue, and doubleValue methods return the values of a floating point number converted to the appropriate type. For example, the following code shows how to convert a Float to a long: Float f = new Float(5.237); long l = f.longValue(); In this example, the value of the Float variable f is converted to a long and stored in the long variable l. This results in the floating point value 5.237 being converted to the long value 5. The last two methods implemented by the Float class are floatToIntBits and intBitsToFloat. The floatToIntBits and intBitsToFloat methods convert floating point values to their integer bit representations and back. The Float class also has a group of final static (constant) data members: MINVALUE, MAXVALUE, NEGATIVE_INFINITY, POSITIVE_INFINITY, and NaN. MINVALUE and MAXVALUE specify the smallest and largest numbers that can be represented by a Float object. NEGATIVE_INFINITY and POSITIVE_INFINITY represent negative and positive infinity, while NaN represents the special not-a-number condition. The Double object is very similar to the Float object. The only difference is that Double wraps the fundamental type double instead of float. Double implements similar methods as Float, with the exception that the methods act on double rather than float type numbers.
The Math Class The Math class contains many invaluable mathematical functions along with a few useful constants. The Math class isn’t intended to be instantiated; it is basically just a holding class for mathematical functions. Additionally, the Math class is declared as final so you can’t derive from it. The most useful methods implemented by the Math class follow:
static double sin(double a) static double cos(double a) static double tan(double a) static double asin(double a) static double acos(double a) static double atan(double a) static double exp(double a) static double log(double a) static double sqrt(double a) static double pow(double a, double b) static double ceil(double a) static double floor(double a) static int round(float a) static long round(double a) static double rint(double a) static double atan2(double a, double b) static synchronized double random() static int abs(int a)
Java Unleashed
Page 152
static long abs(long a) static float abs(float a) static double abs(double a) static int min(int a, int b) static long min(long a, long b) static float min(float a, float b) static double min(double a, double b) static int max(int a, int b) static long max(long a, long b) static float max(float a, float b) static double max(double a, double b) The trigonometric methods sin, cos, tan, asin, acos, and atan perform the standard trigonometric functions on double values. All the angles used in the trigonometric functions are specified in radians. Following is an example of calculating the sine of an angle: double dSine = Math.sin(Math.PI / 2); Notice in the example that the PI constant member of the Math class was used in the call to the sin method. You’ll learn about the PI constant member variable of Math at the end of this section. The exp method returns the exponential number E raised to the power of the double parameter a. Similarly, the log method returns the natural logarithm (base E) of the number passed in the parameter a. The sqrt method returns the square root of the parameter number a. The pow method returns the result of raising a number to a power. pow returns a raised to the power of b. Following are some examples of using these math methods: double double double double double
d1 d2 d3 d4 d5
= = = = =
12.3; Math.exp(d1); Math.log(d1); Math.sqrt(d1); Math.pow(d1, 3.0);
The ceil and floor methods return the “ceiling” and “floor” for the passed parameter a. The ceiling is the smallest whole number greater than or equal to a, where the floor is the largest whole number less than or equal to a. The round methods round float and double numbers to the nearest integer value, which is returned as type int or long. Both round methods work by adding 0.5 to the number and then returning the largest integer that is less than or equal to the number. The rint method returns an integral value, similar to round, that remains a type double. Following are some examples of using these methods: double d1 = 37.125; double d2 = Math.ceil(d1); double d3 = Math.floor(d1); int i = Math.round((float)d1); long l = Math.round(d1); double d4 = Math.rint(d1); Notice in the first example of using round that the double value d1 must be explicitly cast to a float. This is necessary because this version of round takes a float and returns an int. The atan2 method converts rectangular coordinates to polar coordinates. The double parameters a and b represent the rectangular x and y coordinates to be converted to polar coordinates, which are returned as a double value.
Java Unleashed
Page 153
The random method generates a pseudo-random number between 0.0 and 1.0. random is useful for generating random floating point numbers. To generate random numbers of different types, you should use the Random class, which is located in the utilities package, java.util. The utilities package, including the Random class, is covered in the next chapter. The abs methods return the absolute value of numbers of varying types. There are versions of abs for working with the following types: int, long, float, and double. Following is an example of using the abs method to find the absolute value of an integer number: int i = -5, j; j = Math.abs(i); The min and max methods return the minimum and maximum numbers given a pair of numbers to compare. Like the abs methods, the min and max methods come in different versions for handling the types int, long, float, and double. Following is an example of using the min and max methods: double d1 = 14.2, d2 = 18.5; double d3 = Math.min(d1, d2); double d4 = Math.max(d1, 11.2); Beyond the rich set of methods provided by the Math class, there are also a couple of important constant member variables: E and PI. The E member represents the exponential number (2.7182...) used in exponential calculations, where PI represents the value of Pi (3.1415...).
String Classes Unlike C and C++, text strings in Java are represented with classes rather than character arrays. The two classes that model strings in Java are String and StringBuffer. The reason for having two string classes is that the String class represents constant (immutable) strings and the StringBuffer class represents variable (mutable) strings.
The String Class The String class is used to represent constant strings. The String class has less overhead than StringBuffer, which means you should try to use it if you know that a string is constant. The creation methods for the String class follow:
String() String(String value) String(char value[]) String(char value[], int offset, int count) String(byte ascii[], int hibyte, int offset, int count) String(byte ascii[], int hibyte) String(StringBuffer buffer) It is readily apparent from the number of creation methods for String that there are many ways to create String objects. The first creation method simply creates a new string that is empty. All of the other creation methods create strings that are initialized in different ways from various types of text data. Following are examples of using some of the String creation methods to create String objects: String s1 = new String s2 = new char cArray[] = String s3 = new String s4 = new
String(); String(“Hello”); {‘H’, ‘o’, ‘w’, ‘d’, ‘y’}; String(cArray); String(cArray, 1, 3);
Java Unleashed
Page 154
In the first example, an empty String object (s1) is created. In the second example, a String object (s2) is created from a literal String value, “Hello”. The third example shows aString object (s3) being created from an array of characters. Finally, the fourth example shows a String object (s4) being created from a subarray of characters. The subarray is specified by passing 1 as the offset parameter and 3 as the count parameter. This means that the subarray of characters is to consist of the first three characters starting at one character into the array. The resulting subarray of characters in this case consists of the characters ‘o’, ‘w’, and ‘d’. Once you have some String objects created, you are ready to work with them using some of the powerful methods implemented in the String class. Some of the most useful methods provided by the String class follow:
int length() char charAt(int index) boolean startsWith(String prefix) boolean startsWith(String prefix, int toffset) endsWith(String suffix) int indexOf(int ch) int indexOf(int ch, int fromIndex) int indexOf(String str) int indexOf(String str, int fromIndex) int lastIndexOf(int ch) int lastIndexOf(int ch, int fromIndex) int lastIndexOf(String str) int lastIndexOf(String str, int fromIndex) String substring(int beginIndex) String substring(int beginIndex, int endIndex) boolean equals(Object anObject) boolean equalsIgnoreCase(String anotherString) int compareTo(String anotherString) String concat(String str) String replace(char oldChar, char newChar) String trim() String toLowerCase() String toUpperCase() static String valueOf(Object obj) static String valueOf(char data[]) static String valueOf(char data[], int offset, int count) static String valueOf(boolean b) static String valueOf(char c) static String valueOf(int i)
Java Unleashed
Page 155
static String valueOf(long l) static String valueOf(float f) static String valueOf(double d) The length method simply returns the length of a string, which is the number of Unicode characters in the string. The charAt method returns the character at a specific index of a string specified by the int parameter index. The startsWith and endsWith methods determine whether or not a string starts or ends with a prefix or suffix string, as specified by the prefix and suffix parameters. The second version of startsWith enables you to specify an offset to begin looking for the string prefix. Following are some examples of using these methods: String s1 = new String(“This is a test string!”); int len = s1.length(); char c = s1.charAt(8); boolean b1 = s1.startsWith(“This”); boolean b2 = s1.startsWith(“test”, 10); boolean b3 = s1.endsWith(“string.”); In this series of examples, a String object is first created with the value “This is a test string!”. The length of the string is calculated using the length method, and stored in the integer variable len. The length returned is 22, which specifies how many characters are contained in the string. The character at offset 8 into the string is then obtained using the charAt method. Like C and C++, Java offsets start at 0, not 1. If you count eight characters into the string, you can see that charAt returns the ‘a’ character. The next three examples use the startsWith method to determine if specific strings are located in the String object. The first startsWith example looks for the string “This” at the beginning of the String object. This returns true because the string is in fact located at the beginning of the String object. The second startsWith example looks for the string “test” beginning at offset 10 into the String object. This call also returns true because the string “test” is located ten characters into the String object. The last example uses the endsWith method to check for the occurrence of the string “string.” at the end of the String object. This call returns false because the String object actually ends with “string!”. The indexOf methods return the location of the first occurrence of a character or string within a String object. The first two versions of indexOf determine the index of a single character within a string, while the second two versions determine the index of a string of characters within a string. Each pair of indexOf methods contain a version for finding a character or string based on the beginning of the String object, as well a version that enables you to specify an offset into the string to begin searching for the first occurrence. If the character or string is not found, indexOf returns -1. The lastIndexOf methods work very much like indexOf, with the exception that lastIndexOf searches backwards through the string. Following are some examples of using these methods: String int i1 int i2 int i3
s1 = new String(“Saskatchewan”); = s1.indexOf(‘t’); = s1.indexOf(“chew”); = s1.lastIndexOf(‘a’);
In this series of examples, a String object is created with the value 93"Saskatchewan”. The indexOf method is then called on this string with the character value ‘t’. This call to indexOf returns 5, since the first occurrence of ‘t’ is five characters into the string. The second call to indexOf specifies the string literal “chew”. This call returns 6, since the substring “chew” is located six characters into the String object. Finally, the lastIndexOf method is called with a character parameter of ‘a’. The call to lastIndexOf returns 10, indicating the third ‘a’ in the string. Remember, lastIndexOf searches backward through the string to find the first occurrence of a character. The substring methods return a substring of the calling String object. The first version of substring returns the substring beginning at the index specified by beginIndex, through the end of the calling String object. The second version of substring returns a substring beginning at the index specified by beginIndex and ending at the index specified by endIndex. Following is an example of using one of the substring methods:
Java Unleashed
Page 156
String s1 = new String(“sasquatch”); String s2 = s1.substring(3); String s3 = s1.substring(2, 7); In this example, a String object is created with the value “sasquatch”. A substring of this string is then retrieved using the substring method and passing 3 as the beginIndex parameter. This results in the substring “quatch”, which begins at the string index of three and continues through the rest of the string. The second version of substring is then used with starting and ending indices of 2 and 7, yielding the substring “squat”. There are two methods for determining equality between String objects: equals and equalsIgnoreCase. The equals method returns a Boolean value based on the equality of two strings. isEqualNoCase performs a similar function, except it compares the strings with case insensitivity. Similarly, the compareTo method compares two strings and returns an integer value that specifies whether the calling String object is less than, greater than, or equal to the anotherString parameter. The integer value returned by compareTo specifies the numeric difference between the two strings; it is a positive value if the calling String object is greater, and negative if the passed String object is greater. If the two strings are equal, the return value is 0. Wait a minute—if strings are just text, how can you get a numeric difference between two strings, or establish which one is greater than or less than the other? When strings are compared using the compareTo method, each character is compared to the character at the same position in the other string, until they don’t match. When two characters are found that don’t match, compareTo converts them to integers and finds the difference. This difference is what is returned by compareTo. Check out the following example to get a better idea of how this works: String s1 = new String(“abcfj”); String s2 = new String(“abcdz”); System.out.println(s1.compareTo(s2)); Each pair of characters is compared until two are encountered that don’t match. In this example, the ‘f’ and ‘d’ characters are the first two that don’t match. Since the compareTo method is called on the s1 String object, the integer value of ‘d’ (100) is subtracted from the integer value of ‘f’ (102) to determine the difference between the strings. Notice that all characters following the two nonmatching characters are ignored in the comparison. The concat method is used to concatenate two String objects. The string specified in the str parameter is concatenated onto the end of the calling String object. Following are a few examples of string concatenation: String s1 = new String(“I saw sasquatch “); String s2 = new String(s1 + “in Saskatchewan.”); String s3 = s1.concat(“in Saskatchewan.”); In these concatenation examples, a String object is first created with the value “I saw sasquatch“. The first concatenation example shows how two strings can be concatenated using the addition operator (+). The second example shows how two strings can be concatenated using the concat method. In both examples, the resulting string is the sentence “I saw sasquatch in Saskatchewan.”. The replace method is used to replace characters in a string. All occurrences of oldChar are replaced with newChar. Using the strings from the previous concatenation examples, you could replace all of the s characters with m characters like this: String s4 = s3.replace(‘s’, ‘m’); This results in the string “I maw mamquatch in Samkatchewan.”. Notice that the uppercase ‘S’ character wasn’t replaced. The trim method trims leading and trailing whitespace off of a String object. The toLowerCase and toUpperCase methods are used to convert all of the characters in a String object to lower and uppercase. Following are some examples of using these methods using the strings from the previous two examples:
Java Unleashed String String String String
s5 s6 s7 s8
= = = =
Page 157
new String(“\t Yeti\n”); s5.trim(); s3.toLowerCase(); s4.toUpperCase();
In this example, the trim method is used to strip off the leading and trailing whitespace, resulting in the string “Yeti”. The call to toLowerCase results in the string “i saw sasquatch in saskatchewan.”. The only character modified was the ‘I’ character, which was the only uppercase character in the string. The call to toUpperCase results in the string “I MAW MAMQUATCH IN SAMKATCHEWAN.”. All of the lowercase characters were converted to uppercase, as you might have guessed! Finally, the valueOf methods all return String objects that represent the particular type taken as a parameter. For example, the valueOf method that takes an int will return the string “123” when passed the integer number 123.
The StringBuffer Class The StringBuffer class is used to represent variable, or non-constant, strings. The StringBuffer class is useful when you know that a string will change in value or in length. The creation methods for the StringBuffer class follow:
StringBuffer() StringBuffer(int length) StringBuffer(String str) The first creation method simply creates a new string buffer that is empty. The second creation method creates a string buffer that is length characters long, initialized with spaces. The third creation method creates a string buffer from a String object. This last creation method is useful when you need to modify a constant String object. Following are examples of using the StringBuffer creation methods to create StringBuffer objects: String String String String
s1 = new String(“This is a string!”); sb1 = new StringBuffer(); sb2 = new StringBuffer(25); sb3 = new StringBuffer(s1);
Some of the most useful methods implemented by StringBuffer follow:
int length() int capacity() synchronized void setLength(int newLength) synchronized char charAt(int index) synchronized void setCharAt(int index, char ch) synchronized StringBuffer append(Object obj) synchronized StringBuffer append(String str) synchronized StringBuffer append(char c) synchronized StringBuffer append(char str[]) synchronized StringBuffer append(char str[], int offset, int len) StringBuffer append(boolean b) StringBuffer append(int I) StringBuffer append(long l)
Java Unleashed
Page 158
StringBuffer append(float f) StringBuffer append(double d) synchronized StringBuffer insert(int offset, Object obj) synchronized StringBuffer insert(int offset, String str) synchronized StringBuffer insert(int offset, char c) synchronized StringBuffer insert(int offset, char str[]) StringBuffer insert(int offset, boolean b) StringBuffer insert(int offset, int I) StringBuffer insert(int offset, long l) StringBuffer insert(int offset, float f) StringBuffer insert(int offset, double d) String toString() The length method is used to get the length, or number of characters in the string buffer. The capacity method is similar to length except it returns how many characters a string buffer has allocated in memory, which is sometimes greater than the length. Characters are allocated for a string buffer as they are needed. Many times more memory is allocated for a string buffer than is actually being used. In these cases, the capacity method will return the amount of memory allocated for the string buffer. You can explicitly change the length of a string buffer using the setlength method. An example of using setLength would be to truncate a string by specifying a shorter length. The following example illustrates the effects of using these methods: StringBuffer s1 = new StringBuffer(14); System.out.println(“capacity = “ + s1.capacity()); System.out.println(“length = “ + s1.length()); s1.append(“Bigfoot”); System.out.println(s1); System.out.println(“capacity = “ + s1.capacity()); System.out.println(“length = “ + s1.length()); s1.setLength(3); System.out.println(s1); System.out.println(“capacity = “ + s1.capacity()); System.out.println(“length = “ + s1.length()); The resulting output of this example follows: capacity length = Bigfoot capacity length = Big capacity length =
= 14 0 = 14 7 = 14 3
In this example, the newly created string buffer shows a capacity of 14 (based on the value passed in the creation method) and a length of 0. After appending the string “Bigfoot” to the buffer, the capacity remains the same but the length grows to 7, which is the length of the string. Calling setLength with a parameter of 3 truncates the length down to 3, but leaves the capacity unaffected at 14.
Java Unleashed
Page 159
The charAt method returns the character at the location in the string buffer specified by the index parameter. You can change characters at specific locations in a string buffer using the setCharAt method. The setCharAt method replaces the character at index with the ch character parameter. The following example illustrates these two methods: StringBuffer s1 = new StringBuffer(“I saw a Yeti in Yellowstone.”); char c1 = s1.charAt(9); System.out.println(c1); s1.setCharAt(4, ‘r’); System.out.println(s1); In this example, the call to charAt results in the character ‘e’, which is located 9 characters into the string. The call to setCharAt results in the following output, based on the ‘w’ in “saw” being replaced by ‘r’: I sar a Yeti in Yellowstone. The StringBuffer class implements a variety of overloaded append methods. The append methods allow you to append various types of data onto the end of a String object. Each append method returns the String object that it was called on. The insert methods enable you to insert various data types at a specific offset in a string buffer. insert works very similar to append, with the exception of where the data is placed. Following are some examples of using append and insert: StringBuffer sb1 = new StringBuffer(“2 + 2 = “); StringBuffer sb2 = new StringBuffer(“The tires make contact “); sb1.append(2 + 2); sb2.append(“with the road.”); sb2.insert(10, “are the things on the car that “); In this set of examples, two string buffers are first created using the creation method for StringBuffer that takes a string literal. The first StringBuffer object initially contains the string “2 + 2 = “. The append method is used to append the result of the integer calculation 2 + 2. In this case, the integer result 4 is converted by the append method to the string “4” before it is appended to the end of the StringBuffer object. The value of the resulting StringBuffer object is “2 + 2 = 4”. The second string buffer object begins life with the value “The tires make contact “. The string “with the road.” is then appended onto the end of the string buffer using the append method. Then the insert method is used to insert the string “are the things on the car that “. Notice that this string is inserted at index 10 within the StringBuffer object. The resulting string after these two methods are called follows: The tires are the things on the car that make contact with the road. The last method of interest in StringBuffer is the toString method. toString returns the String object representation of the calling StringBuffer object. toString is useful when you have a StringBuffer object but need a String object.
System and Runtime Classes The System and Runtime classes provide access to the system and runtime environment resources. The System class is defined as final and is composed entirely of static variables and methods, which means you will never actually instantiate an object of it. The Runtime class provides direct access to the runtime environment, and is useful for executing system commands and determining things like the amount of available memory.
The System Class The System class contains the following useful methods:
static long currentTimeMillis() static void arraycopy(Object src, int src_position, Object dst, int dst_position, int length) static Properties getProperties() static String getProperty(String key)
Java Unleashed
Page 160
static String getProperty(String key, String def) static void setProperties(Properties props) static void gc() static void loadLibrary(String libname) The currentTimeMillis method returns the current system time in milliseconds. The time is specified in GMT (Greenwich Mean Time), and reflects the number of milliseconds that have elapsed since midnight on January 1, 1970. This is a standard frame of reference for computer time representation. The arraycopy method copies data from one array to another. arraycopy copies length elements from the src array beginning at position src_position to the dst array starting at dst_position. The getProperties method gets the current system properties and returns them via a Properties object. There are also two getProperty methods in System that allow you to get individual system properties. The first version of getProperty returns the system property matching the key parameter passed into the method. The second version of getProperty does the same as the first except it returns the default def parameter if the property isn’t found. The setProperties method takes a Properties object and sets the system properties with it. The gc method stands for garbage collection and does exactly that. gc forces the Java runtime system to perform a memory garbage collection. You can call gc if you think the system is running low on memory, since a garbage collection will usually free up memory. The Java system supports executable code in dynamic link libraries. A dynamic link library is a library of Java classes that can be accessed at runtime. The loadLibrary method is used to load a dynamic link library. The name of the library to load is specified in the libname parameter. The System class contains three member variables that are very useful for interacting with the system: in, out, and err. The in member is an InputStream object that acts as the standard input stream. The out and err members are PrintStream objects that act as the standard output and error streams.
The Runtime Class The Runtime class is another very powerful class for accessing Java system-related resources. Following are a few of the more useful methods in the Runtime class:
static Runtime getRuntime() long freeMemory() long totalMemory() void gc() synchronized void loadLibrary(String libname) The static method getRuntime returns a Runtime object representing the runtime system environment. The freeMemory method returns the amount of free system memory in bytes.Because freeMemory returns only an estimate of the available memory, it is not completely accurate. If you need to know the total amount of memory accessible by the Java system, you can use the totalMemory method. The totalMemory method returns the number of bytes of total memory, where the freeMemory method returns the number of bytes of available memory. Listing 18.1 contains the source code for the Memory program, which displays the available free memory and total memory.
Listing 18.1. The Memory class.
Java Unleashed
Page 161
class Memory { public static void main (String args[]) { Runtime runtime = Runtime.getRuntime(); long freeMem = runtime.freeMemory() / 1024; long totalMem = runtime.totalMemory() / 1024; System.out.println(“Free memory : “ + freeMem + “KB”); System.out.println(“Total memory : “ + totalMem + “KB”); } }
An example of the output of running the Memory program follows: Free Memory : 3068KB Total Memory : 3071KB The Memory class uses the getRuntime, freeMemory, and totalMemory methods of the Runtime class. Note that the amount of memory returned by each method is converted from bytes to kilobytes by dividing by 1024. The other two methods of importance in the Runtime class, gc and loadLibrary, work exactly the same as the versions belonging to the System class.
Class Classes Java provides two classes in the language package for dealing with classes: Class and ClassLoader. The Class class allows you access to the runtime information for a class. The ClassLoader class provides support for dynamically loading classes at runtime.
The Class Class Some of the more useful methods implemented by the Class class follow:
static Class forName(String className) String getName() Class getSuperclass() ClassLoader getClassLoader() boolean isInterface() String toString() The forName method is a static method used to get the runtime class descriptor object for a class. The String parameter className specifies the name of the class you want information for. forName returns a Class object containing runtime information for the specified class. Notice that forName is static and is the method you typically will use to get an instance of the Class class for determining class information. Following is an example of how to use the forName method to get information about the StringBuffer class: Class info = Class.forName(“java.lang.StringBuffer”); The getName method retrieves the string name of the class represented by a Class object. Following is an example of using the getName method: String s = info.getName();
Java Unleashed
Page 162
The getSuperclass method returns a Class object containing information about the superclass of an object. The getClassLoader method returns the ClassLoader object for a class, or null if no class loader exists. The isInterface method returns a Boolean indicating whether or not a class is an interface. Finally, the toString method returns the name of a class or interface. toString automatically prepends the string “class” or “interface” to the name based on whether the Class object represents a class or an interface.
The ClassLoader Class The ClassLoader class provides the framework for enabling you to dynamically load classes into the runtime environment. Following are the methods implemented by ClassLoader:
abstract Class loadClass(String name, boolean resolve) final Class defineClass(byte data[], int offset, int length) final void resolveClass(Class c) final Class findSystemClass(String name) The loadClass method is an abstract method that must be defined in a subclass of ClassLoader. loadClass resolves a class name passed in the String parameter name into a Class runtime object. loadClass returns the resulting Class on success, or null if not successful. The defineClass method converts an array of byte data into a Class object. The class is defined by the data parameter beginning at offset and continuing for length bytes. A class defined with defineClass must be resolved before it can be used. You can resolve a class by using the resolveClass method, which takes a Class object as its only parameter. Finally, the findSystemClass method is used to find and load a system class. A system class is a class that uses the built in (primordial) class loader, which is defined as null.
Summary In this chapter you learned a great deal about the classes and interfaces that make up the Java language package. The language package lays out the core classes, interfaces, and errors of the Java class libraries. Although some of the classes implemented in the language package are fairly low-level, a solid understanding of these classes is necessary to move on to other areas of the Java class libraries. You learned in this chapter how fundamental data types can become objects using the data type wrappers. You then learned about the many mathematical functions contained in the Math class. And don’t forget about the string classes, which provide a powerful set of routines for working with strings of text. You finished up with a tour of how to access the system and runtime resources of Java, along with the lower-level runtime and dynamic class support. The next chapter provides the next stop on this guided tour of the Java class libraries, which is the Java utilities package.
Java Unleashed
Page 163
Chapter 19 The utilities package The Utilities package provides a collection of classes that implement various standard programming data structures. These classes are useful in a variety of ways and are the fundamental building blocks of the more complicated data structures used in the other Java packages and in your own applications. Unless otherwise noted, all of the interfaces and classes discussed in this chapter extend the java.lang.Object class. Table 19.1 lists the classes and interfaces implemented in this package along with a brief description of each. Table 19.1. Utilities package interfaces and classes.*
*Interfaces Enumeration Observer
Interface for classes that can enumerate a vector. Interface for classes that can observe observable objects.
* *Classes BitSet Date Dictionary Hashtable Observable Properties Random Stack StringTokenizer Vector
Used to store a collection of binary values. Used to store date and time data. Used to store a collection of key and value pairs. Used to store a hash table. Used to store observable data. Used to store a properties list that can be saved. Used to generate a pseudo-random number. Used to store a stack. Used to tokenize a string. Used to store a vector data type
. Interfaces The Utilities package has two interfaces that can be used in classes of your own design—Enumeration and Observer. Interfaces are a set of methods that must be written for any class that claims to “implement” the interface. This provides a consistent way of using all classes that implement the interface. The Enumeration interface is used for classes that can retrieve data from a list, element by element. For example, there is an Enumeration class in the Utilities package that implements the Enumeration interface for use in conjunction with the Vector class. The Observer interface is useful in designing classes that can watch for changes that occur in other classes.
Enumeration This interface specifies a set of methods used to enumerate—that is, iterate through—a list. An object that implements this interface may be used to iterate through a list only once because the Enumeration object is consumed through its use. For example, an Enumeration object can be used to print all the elements of a Vector object, v, as follows: for (Enumeration e=v.elements();e.hasMoreElements();) System.out.print(e.nextElement()+” “); The Enumeration interface specifies only two methods—hasMoreElements() and nextElement(). The hasMoreElements() method must return True if there are elements remaining in the enumeration. The nextElement() method must return an object representing the next element within the object that is being enumerated. The details of how the Enumeration interface is implemented and how the data is represented internally are left up to the implementation of the specific class. See also: Dictionary, Hashtable, Properties, Vector.
Java Unleashed
Page 164
Observer This interface, if implemented by a class, allows an object of the class to observe other objects of the class Observable. The observer is notified whenever the Observable object that it is watching has been changed. The interface only specifies one method, update(Observable, Object). This method is called by the observed object to notify the observer of changes. A reference to the observed object is passed along with any additional object that the observed object wishes to pass to the observer. The first argument enables the observer to operate on the observed object, while the second argument is used to pass information from the observed to the observer.
Classes The Utilities package supplies ten different classes that provide a wide variety of functionality. While these classes don’t generally have much in common, they all provide support for the most common data structures used by programmers.
BitSet This class implements a data type that represents a collection of bits. The collection will grow dynamically as more bits are required. It is useful for representing a set of True/False values. Specific bits are identified using non-negative integers. The first bit is bit 0 (see Figure 19.1). FIGURE 19.1 Example of a BitSet object. This class is most useful for storing a group of related True/False values such as user responses to Yes/No questions. Individual bits in the set are turned on or off with the set() and clear() methods, respectively. Individual bits are queried with the get() method. These methods all take the specific bit number as their only argument. The basic Boolean operations AND, OR, and XOR can be performed on two BitSets using the and(), or(), and xor() methods. Because these methods modify one of the BitSets, one generally will use the clone() method to create a duplicate of one, and then AND, OR, or XOR the clone with the second BitSet. The result of the operation then will end up in the cloned BitSet. The BitSet1 program in Listing 19.1 illustrates the basic BitSet operations, whereas Table 19.2 summarizes all the various methods available in the BitSet class.
Listing 19.1. BitSet1.java—BitSet example program.
Java Unleashed import java.io.DataInputStream; import java.util.BitSet; class BitSet1 { public static void main(String args[]) throws java.io.IOException { DataInputStream dis=new DataInputStream(System.in); String bitstring; BitSet set1,set2,set3; set1=new BitSet(); set2=new BitSet(); // Get the first bit sequence and store it System.out.println(“Bit sequence #1:”); bitstring=dis.readLine(); for (short i=0;i=0; x--) { number[x] = new Button((new String()).valueOf(x)); num_panel.add(number[x]); } function[4] = new Button(“.”); num_panel.add(function[4]); function[5] = new Button(“=”); num_panel.add(function[5]); bottom.add(“Center”, num_panel); func_panel = new Panel(); func_panel.setLayout(new GridLayout(4,1)); function[0] = new Button(“+”); function[1] = new Button(“-”); function[2] = new Button(“*”); function[3] = new Button(“/”); for (int x=0; x 0) LoadImages.addImage(slides[i], 1); else LoadImages.addImage(slides[i], 0); } try { LoadImages.waitForID(0); } catch (InterruptedException e) { System.out.println(“Image Loading Failed!”); } showStatus(imagefile + “ Images Loaded”); index = 0; repaint( ); LoadImages.checkID(1,true); } In the preceding code, we create a media tracking object called LoadImages which will be responsible for tracking the loading of our images. We’ve created an array, slides[], to store the images that are loaded, and created an int variable images to represent the total number of images. The next step is to use our media tracking object in conjunction with getImage( ) to load our slides. We use the MediaTracker method addImage( ) to add each image to the list of images being tracked, and then we use checkID( ) with the true boolean to make sure that the images are loaded before proceeding. Using MediaTracker to help you monitor status can be a safeguard for your applets. If you have an image-based animation, or some other graphics-intensive applet, it might be wise to use MediaTracker. As the name implies, MediaTracker is designed to be used with any media—graphics, audio, and so on. However, the current implementation of MediaTracker only supports images.
Audio The Applet Package also contains methods for working with audio files. (See Table 23.2.) Table 23.2. Audio methods. getAudioClip( ) play( ) loop( ) stop( ) The getAudioClip( ) method can be found in java.applet.Applet and is used in the same manner as getImage( ). The remaining audio methods are contained in the AudioClip interface and exist to manipulate the file itself. These functions include the following: play( ) Play an audio file until the end of the file. loop( ) Play an audio file until the end, and repeat. stop( ) Stop playing a sound file.
Java Unleashed
Page 233
Loading and playing an audio file operates much like loading and playing images, as shown in the following: import java.applet.*; import java.lang.*; import java.net.URL; public class PlaySound extends Applet { AudioClip sound; public void init( ) { sound = getAudioClip(getDocumentBase( ), “hi.au”); } public void start( ) { sound.play( ); } }
Audio Limitations Unfortunately, the methods currently provided for audio are quite limited. The audio methods currently only support .au format sound files. Additionally, the methods provided aren’t very robust. You only can play a sound file. There is no way to pause the file or clip a sound file. The lack of audio context methods means that your applets only can incorporate limited audio features, but the AudioClip interface is certainly an area that will be slated for improvement in future releases of the JDK.
Applet Contexts The AppletContext interface contains methods that are useful for manipulating an applet’s environment. These methods allow the applet to exchange information with the browser to update web pages and to communicate applet status to the browser. The Applet Package contains the method getAppletContext( ), which allows the applet to obtain information about its environment, the AppletViewer, or a Web browser. This context information can then be used by the applet to the environment.
Using showDocument( ) Applets can be used to manipulate the browser itself. For example, an applet could function as an animated button on a Web page. An applet also could serve as a dynamic image map, providing users with instant status updates about the URLs contained in the map. In order to do that, an applet needs to be able to issue instructions to the browser. One of those instructions is showDocument( ). The showDocument( ) method can be used to send the browser to a new URL. It is evoked in the following manner: try { getAppletContext( ).showDocument(new URL(url)); } catch (java.net.MalformedURLException e) { System.out.println(“URL Unreachable”); } This line of code obtains information about the environment that the applet running in and instructs that environment to go to another URL. Because of the nature of showDocument( ), it is important to monitor for errors. The try and catch syntax is very important when using showDocument( ). Because the method causes the browser to search over the Net for a URL, the success of this method depends on the availability of the given URL. Should the browser not be able to reach the specified URL, your applet needs to be instructed to catch any errors that might be generated.
Using showStatus( ) Often, an applet will want to communicate the status of an operation to the user. This can be accomplished with the showStatus( ) method. For example, when we looked at loading images using MediaTracker, the code contained the following line: showStatus(imagefile + “ Images Loaded”);
Java Unleashed
Page 234
This line of code called the showStatus( ) method to report when the images were finished loading. When an applet engages in tasks that might take a long time, showStatus( ) can be used to keep users informed of an applet’s progress. This can be advantageous when troubleshooting, and adds a level of user friendliness to your applets.
Getting Parameters One of the most important aspects of the HTML files that evoke Java applets is the <param> tag. The parameter tag allows the applet user to specify the value of variables that will be passed to the applet. When your applet loads images or sound files, using parameters to pass the filenames to the applet can make your applet more flexible. Parameters also can be used to change the functionality of your applet. For example, the amount of delay between images in the SlideShow applet is defined by a parameter. The format for specifying parameters in an HTML files is as follows: <param name=”caption” value=”A Sample Photo Album”> name specifies the parameter name and value specifies the parameter value passed to the applet. Parameters are established with the getParameterInfo( ) method. This method establishes an array containing an applet’s parameters and an information string that describes each parameter’s function. Let’s add the capability to accept parameters to our SlideShow example: public class SlideShow extends Applet implements Runnable { String imagefile, soundfile, type, caption; int images, delay; public String[][] getParameterInfo( ) { String[][] info = { {“caption”, “string”, “Title of the Slide Viewer”}, {“imagefile”, “string”, “Base Image File Name (ex. picture)”}, {“soundfile”, “string”, “Sound File Name (ex. audio.au)”}, {“images”, “int”, “Total number of image files”}, {“delay”, “int”, “Delay in between images (in ms.)”}, {“type”, “string”, “Image File Type(gif, jpg, etc)”}, }; return info; } public void init( ) { caption = getParameter(“caption”); imagefile = getParameter(“imagefile”); soundfile = getParameter(“soundfile”); type = getParameter(“type”); images = Integer.valueOf(getParameter(“images”)).intValue( ); delay = Integer.valueOf(getParameter(“delay”)).intValue( ); } } For example, in the code above, the line {“caption”, “string”, “Title of the Slide Viewer”}, establishes the caption parameter, which is a string that represents the title of the SlideShow. Once parameters have been defined, they can be accessed using the getParameter( ) method. This method is used to obtain the value of a parameter and assign it to a variable within your applet. In the case of the SlideShow, parameters are used to specify the base imagefile name, the soundfile name, the number of images, the title of the viewer, and the delay between slides for the automatic slideshow option.
Java Unleashed
Page 235
Graphics One of Java’s primary attractions is the ability to spice up Web pages. What is a better way to spice up Web pages than animation and interaction? Before we can cover animation in Chapter 25 it is necessary to cover some basics concerning graphics in Java. *
*NOTE The Graphics( ) methods are actually a part of the Abstract Windows Toolkit. However, because the AWT’s scope is so broad, we’ve broken the AWT up to make it a little more manageable. Keep in mind that graphics are listed under the AWT when consulting the API. Java contains a number of graphics primitives that can enable you to begin creating basic shapes and minimal on-screen graphics quite quickly. Table 23.3 is a summary of some of the more useful Graphics methods. Keep in mind that these methods must be called within a graphics context. Java needs to be made aware of the graphics object you are manipulating in order to draw an on-screen graphic. For example //Draw a Line import java.awt.*; import java.applet.*; public class Line extends java.applet.Applet { public void paint(Graphics g) { g.drawLine(50,50,100,150); } public void start( ) { repaint( ); } } produces the following output shown in Figure 23.1.
FIGURE 23.1.
A line drawn using the drawLine ( ) method. Here, the drawLine( ) method is used to draw a line between the points x1,y1 and x2,y2. The drawLine( ) method is evoked with the following line: g.drawLine(50,50,100,150); This line specifies the default graphics context (graphics object g), and a line from the point (50,50) to (100,150). Table 23.3. A summary of Graphics contexts.*
*
*Result
Method
Graphics( ) create( ) dispose( ) clearRect( ) clipRect( ) drawLine( ) (x1,y1) and (x2,y2). drawRect( ) drawRoundRect( ) draw3DRect( ) drawOval( ) drawPolygon( ) drawString( ) fillRect( )
Constructs a new Graphics object. Creates a new Graphics object. Disposes of the current graphics context. Clears the specified rectangle using the current background color. Clips to a rectangle. Draws a line between two points Draws a rectangle using the current color. Draws a rounded corner rectangle using the current color. Draws a 3-D rectangle. Draws an oval using the current color. Draws a polygon using an array of x points and y points. Draws a string using the current font and color. Fills a rectangle using the current color.
Java Unleashed fillRoundRect( ) fill3DRect( ) fillOval( ) fillPolygon( ) getColor( ) setColor( ) getFont( ) setFont( )
Page 236 Draws a filled rounded rectangle. Paints a 3-D rectangle filled with the current color. Fills an oval using the current color. Fills a polygon with the current color. Gets the current color value. Sets the current color value. Gets the current font name. Sets the font for all text operations.
The methods used to create ovals and rectangles are called in a similar fashion. Each method accepts four integer values to specify the starting point of the shape and its dimensions. Here are some examples (see Figures 23.2 and 23.3) of drawRect( ) and drawOval( ): //Draw a Rectangle import java.awt.*; import java.applet.*; public class Rectangle extends java.applet.Applet { public void paint(Graphics g) { g.drawRect(50,50,100,150); } public void start( ) { repaint( ); } } FIGURE 23.2. A rectangle drawn with drawRect ( ). //Draw an Oval public void paint(Graphics g) { g.drawOval(50,50,100,150); }
FIGURE 23.3.
An oval drawn with drawOval ( ). Just as these methods can be used to create oval and rectangle outlines, fillRect( ) and fillOval( ) can be used to create solid shapes. (See Figure 23.4.) The code is quite similar: //Draw Filled Shapes import java.awt.*; import java.applet.*; public class Fills extends java.applet.Applet { public void paint(Graphics g) { g.setColor(Color.blue); g.fillRect(50,50,100,150); g.setColor(Color.red); g.fillOval(200,50,100,150); } public void start( ) { repaint( ); } } FIGURE 23.4. A filleed oval and filled rectangle drawn using fillRect ( ) and fillOval ( ). In addition to the basic rectangle and oval, the Graphics class also contains a method for creating polygons. The drawPolygon() method and fillPolygon() method can be used to create polygons containing any number of points. The methods accept integer arrays for the x and y values of the points, as in the following:
Java Unleashed
Page 237
drawPolygon(int[], int[], int) fillPolygon(int[], int[], int) The last integer accepted is the total number of points contained in the polygon and the closing point. When creating polygons, keep in mind the following caveats:
The integer arrays represent all of the x points and all of the y points, not the point pairs. This can be confusing at first, so keep careful track of the arrays. Any polygon you create must contain a closing point. Java does not automatically close polygons, so be sure to list all points including a common beginning and ending point. Keeping those ideas in mind, drawing a polygon (see Figure 24.5) can actually be quite easy, as in the following: //Draw a Polygon and Filled Polygon import java.awt.*; import java.applet.*; public class Poly extends java.applet.Applet { int x[] = {100,200,250,50,100}; int y[] = {50,50,200,200,50}; int a[] = {300,350,400,300}; int b[] = {200,50,200,200}; public void paint(Graphics g) { g.drawPolygon(x,y,5); g.setColor(Color.blue); g.fillPolygon(a,b,4); } public void start( ) { repaint( ); } } FIGURE 23.5. Examples of filled and unfilled polygons. In the preceding examples, we’ve also used the setColor() method. The setcolor method can be used to change the drawing color used by any method in the Graphics class. The setColor() method can accept a number of predefined color variables (such as red and blue) or can accept RGB color values. These are the basic graphics methods you need to get started creating images in your applets. Chapter 25 will discuss some more advanced uses for these methods, such as animation.
A Sample Applet: SlideShow SlideShow is a simple photo gallery application that can be used to set up a slide show of images (see Figure 23.6). The application accepts the following several parameters:
caption is the title that is displayed above the control buttons. You can use this parameter to personalize your slide show. imagefile is the base filename for the images to shown. The images should all share the same base filename and be numbered sequentially starting with zero. For example, image0.gif, image1.gif, and so on. It is not necessary to give the image numbers or extension type in this parameter, as they will be specified later.
Java Unleashed
Page 238
soundfile is the name of the soundfile to be played during the automatic slideshow. It should be the full filename, complete with extension. For example, sound.au. images is an integer value specifying the total number of images to be displayed. delay is an integer value for the delay between images in the AutoShow. type parameter specifies if the images are GIF or JPEG files. FIGURE 23.6. The SlideShow in action: an image of the author hard at work. The structure of this applet is fairly straightforward. The layout consists of the image to be displayed and some simple controls. The “AutoCycle images” button enables and disables the automatic slide show feature, and the “Sound On” button enables the user to turn the sound off. Previous and Next buttons are provided to enable users to cycle through the slides at their own pace. The applet itself loads the images using MediaTracker during the Init() state, and then begins a thread for the automatic show. The activity of the thread is then controlled by the check box. The check boxes and buttons are monitored for action events. If an action event is detected from one of the check boxes, the thread and audio are stopped or started as appropriate. The buttons also are monitored for action events, which simply increment or decrement the index counter. The end result is a simple SlideShow applet that enables you to have an automatic show or control your own images. The HTML file for the SlideShow applet is as follows: <param name=”caption” value=”A Sample Photo Album”> <param name=”imagefile” value=”image”> <param name=”soundfile” value=”song.au”> <param name=”images” value=”5"> <param name=”delay” value=”5000"> <param name=”type” value=”gif”> Listing 23.1 is the complete code for the SlideShow applet.
Listing 23.1. The complete SlideShow applet.
/* A simple Slide Viewer */ import java.awt.*; import java.lang.*; import java.applet.*; Java Unleashed Page 239 import java.net.URL; public class SlideShow extends Applet implements Runnable { MediaTracker LoadImages; Image slides[]; String imagefile, soundfile, type, caption; Thread AutoShow; int images, delay; int index = 0; Button forward, backward; Checkbox auto, sound; Label title; AudioClip clip; Panel marquee, control; public String[][] getParameterInfo() { String[][] info = { {“caption”, “string”, “Title of the Slide Viewer”}, {“imagefile”, “string”, “Base Image File Name (ex. picture)”}, {“soundfile”, “string”, “Sound File Name (ex. audio.au)”}, {“images”, “int”, “Total number of image files”}, {“delay”, “int”, “Delay in between images (in ms.)”}, {“type”, “string”, “Image File Type(gif, jpg, etc)”}, }; return info; } public void init() { //Parse the parameters from the HTML file LoadImages = new MediaTracker(this); caption = getParameter(“caption”); imagefile = getParameter(“imagefile”); soundfile = getParameter(“soundfile”); type = getParameter(“type”); images = Integer.valueOf(getParameter(“images”)).intValue(); slides = new Image[images]; delay = Integer.valueOf(getParameter(“delay”)).intValue(); //Use MediaTracker to load the images for (int i = 0; i < images; i++) { slides[i] = getImage(getDocumentBase(), imagefile + i + “.” + type); if (i > 0) LoadImages.addImage(slides[i], 1); else LoadImages.addImage(slides[i], 0); } try { LoadImages.waitForID(0); } catch (InterruptedException e) { System.out.println(“Image Loading Failed!”); } showStatus(imagefile + “ Images Loaded”); index = 0; repaint(); LoadImages.checkID(1,true); clip = getAudioClip(getDocumentBase(), soundfile); //Create the SlideViewer layout setLayout(new BorderLayout()); forward = new Button(“Next”); backward = new Button(“Previous”); auto = new Checkbox(“AutoCycle Images”); auto.setState(true); sound = new Checkbox(“Sound On”); sound.setState(true); title = new Label(caption); Panel marquee = new Panel(); marquee.setLayout(new BorderLayout()); marquee.add(“North”, title); Panel control = new Panel(); control.setLayout(new FlowLayout()); control.add(auto); control.add(sound); control.add(backward);
Java Unleashed
Page 240
Summary With the methods found in the AWT and in the Applet Package you have all the tools you need to create applets on your own. Understanding the lifecycle of applets can help you make your applets more efficient and ensure they function correctly. You should also be able to handle images and audio files and have a basic understanding of graphics. Now that the building blocks for applets are under your command, let’s move on to Chapter 24, “Programming Applets,” and look at some practical implementations of these techniques.
Java Unleashed
Page 241
Chapter 24 Programming Applets Previous chapters have covered all the separate elements needed to program applets. In this chapter we bring these concepts together. We first cover general applet design and then construct two practical applets.
Basic Applet Design Although applets can be programmed by trial and error, putting some effort into design ahead of time can save headaches later in the programming process. You wouldn’t build a house without a floor plan, so why jump into building an applet? Designing any program is not a trivial task, but if you take some time to figure out the major foundations of your applet, the coding will be much smoother. There are many facets to program design. What do you want your program to do? Who is the program being written for? This chapter assumes that you’ve already answered these fundamental questions and are ready to code your applet. We’ll concentrate on two very important aspects of applet design: user interface and class design.
User Interface Almost all applets involve some sort of visual interaction with the user. It is important to structure the interface near the beginning of the design process because changing the user interface later can be difficult. User interface (UI) is the subject of many books, and there are as many approaches to user interface as there are modern religions. For the sake of simplicity, we’ve tried to keep the applets in this section functional. When your are designing your UI, always keep functionality mind. Some other major areas to consider are layout, grouping, and user interaction.
Layout Choosing the wrong layout on a container can make the job of laying out the user interface painful. Different layouts have different strengths:
GridLayout is a natural choice when the designer needs to lay out the components in a rigid order. Because the layout follows the form of a grid, it’s a natural choice for elements such as keypads. BorderLayout fits better when there is a main working area to place in the center with toolbars on the periphery. Because the element in the center is allocated all remaining layout space, it can be useful for elements such as pictures. FlowLayout is a catch-all layout. You can add elements sequentially, and it spaces elements automatically. It can be suited to a variety of control situations. Grouping The judicious use of containers is extremely important in applet programming. If there is a logical distinction between user interface components, group them in a container. Remember, there are no restrictions on the number of elements and layouts you use in your applet. Grouping similar elements together and nesting layouts might be the most effective way to achieve the UI you want. It can also make moving or modifying elements later much more manageable.
User Interaction Any applet, no matter how big or small, should react logically to a user’s actions. For instance, if the applet’s main function is to play sound, clicking the mouse button should probably toggle the sound on and off. Always try to anticipate ways users will want to interact with your applet. Try to make your applets consistent with industry standards so users are not surprised by idiosyncratic quirks in the UI.
Java Unleashed
Page 242
Class Design Because Java is an object-oriented environment, it’s natural to split a program up into classes. Once you start to break applets down into different classes, however, you add to an applets complexity. Because a majority of applets are reasonably simple, most applets need only one class. Class design is a sensitive subject. Class design is a near-religious issue among many object-oriented programmers. The subject can be broken into two camps:
Minimalist this approach you ask yourself, “Do I really need another class here?” If an applet is small and no part of it will likely be used in the future, it usually makes sense to try to keep things as simple as possible and the number of classes to a minimum. This doesn’t mean trying to cram everything into one class, but using only enough to get the job done. Whenever another class is made, instances of it need to be declared and there will need to be interactions with it. Complexity is the bane of programming, and it makes sense to avoid it when possible. Complete this approach you attempt to model the desired program as a series of instances of classes, and design those classes to interact in ways to produce the desired response. This is arguably a superior approach to the minimalist approach, because reusable components tend to accumulate through its use. There is no right or wrong answer to class design. The correct answer is probably somewhere in between the extremes. If a project is large or may contain large components that could be reused in later projects, try to break it up into its natural class structure. If it will just be a small one-time applet, it probably makes more sense to keep things simple. In this chapter, we concentrate on two working applets. Each concentrates on a different important aspect of programming applets:
Calculator Calculator applet finishes the applet started in Chapter 22, “The Windowing Package.” It is extended to be a fully functional four-function algebraic calculator. It’s a good example of programming an applet with only one class, using the AWT components feature of passing events to keep things simple. ColorPicker ColorPicker enables you to display a specified RGB color in real time. This could be used to find the value of colors that could be used with an applet in the browser. It uses multiple classes and shows the strengths and weaknesses of that approach.
The Calculator At the end of Chapter 22 we developed a sample user interface for an algebraic calculator. Luckily, creating prototypes like this is often quite fruitful. With some minor modifications, we can use that prototype code as the interface of a working applet. Listing 24.1 shows the code we start with.
Listing 24.1. The original Calculator code.
Java Unleashed
Page 243
import java.awt.*; import java.applet.Applet; public class Calculator extends Applet { Label display; Panel bottom; Panel num_panel; Panel func_panel; Button number[] = new Button[10]; Button function[] = new Button[6]; public void init() { setLayout(new BorderLayout()); display = new Label(“0”, Label.RIGHT); add(“North”, display); bottom = new Panel(); bottom.setLayout(new BorderLayout()); num_panel = new Panel(); num_panel.setLayout(new GridLayout(4,3)); for (int x=9; x>=0; x--) { number[x] = new Button((new String()).valueOf(x)); num_panel.add(number[x]); } function[4] = new Button(“.”); num_panel.add(function[4]); function[5] = new Button(“=”); num_panel.add(function[5]); bottom.add(“Center”, num_panel); func_panel = new Panel(); func_panel.setLayout(new GridLayout(4,1)); function[0] = new Button(“+”); function[1] = new Button(“-”); function[2] = new Button(“*”); function[3] = new Button(“/”); for (int x=0; x0;i = i - 3) for (int j=0; j Gene 0-117-095-158-5544 <EOL> How can we dial a phone number? Our first idea would be to simply send the AT command sequence to a modem and have it dial the number over the interface with a telephone line. But what if you do not have a modem attached to your computer or it is busy because you are connected to a BBS or an Internet service provider? A better way would be to dial the number by playing telephone dialing tones on the computer speaker. We always can create or record dialing tones (also called DTMF tones, for Dual Tone MultiFrequency) and use them when we need to dial a number. Now let’s think about the user interface. Definitely, we will need to display telephone numbers and a searchable list where the user can select a name or address. We will need a dial button. Also, it would be nice to have something like a telephone button pad so that users could enter a phone number that is not found in the telephone book.
Java Unleashed
Page 272
We can start designing application components and creating a skeleton for our program. First of all, to have a valid Java application accessible from the Internet browser, we need to include an Applet class. Then, we need a class that incorporates user controls: buttons, edit boxes, and list boxes. Let’s call it PhoneControls. To include the functionality of the telephone button pad, we will create a third class called ButtonPad. The PhoneDial class, derived from the Java Applet class, implements the functionality of Java interactive application capable of communicating with Java-enabled Internet browsers. We will need to customize the default behavior of the Applet class in order to bring telephone book functionality to our users. At this time, the PhoneDial applet does almost nothing. It creates a PhoneControls class that will have telephone book controls, adding this to the center of its own window. Then it passes start and stop notification events to the PhoneControl class in order to enable controls when Applet is started and disable them when it is about to finish. public class PhoneDial extends Applet { PhoneControls controls; public void init( ) //applet initialization function { String strParam = getParameter(“PHONEBOOK”); //get argument “PHONEBOOK” String strPhoneBook = (strParam == null) ? “phonebook.html” : strParam; //use the default telephone book document name if argument not found controls = new PhoneControls( ); //create controls add(“Center”, controls); //add controls to the applet } public void start( ) //applet starting { controls.enable( ); //enable controls on applet start } public void stop( ) //applet is about to be closed { controls.disable( ); //disable controls } public static void main(String args[]) { Frame f = new Frame(“PhoneDial”); //create applet frame PhoneDial phoneDial = new PhoneDial( ); //create a new applet class phoneDial.init( ); //init applet phoneDial.start( ); //start applet f.add(“Center”, phoneDial); //add applet to the center of allocated window f.resize(150, 200); //resize to preferred dimensions f.show( ); //show applet } } The function main( ) does a very important job for the applet: It creates a frame for our application and starts a thread where the application actually is running. At first it might seem a little strange. Why should we care about creating a thread for an application if it already is running? The key to understanding this is in the fact that we should return control from the main function, and must do it as fast as possible in order to free up the Internet browser for other useful tasks. At the same time, we will need to keep our application running to process user input, update the screen when necessary, and so on. Luckily, we do not need to write any code to start a thread. This is the default behavior of the Applet class. We want to add a frame and possibly resize the applet to the dimensions we think would be optimal for our application. We do not expect that we will always get these dimensions. Actually, the browser will negotiate the real size to allocate for the applet during initialization. The application-desired size might be overridden when the actual space available is not large enough or when dimensions are specified in the HTML document that includes calls to the applet. The last function call we want to add to the main function is show( ). It will send a request to the Internet browser notifying it that our application is ready to be displayed and will cause it to update the part of the screen allocated for our applet. Two other classes, PhoneControls and ButtonPad, are derived from the Panel class and inherit the functionality of that group of controls. ButtonPad will include telephone buttons and PhoneControls class will include all other elements of user interface: text box
Java Unleashed
Page 273
for telephone number, list box for selection telephone number from the list of persons and organizations, and the “dial” button that initiate process of dialing selected number. class PhoneControls extends Panel //class that will incorporate user controls { ButtonPad controlsButtonPad; //declare class with telephone-style buttons public PhoneControls( ) // PhoneControls class constructor { //TO DO: Add buttons “0” to “9”, “*” and “#” }
} class ButtonPad extends Panel //class with telephone button pad keys { public ButtonPad( ) // ButtonPad class constructor { //TO DO: Add text box for telephone number, list box and “Dial” button } } At this time, we included only empty constructors for PhoneControls and ButtonPad classes and placed the ButtonPad variable declaration into the PhoneControls. Note that we have not actually created a new ButtonPad class. We do not need that unless we actually have some buttons in it. At this time we can compile our project and create an HTML file to test it out. We do not need to have anything in this page but a call to the PhoneDial applet: PhoneBook It is no surprise that our application does nothing at this point except display a gray square.
Interface Design Now we need to forget that we are computer programmers and become an artist for a moment. We need to place our controls so that they will be easy to find. We do not have too many of them—just a button pad, a window where we want to edit the phone number, and a box where we can select a person or organization we want to call. The more people involved in interface design, the more solutions you will have. One of the possible solutions for the user interface is shown on Figure 26.1. FIGURE 26.1. User interface for the Phone Book and Telephone Dialer application. Now we can go back to coding and start from the button pad control. First, we need to select the layout. Because all buttons have the same size, GridLayout is the natural choice. We create a GridLayout class and declare the dimensions (four rows and three columns as on your telephone pad) and a gap between buttons. GridLayout bag = new GridLayout(4, 3, 1, 1); Now we can assign this layout to our panel as follows: setLayout(bag); At this time we can add buttons. To do this, we will create a button, set a label for it, and then add this button into our control panel. We use a small case statement to handle labels for the buttons on the bottom row differently—instead of sequential numbers we need to add * , 0, and # to these buttons.
Java Unleashed for(int i= 0; i
Summary You now should know enough to strike out on your own and develop networking applets and applications. For simple Web-based applets, the urlFetcher example should show you all you need to know. The other examples can be altered and used as the core of new applications. For example, the TCPServer could be rewritten to service Finger requests for hosts that don’t already have a finger daemon (such as Windows 95 boxes). To see more examples of how to put the networking classes to use, check out Chapter 31, “Extending Java with Content and Protocol Handlers.” The fingerClient is used to enable Java to understand URLs of the form finger://hostname/user, and the urlFetcher applet is extended.
Java Unleashed
Page 324
Chapter 30 Overview of content and protocol handlers The first section of this chapter is a note on the history and evolution of protocol and content handler architecture within Java. The second section discusses protocol handlers—their definition, potential applications where they can be used as a very effective and powerful design and implementation tool, and general guidelines for writing your own protocol handlers. The third section of this chapter discusses the same issues for content handlers.
Historical and Evolutionary Note The Alpha version of Java had a standardized API for handlers. This was possible because the only Java-enabled Web browser at that time was Sun’s own HotJava. Within HotJava, Sun had full control over the architecture and deployment of handlers—both standard handlers as well as custom user-designed handlers. Beginning with Java Beta, however, Sun is evolving the Java API, including the protocol and content handler APIs, with the objective of integrating it with other vendors’ Web browsers, including Netscape Navigator. The next chapter provides examples of code written to illustrate protocol and content handlers using the latest release of the JDK.
Java Protocol Handlers This section starts with a definition of protocol handlers. We then examine dynamic protocol handlers in the Java language, followed by a discussion of potential application scenarios. This section concludes with a set of guidelines for designing and implementing protocol handlers in Java.
Definition of a Protocol Handler In the context of client-server software design, a protocol is a predefined set of sequences in which information is exchanged between the client and the server software. It is the common language that they have been designed (by the programmer) to use to communicate with each other. A protocol handler is a piece of code that is used (both by the server and client programs), to perform the function of shuttling data to and from the server and client applications. In the read mode, protocol handlers listen for incoming data on a network connection, verify if the data sequence conforms to the predefined protocol, and if it does, pass the data on to other parts of the program. In the write mode, they shuttle data in the opposite direction. In conventional languages like C and C++, protocol handlers have to be designed and integrated into the system architecture up front, along with other components of a client-server system. This means that when the protocol has to be changed for business or engineering reasons, the handler and other related parts of the client and server systems have to go through a complete implementation and release cycle. This limits the shelf life of the handler and thereby the return-on-investment of the implementation effort.
Dynamic Protocol Handlers in Java The Java language eliminates all constraints of static design on protocol handlers and their deployment in client-server systems. Unlike a static/frozen protocol handler in conventional languages, a protocol handler in Java has the following elements of dynamism:
A client or a server program need not possess the protocol handler code in advance—the code can be loaded on the fly. If a client program needs a new (and hitherto unknown) protocol handler, it can query the server (which initiated the transaction) and get the handler code from across the network. Design changes in the handler can be propagated on the fly without affecting other parts of the system and going through a full implementation-release cycle. The following features of Java make this possible:
Java Unleashed
Page 325
version-checked loading of classes ability to fetch classes from across the network Given these two Java language features, you can easily make protocol handlers dynamically loadable, given that you design and implement the handlers as a Java class hierarchy. Detailed guidelines for exploiting the previous Java features to construct dynamic protocol handlers are presented in a later section (Guidelines for Designing Dynamic Protocol in Java). In the next section, we’ll explore application scenarios where dynamic protocol handlers are a powerful and even necessary design mechanism.
Application Scenarios for Dynamic Protocol Handlers In this section we examine some client-server application scenarios where dynamic protocol handling is shown to be much more effective and powerful than traditional static handler design.
Dynamically Extensible Web Browsers Most Web browsers can handle the http, FTP, gopher and other popular Internet protocols. In addition, some browsers can handle secure/encrypted protocols. Let’s say, for example, a browser vendor or the W3O (the organization that sets and arbitrates standards for the World Wide Web) makes some changes to an existing protocol or creates a new protocol. Any such new development triggers a new cycle of product design and implementation and distribution of new browsers. Users are also forced to keep abreast of announcements and releases, periodically download new browsers, and install them on their machines. Therefore, any change or improvement in a Web protocol forces the entire industry and the user community to swallow the change whole. This problem can be solved by using dynamically loadable protocol handlers. A commonly used protocol for secure data transmission on the Web is HTTPS. A standard Web URL such as http://someserver.com/info.html will initiate a nonsecure network connection between the server and the browser. A secure server URL such as https://someserver.com/secretinfo.html, however, will cause both the browser and the server to encrypt or scramble all transactions. Of course for this to work, both the browser and the server have to be “aware” of this protocol and incorporate the actual protocol code. Now imagine that the federal government comes up with a new standard for secure financial transmission, called (for example) HTTP-FED and expects all Web browsers and servers to incorporate the new protocol. This would require
vendors to design and distribute new versions of their products users to upgrade their installations servers to constantly check and warn any stragglers who have not upgraded their browsers Going through this change can be tiresome, expensive, and error prone, especially when the technology is rapidly changing and evolving. This problem can be alleviated by separating the design of the browser (and the server) from that of the protocol and making the protocol dynamically loadable. This is very easy to do in Java, as opposed to other conventional languages such as C or C++. To make a product, such as a Web browser, automatically protocol-adaptive to the previously mentioned hypothetical new protocol HTTP-FED, a designer needs to
implement the protocol-dependent part of the browser in Java and make it aware of a “Home” server, where new handlers will be found host the actual HTTP-FED protocol handling code on the “Home” server When the browser user tries to access a URL with the new HTTP-FED protocol, the browser will see that it does not understand HTTP-FED. This will cause the Java-implemented part of the browser to access the “Home” server and automatically download the new protocol handler. All this will be transparent to the user.
Java Unleashed
Page 326
Multiprotocol Financial Transaction Systems A rapidly emerging technology in the Internet arena is the ability to conduct financial transactions on the Internet. The scope of this technology includes home banking, online shopping, online bill payment, and so on. The theory is that, using a Web browser, you will be able to conduct any sort of transaction that involves the transfer of money. The reality of implementing this, however, is a bit more complex. There are several industry groups and individual companies that are involved in creating protocols and standards for online financial transactions. Each is interested in having some or all of their favorite proposals gain pervasive acceptance by the industry and end users. Because most of us pay for almost anything we do, there is a lot of money to be made in being a primary enabler or carrier of these transactions. While the industry players fight it out, what is the best product design strategy to follow, from the perspective of the following users of this technology:
first-tier product vendors (such as browser makers)? second-tier vendors (such as makers of financial and tax utilities, pay-per-access service providers, online retailers)? end users who wish to conduct online transactions now? In the short run, the standards and protocol situation is going to remain fragmented and rapidly changing. Given the shifting sands of online transaction technology, the best bet is to design applications that are immune or adaptive in relation to multiple and emerging protocols and standards. Again, the only way to do this is to implement dynamic protocol handlers in Java.
Multiplayer Games Imagine that you are in charge of designing the protocols for a networked multiplayer electronic game. Such games enable multiple people to participate in a game session through a network. Such games are usually designed as a client-server system, with
a game client program that runs on each user’s machine and provides the visual interface to the game game server software that runs on a gaming server computer and keeps track of the number of people, their game moves, scores, and so on a game protocol that enables the server and multiple clients to exchange game data and to remain in sync with each other during a game session To make the example concrete, let’s assume you are designing some sort of a wagering game, which requires players to make a wager before each move. The person who makes the move wins or loses money depending on whether the move was good or bad within the rules of the game. This means that for every move, the protocol for this game has to carry information about (among other things)
the value of the wager the actual move made by a player the net gain or loss for each player Now, for an interesting twist to this scenario, let’s say you wish to allow your target consumer to try the game before he or she buys it. This means you would have to distribute two versions of your game: a paid licensed version that allows actual wagering and financial transaction, and another free version that allows people to play but not actually wager and exchange money. Depending on whether the player is using a free version or paid version of the client, the protocol code in the client has to transmit or block wagering information during the game session.
Java Unleashed
Page 327
If you were coding the game in C or C++, you would have to distribute physically two separate versions of the game—one with a fully functional protocol and the other with the wagering part of the protocol turned off. This would create the overhead of two separate tracks of the product-cycle that spans coding, documentation, maintenance, advertising, distribution, support, and maintenance mechanisms. If you implement your game in Java, you can merge the two product-cycle tracks and implement a fully automated try-before-you-buy mechanism. This is accomplish by doing the following:
design and distribute a single client program, with no protocol code included design the protocol as a class hierarchy to segment the handling of wagering data from other parts of the protocol host the protocol code on the gaming server Then, when the user runs the client program and connects to the gaming server, the server can authenticate the client for a valid license code and send the appropriate protocol code—the fully functional version for licensed clients and the wager-disabled version for unlicensed clients. It is pretty obvious that the Java-based implementation with dynamically loadable protocol handlers is much cleaner and effective than a C- or C++-based implementation with no such flexibility.
Guidelines for Designing Dynamic Protocol Handlers in Java From the earlier application scenarios, you might already have a good idea of how to go about designing a dynamic protocol handler and how to deploy it within a client-server application. Here is a set of guidelines that will help consolidate the ideas and provide a strategy for implementing Java-based dynamic protocol handlers in diverse design situations.
Identify all the client and program states during which some transactions will occur between the client and the server. Let’s call this the set of transactional states of the system. This set of transactional states should be carefully chosen so there is no duplication of functionality between members of this set. For each such transactional state, identify the one or more protocols that will be used. Create detailed specifications for each protocol in the proposed protocol suite. Parameterize the protocol specifications per the different transactional states in the system and partition them into a Java class hierarchy that corresponds to your parameterization. Create a single generic protocol loader class within both the client and server programs. The class should accept a set of parameters that identify a transactional state and load the appropriate protocol from the protocol class hierarchy. If you wish to add a new protocol to the system, as stated, identify the transactional state that will use it and add the protocol code at an appropriate place in the exiting protocol class hierarchy. For both existing and new protocols, the generically designed loader will use a local version of the protocol. If it does not find one on the local file system, it will go across to a specified “Home” server and download an appropriate handler from there.
Java Content Handlers Much of what has been said for protocol handlers in the previous section also applies to content handlers. Thus, some material here is similar to material in prior sections. There is, however, a fundamental difference between content and protocol handlers that becomes clear as we go along.
Java Unleashed
Page 328
Definition of a Content Handler The Internet is used to exchange diverse types of data—images, plain text, audio clips, executable programs, and so on. Each type of data is encoded in a specific format. Common examples of such encoding formats include, among others
JPEG and GIF formats for images AU, AIFF, and WAV formats for audio data PLAIN and DOC for documents HTML for Web pages Such encoded data is targeted for consumption by one or more applications on users’ computers. Content Type, also sometimes called MIME Type, specifies what format the data is encoded in and what class of applications can potentially consume this data. Using this information, a client program such as a Web browser or an e-mail reader can collect incoming data and spawn an appropriate module of internal code or an external “Helper” program to consume the data. In the context of a client-server software system, a protocol is a predefined set of sequences in which information is exchanged between the client and the server software, while content type specifies the exact nature and format of the data being transmitted through the protocol. The content type is physically a part of a protocol sequence and is often transmitted in the header portion of the protocol. The protocol header might include other information such as the length (in bytes) of the data. All this information is used in tandem by the recipient program to decode and use the data. A protocol handler is a piece of code that is used (both by the server and client programs) to perform the function of shuttling data to and from each other. A content handler is a piece of code that kicks in after the protocol handler has done its job. The content handler either directly consumes the incoming data or spawns an external application to use the data. In conventional languages such as C and C++, content handlers, like protocol handlers, have to be designed and integrated along with other components of the system. This means that when a data-encoding format changes, or a new content type has to be supported for business or engineering reasons, the entire application has to go through a complete implementation and release cycle.
Dynamic Content Handlers in Java The Java language eliminates the need for tightly coupled and static design of content handlers and their deployment in client-server systems. Unlike a static/frozen content handler in conventional languages, a content handler in Java has the following elements of dynamism:
A client or a server program need not possess the handler code in advance—the code can be loaded on the fly. If a client program needs a new (and hitherto unknown) content handler, it can query the server (which initiated the transaction) and get the handler code from across the network. Design changes in the handler can be propagated on the fly without affecting other parts of the system and going through a full implementation-release cycle. Just as with protocol handlers, the following features of Java make this possible:
version-checked loading of classes ability to fetch classes from across the network Given these two Java language features, you can easily make content handlers dynamically loadable if you design and implement the handlers as a Java class hierarchy. Detailed guidelines for exploiting the previous Java features to construct dynamic content handlers is be presented in the “Guidelines for Designing Dynamic Content Handlers in Java” section a little later in this chapter. In the next section, however, we’ll explore application scenarios where dynamic content handlers are useful and even necessary.
Java Unleashed
Page 329
Application Scenarios for Dynamic Content Handlers In this section we explore some client-server application scenarios where dynamic content handling is shown to be much more effective and powerful than traditional static handler design.
Dynamically Extensible Web Browsers, E-mail Readers, and News Readers Most Web browsers can handle a limited set of content types, such as HTML, Plain Text, GIF images, and so on. When a Web browser cannot itself handle a particular data type, it calls an external “Helper” application to handle it. E-mail and news readers can only display plain ASCII textual data. All MIME data types attached to the e-mail or news article have to be saved on disk to be handled later by another program. For the purpose of the following discussion, we’ll refer to Web browsers, mail readers, and news readers collectively as browser programs (they all let the user “browse” information coming through the Net). In all the previous cases, the browser program has to do one of the following:
understand the content type internally assume an external program exists on the user’s computer that can be called to handle the data What if neither of the above is true? What if the incoming content type is totally new, so that neither the browser program understands it nor can an external program use it? Well, so far the solution using conventional languages has been this:
incorporate more and more content type handlers into the browser ship external helper applications for every new content type Each such new development triggers a new cycle of product design, implementation, and distribution of new browser programs so that the new features can reach end users. Also, with each new release and new sets of features, applications tend to bloat and consume more hardware resources. Users are also forced to keep abreast of announcements and releases and must periodically download new browser and helper programs and install them on their machines. Thus, any change or improvement in a content type forces the entire industry and the user community to scramble to keep up. This problem can be solved using dynamically loadable content handlers. Let us imagine the MPEG committee comes up with a new MPEG-encoding format called MPEG-2000, and expects all browser programs to understand and render this new format correctly. This would require
MPEG-encoded content vendors to design and distribute new versions of their content, along with appropriate helper applications browser vendors with built-in handlers to update their code to handle the new format users to upgrade their current versions of helper apps and browsers, and tweak all the configuration files so both old MPEG-encoded content and newer content can be seamlessly handled Going through this is a chore. As with protocol handlers, this problem can be alleviated by separating the design of the browser from that of the content handler, and making the content handler dynamically loadable. This is very easy to do in Java, as opposed to other conventional languages such as C or C++. To make a product such as a Web browser automatically adaptive to the hypothetical new MPEG content type
implement the content handler part of the browser in Java host the Java code to handle the new content type on the same server from which you plan to serve the data encoded in the proposed new content type When the browser tries to decode data that is encoded in the new content type, the Java-implemented part will access the same server where the data came from and automatically download the appropriate handler. All this will be transparent to the user.
Java Unleashed
Page 330
Other Nonbrowser Applications The concept of dynamic content handling can be extended to several other application domains. A generic potential application domain is to use content handlers to mediate “computing-resource-for-rent” type applications. Let’s say a hypothetical company, TronGlut, Inc., has an enormous array of powerful computers, and a hypothetical compression technology researcher—Dr. Pix—has invented a new compression algorithm. This algorithm is very advanced, but requires enormous computing power to test. Thus, Dr. Pix contracts with TronGlut, Inc. to send test data (such as images, sounds, and text) and algorithms to TronGlut’s computers, which will run the algorithm and return compressed data to Dr. Pix. In this scenario, Dr. Pix will host the data and host the corresponding handler code (the compression algorithms to be tested) on a server. TronGlut’s computer runs an automatic browser-type application that sequentially accesses uncompressed data and corresponding handler code from Dr. Pix’s server. After running the handler on each data item, the results are e-mailed back to Dr. Pix, who evaluates the results visually and algorithmically with another browser.
Guidelines for Designing Dynamic Content Handlers in Java From the earlier application scenarios, you may already have a good idea of how to go about designing a dynamic content handler. Here is a set of guidelines that will help consolidate the ideas and provide a strategy for implementing Java-based dynamic content handlers in diverse design situations:
Create a Java class for each content type you need to handle in your application, and organize them into a suitable class hierarchy. Create a single generic content handler loader class within both the client and server programs that accepts a set of parameters that identify a specific content type and loads the appropriate content handler from the content handler class hierarchy. If you are a content vendor and wish to create a new content type, create a Java class that handles the new type, and host the code on the same server from which you plan to serve data. For both existing and new content types, the generically designed content handler loader will use a local version of the handler. If it doesn’t find one on the local file system, it will go access the specified content server and download an appropriate handler from there.
Summary In this chapter, we examined the concept of dynamically loadable protocol and content handlers in Java. With specific application scenarios and design guidelines, we saw how these twin features of Java empower software designers to implement client-server applications in a modular, adaptive, and extensible way. In the light of these features, we contrasted client-server design using Java against using other languages like C and C++. The next chapter expands the material presented in this chapter and presents specific code samples illustrating the design of protocol and content handlers in Java.
Java Unleashed
Page 331
Chapter 31 Extending Java with content and protocol handlers Java’s URL class gives applets and applications easy access to the World Wide Web using the HTTP protocol. This is fine and dandy if you can get the information you need into a format that a Web server or CGI script can access. However, wouldn’t it be nice if your code could talk directly to the server application without going through an intermediary CGI script or some sort of proxy? Wouldn’t you like your Java-based Web browser to be able to display your wonderful new image format? This is where protocol and content handlers come in.
What Are Protocol and Content Handlers? Handlers are classes that extend the capabilities of the standard URL class. A protocol handler provides an InputStream (and OutputStream, where appropriate) that retrieves the content of a URL. Content handlers take an InputStream for a given MIME type and convert it into a Java object of the appropriate type.
MIME Types MIME, or Multipurpose Internet Mail Extensions, is the Internet standard for specifying what type of content a resource contains. As you might have guessed from the name, it originally was proposed for the context of enclosing nontextual components in Internet email. This allows different platforms (PCs, Macintoshes, UNIX workstations, and others) to exchange multimedia content in a common format. The MIME standard, described in RFC 1521, defines an extra set of headers similar to those on Internet e-mail. The headers describe attributes such as the method of encoding the content and the MIME content type. MIME types are written as type/subtype, where type is a general category such as text or image and subtype is a more specific description of the format such as html or jpeg. For example, when a Web browser contacts an HTTP daemon to retrieve an HTML file, the daemon’s response looks something like the following: Content-type: text/html <TITLE>Document moved Document moved The Web browser parses the Content-type: header and sees that the data is text/html—an HTML document. If it was a GIF image the header would have been Content-type: image/gif. IANA (Internet Assigned Numbers Authority), the group that maintains the lists of assigned protocol numbers and the like, is responsible for registering new content types. A current copy of the official MIME types is available from ftp://ftp.isi.edu/innotes/iana/assignments/media-types/. This site also has specifications or pointers to specifications for each type.
Getting Java to Load New Handlers The exact procedure for loading a protocol or content handler depends on the Java implementation. The following instructions are based on Sun’s Developer’s Kit and should work for any implementation derived from Sun’s. If you have problems, check the documentation for your particular Java version. In the JDK implementation, the URL class and helpers look for classes in the sun.net.www package. Protocol handlers should be in a package called sun.net.www.protocol.ProtocolName, where ProtocolName is the name of the protocol (such as ftp or http). The handler class itself should be named Handler. For example, the full name of the HTTP protocol handler class, provided by Sun with the JDK, is sun.net.www.protocol.http.Handler. In order to get your new protocol handler loaded, you need to construct a directory structure corresponding to the package names and add the directory to your CLASSPATH environment variable. Assume that you have a handler for a protocol—we’ll call it the foo protocol. Your Java library directory is …/java/lib/ (…\java\lib\ on Windows machines). You will need to take the following steps:
Make directories …/java/lib/sun, …/java/lib/sun/net, and so on.The last directory should be called ./java/lib/sun/net/www/protocol/foo.
Java Unleashed
Page 332
Place your Handler.java file in the last directory (that is, it should be named …/java/lib/sun/net/www/protocol/foo/Handler.java). Compile the Handler.java file. If you place the ZIP file of network classes from the CD-ROM in your CLASSPATH, the example handlers should load correctly.
Creating a Protocol Handler We will start extending Java by using the fingerClient class developed in Chapter 29, “Network Programming,” to implement a handler for the Finger protocol. Our handler will take URLs of the form finger:user@hostname, where user is the user name we want information for (or all users, if omitted) and hostname is the host to query. The urlFetcher applet will be used to demonstrate the protocol handler.
Design The first decision that needs to be made is how to structure URLs for our protocol. We’ll imitate the HTTP URL and specify that Finger URLs should be of the following form: finger://host/user host is the host to contact and user is an optional user to ask for information about. If the user name is omitted, we will return information about all users. This is the same behavior as the fingerClient developed in Chapter 29. The only modification to the fingerClient class that needs to be made is to insert the package statement to put the class in the correct package. Because we already have the fingerClient, we only need to write subclasses to URLStreamHandler and URLConnection. Our stream handler will use the client object to format the returned information using HTML. The handler will write the content into a StringBuffer, which will be used to create a StringBufferInputStream. The fingerConnection, a subclass of URLConnection, will take this stream and implement the getInputStream( ) and getContent( ) methods. In our implementation, the protocol handler object does all the work of retrieving the remote content while the connection object simply retrieves the data from the stream provided. Usually, you would have the connection object handler retrieving the content. The openConnection( ) method would open a connection to the remote location, and the getInputStream( ) would return a stream to read the contents.
Copying the fingerClient The first thing to do is copy the fingerClient source from Chapter 29 into the right subdirectory. The only modification that needs to be made to the class is to add the following statement to the top of the file: package sun.net.www.protocol.finger; Everything else in the file remains the same. You just need to recompile the source so that you get a class file with the correct package information.
fingerConnection Source We’ll go ahead and present the source for the URLConnection subclass. This class should go in the same file as the Handler class. The constructor will copy the InputStream passed and call the URLConnection constructor. It also sets the URLConnection member to indicate that the connection cannot take input. Listing 31.1 contains the source for this class.
Listing 31.1. The fingerConnection class.
Java Unleashed
Page 333
class fingerConnection extends URLConnection { InputStream in; fingerConnection( URL u, InputStream in ) { super( u ); this.in = in; this.setDoInput( false ); } public void connect( ) { return; } public InputStream getInputStream( ) throws IOException { return in; } public Object getContent( ) throws IOException { String retval; int nbytes; byte buf[] = new byte[ 1024 ]; try { while( (nbytes = in.read( buf, 0, 1024 )) != -1 ) { retval += new String( buf, 0, 0, nbytes ); } } catch( Exception e ) { System.err.println( “fingerConnection::getContent: Exception\n” + e ); e.printStackTrace( System.err ); } return retval } }
Handler Source First we’ll rough out the skeleton of the Handler.java file. We need the package statement so that our classes are compiled into the package where the runtime handler will be looking for them. We also import the fingerClient object here. The outline of the class is shown in Listing 31.2.
Listing 31.2. Protocol handler skeleton. package sun.net.www.protocol.finger; import java.io.*; import java.net.*; import sun.net.www.protocol.finger.fingerClient; // fingerConnection source goes here public class Handler extends URLStreamHandler { // openConnection( ) Method }
openConnection( ) Method Now we’ll develop the method responsible for returning an appropriate URLConnection object to retrieve a given URL. Our method starts out by allocating a StringBuffer to hold our return data. We also will parse out the host name and user name from the URL argument. If the host was omitted, we default to localhost. The code for openConnection( ) is given in Listings 31.3 through 31.6.
Listing 31.3. The openConnection( ) method, part one.
Java Unleashed
Page 334
public synchronized URLConnection openConnection( URL u ) { StringBuffer sb = new StringBuffer( ); String host = u.getHost( ); String user = u.getFile( ).substring( 1, u.getFile( ).length( ) ); if( host.equals( “” ) ) { host = “localhost”; }
Next, the method will write an HTML header into the buffer. This will allow a Java-based Web browser to display the Finger information in a nice-looking format.
Listing 31.4. The openConnection( ) method, part two. sb.append( sb.append( sb.append( sb.append( sb.append( sb.append( sb.append(
“\n”); “Fingering “ ); (user.equals(“”) ? “everyone” : user) ); “@” + host ); “\n” ); “\n” ); “<pre>\n” );
We now will use the fingerClient class to get the information into a String and then append it to our buffer. If there is an error while getting the Finger information, we will put the error message from the exception into the buffer instead.
Listing 31.5. The openConnection( ) method, part three. try { String info = null; info = (new fingerClient( host, user )).getInfo( ); sb.append( info ) } catch( Exception e ) { sb.append( “Error fingering: “ + e ); }
Finally we’ll close off the open HTML tags and create a fingerConnection object which will be returned to the caller, as follows:
Listing 31.6. The openConnection( ) method, part four.
}
sb.append( “\n\n\n” ); return new fingerConnection( u, (new StringBufferInputStream( sb.toString( ) ) ) );
Using the Handler Once you have all of the code compiled and in the right locations, load the urlFetcher applet from Chapter 29 and enter a Finger URL. If everything loads right, you should see something that looks like Figure 31.1. If you get an error that says Finger is an unknown protocol, check that you have your CLASSPATH set correctly.
Java Unleashed
Page 335
Creating a Content Handler FIGURE 31.1. The urlFetcher applet displaying a Finger URL. This content handler example will be for the MIME type text/tab-separated-values. This type will be familiar if you have ever used a spreadsheet or database program. Many such applications can import and export data in an ASCII text file, with each column of data in a row separated by a Tab character (“\t”). The first line is interpreted as the names of the fields, and the remaining lines are the actual data.
Design Our first design decision is to figure out what type of Java object or objects to map the tab-separated values. Because this is a textual content, some sort of String object would seem to be the best solution. The spreadsheet characteristics of rows and columns of data can be represented by arrays. Putting these two facts together gives us a data type of String[][], or an array of arrays of String objects. The first array is an array of String[] objects, each representing one row of the data. Each of these arrays consists of a String for each cell of the data. Also, we’ll need to have some way of breaking the input stream into the separate fields. We will make a subclass of java.io.StreamTokenizer to handle this task. The StreamTokenizer class provides methods for breaking an InputStream into individual tokens. You might want to browse over the entry for StreamTokenizer in the API reference if you are not familiar with it.
Content Handler Skeleton Content handlers are implemented by subclassing the java.net.ContentHandler class. These subclasses are responsible for implementing a getContent() method. We’ll start with the skeleton of the class. We’ll import the networking and I/O packages, as well as the java.util.Vector class. We also will define the skeleton for our tabStreamTokenizer class. This is shown in Listing 31.7.
Listing 31.7. Content handler skeleton. /* * Handler for text/tab-separated-values MIME type. */ // This needs to go in this package for JDK-derived // Java implementations package sun.net.www.content.text; import java.net.*; import java.io.*; class tabStreamTokenizer extends StreamTokenizer { public static final int TT_TAB = ‘’\t’ // Constructor } import java.util.Vector; public class tab_separated_values extends ContentHandler { // getContent method }
The tabStreamTokenizer Class First we will define the class that will break the input up into the separate fields. Most of the functionality we need is provided by the StreamTokenizer class, so we only need to define a constructor that will specify the character classes needed to get the behavior we want. For the purposes of this content handler there are three types of tokens: TT_TAB tokens, which will represent fields; TT_EOL tokens, which signal the end of a line (that is, the end of a row of data); and TT_EOF, which signals the end of the input file. Because this class is relatively simple it will be presented in its entirety in Listing 31.8.
Listing 31.8. The tabStreamTokenizer class.
Java Unleashed
Page 336
class tabStreamTokenizer extends StreamTokenizer { public static final int TT_TAB = ‘\t’; tabStreamTokenizer( InputStream in ) { super( in ); // Undo parseNumbers( ) and whitespaceChars(0, ‘ ‘) ordinaryChars( ‘0’, ‘9’ ); ordinaryChar( ‘.’ ); ordinaryChar( ‘-’ ); ordinaryChars( 0, ‘ ‘ ); // Everything but TT_EOL and TT_TAB is a word wordChars( 0, (‘\t’-1) ); wordChars( (‘\t’+1), 255 ); // Make sure TT_TAB and TT_EOL get returned verbatim. whitespaceChars( TT_TAB, TT_TAB ); ordinaryChar( TT_EOL ); } }
The getContent Method Subclasses of ContentHandler need to provide an implementation of getContent( ) that returns a reference to an Object. The method takes as its parameter a URLConnection object from which the class can obtain an InputStream to read the resource’s data.
getContent Skeleton First, we’ll define the overall structure and method variables. We need a flag, which will be called done, to signal when we’ve read all of the field names from the first line of text. The number of fields (columns) in each row of data will be determined by the number of fields in the first line of text, and will be kept in an int variable numFields. We also will declare another integer, index, for use while inserting the rows of data into a String[]. We will need some method of holding an arbitrary number of objects because we cannot tell the number of data rows in advance. To do this we’ll use the java.util.Vector object, which we’ll call lines, to keep each String[]. Finally, we will declare an instance of our tabStreamTokenizer, using the getInputStream( ) method from the URLConnection passed as an argument to the constructor. Listing 31.9 shows the skeleton code for the method.
Listing 31.9. The getContent( ) skeleton. public Object getContent( URLConnection con ) throws IOException { boolean done = false; int numFields = 0; int index = 0; Vector lines = new Vector( ); tabStreamTokenizer in = new tabStreamTokenizer( con.getInputStream( ) ); // Read in the first line of data (Listing 31.10 & 31.11) // Read in the rest of the file (Listing 31.12) // Stuff all data into a String[][] (Listing 31.13) }
Reading the First Line The first line of the file will tell us the number of fields and the names of the fields in each row for the rest of the file. Because we don’t know beforehand how many fields there are, we’ll be keeping each field in a Vector firstLine. Each TT_WORD token that the tokenizer returns is the name of one field. We know we are done once it returns a TT_EOL token and can set the flag done to true.
Java Unleashed
Page 337
We will use a switch statement on the ttype member of our tabStreamTokenizer to decide what action to take. This is done in the code in Listing 31.10.
Listing 31.10. Reading the first line of data. Vector firstLine = new Vector( ); while( !done && in.nextToken( ) != in.TT_EOF ) { switch( in.ttype ) { case in.TT_WORD: firstLine.addElement( new String( in.sval ) ); numFields++; break; case in.TT_EOL: done = true; break; } }
Now that we have the first line in memory, we need to build an array of String objects from those stored in the Vector. To accomplish this we’ll first allocate the array to the size just determined. Then we will use the copyInto( ) method to transfer the strings into the array just allocated. Finally, the array will be inserted into lines. (See Listing 31.11.)
Listing 31.11. Copying field names into an array. // Copy first line into array String curLine[] = new String[ numFields ]; firstLine.copyInto( curLine ); lines.addElement( curLine );
Read the Rest of the File Before reading the remaining data, we need to allocate a new array to hold the next row. Then we loop until encountering the end of the file, signified by TT_EOF. Each time we retrieve a TT_WORD, we will insert the String into curLine and increment index. The end of the line will let us know when a row of data is done, at which time we will copy the current line into our Vector. Then we will allocate a new String[] to hold the next line and set index back to zero (to insert the next item starting at the first element of the array). The code to implement this is given in Listing 31.12.
Listing 31.12. Reading in the rest of the data. curLine = new String[ numFields ]; while( in.nextToken( ) != in.TT_EOF ) { switch( in.ttype ) { case in.TT_WORD: curLine[ index++ ] = new String( in.sval ); break; case in.TT_EOL: lines.addElement( curLine ); curLine = new String[ numFields ]; index = 0; break; } }
Java Unleashed
Page 338
Stuff All Data Into String[][] At this point all of the data has been read in. All that remains is to copy the data from lines into an array of arrays of String, as follows in Listing 31.13.
Listing 31.13. Returning TSV data as String[][]. String retval[][] = new String[ lines.size( ) ][]; lines.copyInto( retval ); return retval;
Using the Content Handler In order to show how the content handler works, we’ll be modifying the urlFetcher applet from Chapter 29. We’ll be changing it to use the getContent( ) method to retrieve the contents of a resource rather than reading the data from the stream returned by getInputStream( ). Because we’re only changing the doFetch() method, we won’t include the entire applet again, only the portions that change. The first change is to call the getContent( ) method and get an Object back rather than getting an InputStream. Listing 31.14 shows this change.
Listing 31.14. Modified urlFetcher.doFetch( ) code, part one. try { boolean displayed = false; URLConnection con = target.openConnection( ); Object obj = con.getContent( );
Next come tests using the instanceof operator. We handle String objects and arrays of String objects by placing the text into the TextArea. Arrays are printed item by item. If the object is a subclass of InputStream, we read the data from the stream and display it. Image content will just be noted as being an Image. For any other content type, we simply throw our hands up and remark that we cannot display the content (because we’re not a full-fledged Web browser). The code to do this is shown below in Listing 31.15.
Listing 31.15. Modified urlFetcher.doFetch() code, part two.
Java Unleashed
Page 339
if( obj instanceof String ) { contentArea.setText( (String) obj ); displayed = true; } if( obj instanceof String[] ) { String array[] = (String []) obj; StringBuffer buf = new StringBuffer( ); for( int i = 0; i < array.length; i++ ) buf.append( “item “ + i + “: “ + array[i] + “\n” ); contentArea.setText( buf.toString( ) ); displayed = true; } if( obj instanceof String[][] ) { String array[][] = (String [][]) obj; StringBuffer buf = new StringBuffer( ); for( int i = 0; i < array.length; i++ ) { buf.append( “Row “ + i + “:\n\t” ); for( int j = 0; j < array[i].length; j++ ) buf.append( “item “ + j + “: “ + array[i][j] + “\t” ); buf.append( “\n” ); } contentArea.setText( buf.toString( ) ); displayed = true; } if( obj instanceof Image ) { contentArea.setText( “Image” ); diplayed = true; } if( obj instanceof InputStream ) { int c; StringBuffer buf = new StringBuffer( ); while( (c = ((InputStream) obj).read( )) != -1 ) buf.append( (char) c ); contentArea.setText( buf.toString( ) ); displayed = true; } if( !displayed ) { contentArea.setText( “Don’t know how to display “ obj.getClass().getName( ) ); } // Same code to display content type and length } catch( IOException e ) { showStatus( “Error fetching \”” + target + “\”: “ + e ); return; }
The complete modified applet source is on the CD as urlFetcher_Mod.java in the tsvContentHandler directory. Figure 31.2 illustrates what it will show when displaying text/tab-separated-values. FIGURE 31.2. The urlFetcher_Mod applet. The file displayed is included as example.tsv. Most HTTP daemons should return the correct content type for files ending in .tsv. If the data does not show up as text/tab-separated-values, you might need to try one of the following things:
Ask your Webmaster to look at the MIME configuration file for your HTTP daemon. The Webmaster will be able to either tell you the proper file suffix or modify the daemon to return the proper type.
Java Unleashed
Page 340
If you can install CGI scripts on your Web server, there is a sample script with the content handler example that returns data in the proper format.
Summary After reading this chapter you should have an understanding of how Java can be extended fairly easily to deal with new application protocols and data formats. You should know what classes you have to derive your handlers from (URLConnection and URLStreamHandler for protocol handlers, ContentHandler for content handlers), and how to get Java to load the new handler classes. If you want to try your hand at writing a handler, try something simple at first. For a protocol handler you could try the echo protocol shown in Chapter 28. A more challenging task might be writing a content handler for application/postscript which prints the file out (this would more than likely need some native code, or some way to use the java.lang.Runtime.exec( ) method to call a local printing program).
Java Unleashed
Page 341
Chapter 32 Game programming with Java Creating games with Java is a lot like creating games with other languages. You need to deal with the design of movable objects (often referred to as sprites), the design of a graphics engine to keep track of the movable objects, and double buffering to make movement look smooth. This chapter will cover methods for creating these standard building blocks of games using Java. Thankfully, Java takes care of a lot of the dirty work that you would need to do if you were writing a game in another language. For instance, Java provides built-in support for transparent pixels, making it easier to write a graphics engine that can draw nonrectangular objects. Java also has built-in support for allowing several different programs to run at once—perfect for creating a world with a lot of creatures, each with its own special methods for acting. However, the added bonuses of Java can turn into handicaps if they are not used properly. This chapter will deal with using the advantages of Java to write games and how to avoid the pitfalls that accompany the power.
Graphics: Creating a Graphics Engine A graphics engine is essential to a well-designed game in Java. A graphics engine is an object that is given the duty of painting the screen. The graphics engine keeps track of all objects that are on the screen at one time, the order in which to draw the objects, and the background to be drawn. By far, the most important function of the graphics engine is the maintenance of movable object blocks.
Movable Object Blocks in Java So what’s the big fuss about movable object blocks (MOBs)? Well, they make your life infinitely easier if you’re interested in creating a game that combines graphics and user interaction, as most games do. The basic concept of a movable object is that the object contains both a picture that will be drawn on the screen and information that tells you where it is on the screen. To make the object move, you simply tell it which way to move and you’re done—it takes care of the redrawing. The bare-bones method for making a movable object block in Java is illustrated by Listing 32.1. As you can see, the movable object consists merely of an image and a set of coordinates. You might be thinking, “Movable objects are supposed to take care of all the redrawing that needs to be done when they are moved. How is that possible with just the code from Listing 32.1?” Redrawing will be the graphics engine’s job. Don’t worry about it for now; we’ll cover it a little later.
Listing 32.1. Use this code to create a bare-bones movable object (MOB). This code should be saved in a file called MOB.java. import java.awt.*; public class MOB { public int x = 0; public int y = 0; public Image picture; public MOB(Image pic) { picture=pic; } }
As you can see in Listing 32.1, the constructor for our movable object block (MOB) takes an Image and stores it away to be drawn when needed. After we’ve instantiated an MOB (that is, after we’ve called the constructor), we have a movable object that we can move around the screen just by changing its x and y values. The engine will take care of redrawing the movable object in the new position, so what else is there to worry about now? One thing to consider is the nature of the picture that is going to be drawn every time the movable object is drawn. Consider the place where the image probably will originate. In all likelihood the picture either will come from a GIF or JPEG file, which has one very important consequence—it will be rectangular. So what? Think about what your video game will look like if all of your movable
Java Unleashed
Page 342
objects are rectangles. Your characters would be drawn, but so would their backgrounds. Chances are, you’ll want to have a background for the entire game, so it would be unacceptable if the unfilled space on character images covered up your background just because the images were rectangular and the characters were of another shape. When programming games in other languages, this problem often is resolved by examining each pixel in a character’s image before drawing it to see whether it’s part of the background. If the pixel is not part of the background, it’s drawn as normal. If the pixel is part of the background, it’s skipped and the rest of the pixels are tested. Pixels that aren’t drawn usually are referred to as transparent pixels. If this seems like a laborious process to you, that’s because it is. Fortunately, Java has built-in support for transparent colors in images, which simplifies your task immensely. You don’t have to check each pixel for transparency before it’s drawn because Java can do that automatically! Java even has built-in support for different levels of transparency. For example, you can create pixels that are 20 percent transparent to give your images a ghostlike appearance. For now, though, we’ll just deal with fully transparent pixels. Java’s capability to draw transparent pixels makes the task much easier of painting movable objects on the screen. But how do you tell Java what pixels are transparent and what pixels aren’t? You could load the image and run it through a filter that changes the ColorModel, but that would be doing it the hard way. Fortunately, Java supports transparent GIF files. Whenever a transparent GIF file is loaded, all of the transparency is preserved by Java. That means your job just got a lot easier. Now the problem becomes how to make transparent GIFs. This part is easier than you think. Simply use your favorite graphics package to create a GIF file (or a picture in some other format that you eventually can convert to a GIF file). Select a color that doesn’t appear anywhere in the picture and fill all areas that you want to be transparent with the selected color. Make a note of the RGB value of the color that you use to fill in the transparent places. Now you can use a program to convert your GIF file into a transparent GIF file. I personally use Giftool, available at http://www.homepages.com/tools/index.html, to make transparent GIF files. You simply pass to Giftool the RGB value of the color you selected for transparency and Giftool makes that color transparent inside the GIF file. Giftool also is useful for making your GIF files interlaced. Interlaced GIF files are the pictures that appear with blocklike edges initially and keep getting more defined as they continue to load.
Construction of a Graphics Engine Now you have movable objects that know where they’re supposed to be and don’t eat up the background as they go there. The next step is to design something that will keep track of your movable objects and draw them in the proper places when necessary. This will be the job of our GraphicsEngine class. Listing 32.2 shows the bare bones of a graphics engine. This is the minimum that you would need to handle multiple movable objects. Even this leaves outseveral things that nearly all games need, but we’ll get to those things later. For now, we’ll concentrate on how this bare-bones system works in order to give you a solid grasp of the basic concepts.
Listing 32.2. This is a bare-bone graphics engine that tracks of your movable objects. The code should be saved in a file called GraphicsEngine.java.
Java Unleashed
Page 343
import java.awt.*; import java.awt.image.*; public class GraphicsEngine { Chain mobs = null; public GraphicsEngine() {} public void AddMOB (MOB new_mob) { mobs = new Chain(new_mob, mobs); } public void paint(Graphics g, ImageObserver imob) { Chain temp_mobs = mobs; MOB mob; while (temp_mobs != null) { mob = temp_mobs.mob; g.drawImage(mob.picture, mob.x, mob.y, imob); temp_mobs = temp_mobs.rest; } } } class Chain { public MOB mob; public Chain rest; public Chain(MOB mob, Chain rest) { this.mob = mob; this.rest = rest; } }
Before we detail how the GraphicsEngine class works, let’s touch on the Chain class. The Chain class looks rather simple—and it can be—but don’t let that fool you. Entire languages such as LISP and Scheme have been built around data structures that have the same function as the Chain class. The class is simply a data structure that holds two objects. Here, we’re calling those two objects item and rest because we are going to use Chain to create a linked list. The power of the Chain structure—and those structures like it—is that they can be used as building blocks to create a multitude of more complicated structures. These structures include circular buffers, binary trees, weighted di-graphs, and linked lists, to name a few. Using the Chain class to create a linked list will be suitable for our purposes. To understand what a linked list is, think of a train as an example of a linked list: The train could be considered to be the first car followed by the rest of the train. The rest of the train could be described as the second car followed by the remaining cars. This description could continue until you reached the last car which would be described as the caboose followed by nothing. To compare this analogy with the code for the class Chain, a Chain is analogous to a train. A Chain can be described as a movable object followed by the rest of the Chain, just as a train could be described as a car followed by the rest of the train. And just as the rest of the train could be considered a train by itself, the rest of the Chain can be considered a Chain itself, and that’s why the rest is of type Chain. From the looks of the constructor for Chain, it appears that you need an existing Chain to make another Chain. This makes sense when you already have a Chain and want to add to it, but how do you start a new Chain? To do this, create a Chain that is an item linked to nothing. How do you link an item to nothing? Use the Java symbol for nothing—null—to represent the rest Chain. If you take a look at the code, that’s exactly what we did. Our instance variable mobs is of type Chain and it is used to hold a linked list of movable objects. Look at the method AddMOB from Listing 32.2. Whenever we want to add another movable object to the list of movable objects that we’re controlling, we simply make a new list of movable objects that has the new movable object as the first item and the old Chain as the rest of the list. Notice that the initial value of mobs is null, which is used to represent nothing. How do we use the list of movable objects once AddMOB has been called for all of the movable objects we want to handle? Take a look at the paint method. The first thing to do is copy the pointer to mobs into a temporary Chain called temp_mobs. Note that the pointer is copied, not the actual contents. If the contents were copied instead of the pointer, this approach would take much longer and would be much more difficult to implement. “But I thought Java doesn’t have pointers,” you might be thinking at this point. That’s
Java Unleashed
Page 344
not exactly true; Java doesn’t have pointer arithmetic, but pointers still are used to pass arguments, although the programmer never has direct access to these pointers. temp_mobs now contains a pointer to the list of all of the movable objects to be drawn. The task at hand is to go through the list and draw each movable object. The variable mob will be used to keep track of each movable object as we get to it. The variable temp_mobs will represent the list of movable objects we have left to draw (that’s why we started it off pointing to the whole list). We’ll know all of our movable objects have been drawn when temp_mobs is null, because that will be like saying the list of movable objects left to draw is empty. That’s why the main part of the code is encapsulated in a while loop that terminates when temp_mobs is null. Take a look at the code inside the while loop of the paint method. The first thing that is done is assigning mob to the movable object at the beginning of the temp_mobs Chain so that there is an actual movable object to deal with. Now it’s time to draw the movable object. The g.drawImage command draws the movable object in the proper place. The variable mob.picture is the picture stored earlier when the movable object was created. The variables mob.x and mob.y are the screen coordinates where the movable object should be drawn; notice that these two variables are looked at every time the movable object is drawn, so changing one of these coordinates while the program is running has the same effect as moving it on the screen. The final argument passed to g.drawImage, imob, is an ImageObserver that is responsible for redrawing an image when it changes or moves. Don’t worry about where to get an ImageObserver from; chances are, you’ll be using the GraphicsEngine class to draw inside a Component (or a subclass of Component such as Applet), and a Component implements the ImageObserver interface so that you can just pass the Component to GraphicsEngine whenever you want to repaint. The final line inside the while loop shortens the list of movable objects that need to be drawn. It points temp_mobs away from the Chain that it just drew a movable object off the top of and points it to the Chain that contains the remainder of the MOBs. As we continue to cut down the list of MOBs by pointing to the remainder, temp_mobs will eventually wind up as null, which will end the while loop with all of our movable objects drawn. (See Figure 32.1 for a graphical explanation.) FIGURE 32.1. A graphical representation of a Chain.
Installing the GraphicsEngine The graphics engine we just built certainly had some important things left out, but it will work. Let’s go over how to install the GraphicsEngine inside a Component first, and then go back and improve on the design of the graphics engine and the MOB. It would be a good idea to type in and compile Listings 32.1 through 32.3 now so that you can get an idea of what the code does. Compile the code by saving each listing into its own file and then using the javac command. In addition to compiling the code in Listings 32.1 through 32.3, you also will need to create an HTML file as shown in Listing 32.4. As the final step, you will need to place a small image file to be used as the movable object in the same directory as the code and either rename it to one.gif or change the line inside the init method in Listing 32.3 that specifies the name of the picture being loaded.
Listing 32.3. This is a sample applet that illustrates the GraphicEngine class. The code should be saved in a file named Game.java.
Java Unleashed
Page 345
import java.awt.*; import java.applet.Applet; import java.net.URL; public class Game extends Applet { GraphicsEngine engine; MOB picture1; public void init() { try { engine = new GraphicsEngine(); Image image1 = getImage(new URL(getDocumentBase(), “one.gif”)); picture1 = new MOB(image1); engine.AddMOB(picture1); } catch (java.net.MalformedURLException e) { System.out.println(“Error while loading pictures...”); e.printStackTrace(); } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { engine.paint(g, this); }
}
public boolean mouseMove (Event evt, int mx, int my) { picture1.x = mx; picture1.y = my; repaint(); return true; }
Listing 32.4. This is the code that must be put into an HTML file in order to view the applet. Use a Javaenabled browser or the JDK AppletViewer to view this file once you have compiled everything. GraphicsEngine Example GraphicsEngine Example
Once you have the example up and running, the image you selected should appear in the upper-left corner of the applet’s window. Pass your mouse over the applet’s window. The image you have chosen should follow your pointer around the window. Let’s go over how the code works that linked the GraphicsEngine into the applet called Game. Our instance variables are engine, which controls all of the movable objects we can deliver, and picture1, a movable object that draws the image chosen.
Java Unleashed
Page 346
The init method is fairly straightforward. You initialize engine by setting it equal to a new GraphicsEngine. Next, the image that you chose is loaded with a call to getImage. This line creates the need for the try and catch statements that surround the rest of the code in order to catch any invalid URLs. After the image is loaded, it is used to create a new MOB, and picture1 is initialized to this new MOB. The work is completed by adding the movable object to engine so that engine will draw it in the future. The remaining lines (the lines inside the catch statement) are just there to provide information about any errors that occur. The update method is used to avoid flickering. By default, applets use the update method to clear the window that they live in before they repaint themselves with a call to their paint method. This can be a useful feature if you’re changing the display only once in a while, but with graphics-intensive programs this can create a lot of flicker because the screen refreshes itself frequently. Because the screen refreshes itself so frequently, once in a while it will catch the applet at a point where it has just cleared its window and hasn’t had a chance to redraw itself yet. This will result in a flicker. The flicker was eliminated here simply by leaving out the code that clears the window and going straight to the paint method. If you have run this example applet, you probably have already noticed that not clearing the screen might solve the problem of flickering, but it creates another problem—your movable object is leaving streaks! Don’t worry, though, the streaks will be eliminated a little later when we introduce double buffering into our graphics engine. As you can see, the Game.paint method consists of one line—a call to the paint method in engine. It might seem like a waste of time going from update to paint to engine.paint just to draw one image. Once you have a dozen or more movable objects on the screen at once, however, you’ll appreciate the simplicity of being able to add the object in the init methodand then forget about it the rest of the time, letting the engine.paint method take care ofeverything. Finally, we have the mouseMove method. This is what provides the tracking motion so that the movable object follows your pointer around the window. There are, of course, other options for user input that will be discussed later. The tracking is accomplished simply by setting the coordinates of the movable object to the position of the mouse. The call to repaint just tells the painting thread that something has changed, and the painting thread will call paint when it gets around to it, so you don’t need to worry about redrawing any more. To finish up, true is returned to inform the caller that the mouseMove event was taken care of.
Improving the Bare-Bones Engine Now that the framework has been laid for a functional graphics engine, it’s time to make improvements. Let’s start with movable objects. What should be considered when thinking about the uses that movable objects have in games? Chances are, sooner or later you’ll want to write a game with a lot of movable objects. It would be much easier to come up with some useful properties that you want all your movable objects to have now so that you don’t have to deal with each movable object individually later. One area that merits improvement is the order in which movable objects are painted. What if you had a ball (represented by a movable object) that was bouncing along the screen, and you wanted it to travel in front of a person (also represented by a movable object)? How could you make sure that the ball was drawn after the person every time, thus making it look like the ball is in front? You could make sure that the ball is the first movable object added to the engine, thus ensuring that it’s always the last movable object painted. However, that could get hairy if you have 10 or 20 movable objects that all need to be in a specific order. Also, what if you wanted the same ball to bounce back across the screen later on, but this time behind the person? The method of adding movable objects in the order you want them drawn obviously wouldn’t work, because you would be switching the drawing order in the middle of theprogram. What is needed is some sort of prioritization scheme. The improved version of the graphics engine implements a scheme where each movable object has an integer that represents itspriority. The movable objects with the highest priority number get drawn last and thus appear in front. Listing 32.5 shows the changes that need to be made to the MOB class to implement prioritization. Listing 32.6 shows the changes that need to be made to the GraphicsEngine class, and Listing 32.7 shows the changes that need to be made to the Game applet. Several other additional features also have been added, and we’ll touch on those later. The heart of the prioritization scheme lies in the new version of GraphicsEngine.paint. The basic idea is that before any movable objects are drawn, the complete list of movable objects is sorted by priority each time. The highest priority objects are put at the end of the list so that they are drawn last and appear in front, and the lowest priority objects are put at the beginning of the list so that they are drawn first and appear in back. A bubble sort algorithm is used to sort the objects. Bubble sort algorithms usually are slower than
Java Unleashed
Page 347
other algorithms, but they tend to be easier to implement. In this case, the extra time taken by the bubble sort algorithm is relatively negligible because the majority of time within the graphics engine is eaten up just by displaying the images. Compile and run the extended versions of the code in Listings 32.5, 32.6, and 32.7. After doing so, look at the init method in the Game class and pay particular attention to the priorities assigned to each movable object. From looking at the mouseMove method, you should be able to see that the first five movable objects line up in a diagonal line of sorts as long as you move your mouse slowly. If you move your mouse slowly, you should see that three of the first five movable objects are noticeably in front of the other two. This should make sense if you examine the priorities they were assigned inside the Game.init method. You also will notice that the bouncing object is always in front of the objects that you control with your mouse. This is because it was assigned a higher priority than all of the other objects. Try hitting the S key. The first object that your mouse controls now should be displayed in front of the bouncing object. Take a look at the Game.keyDown method to see why this occurs. You will see that pressing the S key toggles the priority of picture1 between a priority that is lower than the bouncing object and a priority that is higher than the bouncing object.
Listing 32.5. This is the enhanced version of the MOB class. Save this code in a file named MOB.java. import java.awt.*; public class MOB { public int x = 0; public int y = 0; public Image picture; public int priority = 0; public boolean visible = true;
}
public MOB(Image pic) { picture=pic; }
Listing 32.6. This code listing is the enhanced version of the GraphicsEngine class. Save the code in a file called GraphicsEngine.java.
Java Unleashed
Page 348
import java.awt.*; import java.awt.image.*; public class GraphicsEngine { Chain mobs = null; public Image background; public Image buffer; Graphics pad; public GraphicsEngine(Component c) { buffer = c.createImage(c.size().width, c.size().height); pad = buffer.getGraphics(); } public void AddMOB (MOB new_mob) { mobs = new Chain(new_mob, mobs); } public void paint(Graphics g, ImageObserver imob) { /* Draw background on top of buffer for double buffering. */ if (background != null) { pad.drawImage(background, 0, 0, imob); } /* Sort MOBs by priority */ Chain temp_mobs = new Chain(mobs.mob, null); Chain ordered = temp_mobs; Chain unordered = mobs.rest; MOB mob; while (unordered != null) { mob = unordered.mob; unordered = unordered.rest; ordered = temp_mobs; while (ordered != null) { if (mob.priority < ordered.mob.priority) { ordered.rest = new Chain(ordered.mob, ordered.rest); ordered.mob = mob; ordered = null; } else if (ordered.rest == null) { ordered.rest = new Chain(mob, null); ordered = null; } else { ordered = ordered.rest; } } } /* Draw sorted MOBs */ while (temp_mobs != null) { mob = temp_mobs.mob; if (mob.visible) { pad.drawImage(mob.picture, mob.x, mob.y, imob); } temp_mobs = temp_mobs.rest; } /* Draw completed buffer to g */ }
g.drawImage(buffer, 0, 0, imob);
} class Chain { public MOB mob; public Chain rest; public Chain(MOB mob, Chain rest) { this.mob = mob; this.rest = rest; } }
Java Unleashed
Page 349
Listing 32.7. This is an extended example that illustrates the properties of the GraphicsEngine class. Save this code in a file called Game.java.
import java.awt.*; import java.applet.Applet; import java.net.URL; public class Game extends Applet implements Runnable { Java Unleashed Page 350 Thread kicker; GraphicsEngine engine; MOB picture1, picture2, picture3, picture4, picture5, picture6; public void init() { try { engine = new GraphicsEngine(this); engine.background = getImage(new URL(getDocumentBase(), “background.jpg”)); Image image1 = getImage(new URL(getDocumentBase(), “one.gif”)); picture1 = new MOB(image1); picture2 = new MOB(image1); picture3 = new MOB(image1); picture4 = new MOB(image1); picture5 = new MOB(image1); picture6 = new MOB(image1); picture1.priority = 5; picture2.priority = 1; picture3.priority = 4; picture4.priority = 2; picture5.priority = 3; picture6.priority = 6; engine.AddMOB(picture1); engine.AddMOB(picture2); engine.AddMOB(picture3); engine.AddMOB(picture4); engine.AddMOB(picture5); engine.AddMOB(picture6); } catch (java.net.MalformedURLException e) { System.out.println(“Error while loading pictures...”); e.printStackTrace(); } } public void start() { if (kicker == null) { kicker = new Thread(this); } kicker.start(); } public void run() { while (true) { picture6.x = (picture6.x+3)%size().height; int tmp_y = (picture6.x % 40 - 20)/3; picture6.y = size().width/2 - tmp_y*tmp_y; repaint(); try { kicker.sleep(50); } catch (InterruptedException e) { } } } public void stop() { if (kicker != null && kicker.isAlive()) { kicker.stop(); } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { engine.paint(g, this); } public boolean mouseMove (Event evt, int mx, int my) { picture5.x = picture4.x-10; picture5.y = picture4.y-10;
Java Unleashed
Page 351
Two big features also implemented in the improved code were double buffering and the addition of a background image. This is accomplished entirely in GraphicsEngine. Notice the changes in the constructor for GraphicsEngine. The graphics engine now creates an image so that it can do off-screen processing before it’s ready to display the final image. The off-screen image is named buffer, and the Graphics context that draws into that image is named pad. Now take a look at the changes to the paint method in GraphicsEngine. Notice that up until the end, all of the drawing is done into the Graphics context pad instead of the Graphics context g. The background is drawn into pad at the beginning of the paint method and then the movable objects are drawn into pad after they have been sorted. Once everything is drawn into pad, the image buffer contains exactly what we want the screen to look like; so we draw buffer to g, which displays it on the screen. Another feature that was added to the extended version of the movable objects was the capability to make your movable objects disappear when they aren’t wanted. This was accomplished by giving MOBs a flag called visible. Take a look at the end of GraphicsEngine.paint to see how this works. This feature would come in handy if you had an object that you only wanted to show part of the time. For instance, you could make a bullet as a movable object. Before the bullet is fired, it is in a gun and should not be visible, so you set visible to false and the bullet isn’t shown. Once the bullet is fired it can be seen, so you set visible to true and the bullet is shown. Run the Game applet and try pressing the A key a few times. As you can see from the keyDown method, hitting the A key toggles the visible flag of the bouncing object between true and false. By no means do the features shown in Listings 32.5, 32.6, and 32.7 exhaust the possibilities of what can be done with the structure of movable objects. Several additional features could easily be added, such as a centering feature for movable objects so that they are placed on the screen based on their center rather than edge, an animation feature so that a movable object could step through several images instead of just displaying one, the addition of velocity and acceleration parameters, or even a collision-detection method that would allow you to tell when two movable objects have hit each other. Feel free to extend the code as needed to accommodate your needs. We haven’t actually written a game in this chapter, but we have laid the foundation for writing games. You now have objects that you can move around the screen simply by changing their coordinates. These tools have been the building blocks for games since the beginning ofgraphics-based computer games. Use your imagination and experiment. If you need more help extending the concepts described here concerning the creation of games with movable objects and their associated graphics engines, pick up a book that’s devoted strictly to game programming. Tricks of the Game-Programming Gurus, (Sams Publishing) is a good example.
Sounds We’ve spent all this time learning how to do the graphics for a game in Java, but what about sounds? Sound in Java is not yet complicated. The Java development team worked hard on the first release of Java, but they unfortunately didn’t have time to incorporate a lot of sound support. Check out java.applet.AudioClip to discover the full extent of sound use in the 1.0 release of Java. There are only three methods: loop, play, and stop. This makes life somewhat easier because the interface is so simple. Use Applet.getAudioClip to load an AudioClip in the AU format and you have two choices: Use the play method to play it at specific times or the loop method to play it continuously. The applications for each are obvious. Use the play method for something that’s going to happen once in a while, such as the firing of a gun, and use the loop method for something that should be heard all of the time, such as background music or the hum of a car engine.
Java-Specific Game Design Issues When thinking about the design of your game, there are some Java-specific design issues that you must consider. One of Java’s most appealing characteristics is that it can be downloaded through the Web and run inside a browser. This networking aspect brings several new considerations into play. Java also is meant to be a cross-platform language, which has important ramifications in the design of the user interface and games that rely heavily on timing.
Picking a User Interface When picking a user interface, there are several things you should keep in mind. Above all, remember that your applet should be able to work on all platforms because Java is a cross-platform language. If you choose to use the mouse as your input device, keep in mind that regardless of how many buttons your mouse has, a mouse in Java only has one button. Java can read from any button on a mouse,
Java Unleashed
Page 352
but it considers all the buttons to be the same button. The Java development team made the design choice to have only one button so that Macintosh users wouldn’t get the short end of the stick. If you use the keyboard as your input device it is even more critical for you to remember that although the underlying platforms might be vastly different, Java is platform-independent. This becomes a problem because the different machines that Java can run on might interpretkeystrokes differently when more than one key is held down at once. It might seem worthwhile to throw a supermove in your game that knocks an opponent off the screen, activated by holding down four secret keys at the same time. However, doing this might destroy the platform independence of your program, because there could be other platforms that don’t handle four keystrokes at once. The best way to go would be to design a user interface that doesn’t call into question whether it is truly cross-platform. Try to get by with only one key at a time, and stay away from control and function keys in general since they could be interpreted as browser commands by different browsers that your applet runs in.
Limiting Factors Because one of the main features of Java is that it can be downloaded and run across the Net, the limitations imposed by this method bear some investigation. First, please keep in mind that most people with a network connection aren’t on the fastest lines in the world. You might be ready to develop the coolest animation ever for a Java game, but keep in mind that nobody will want to see it if it takes forever to download. It is a good idea to avoid extra frills when they are going to be costly in terms of downloading time. One trick that you can use to get around a lengthy download time is to send everything that you can for downloading in the background. For instance, you could send level one of your game for downloading, start the game, and while the user plays level one, levels two and up could be sent for downloading in a background thread. This task is simplified considerably with the java.awt.MediaTracker. To use the MediaTracker class, simply add all of your images to a MediaTracker with the addImage method and then call checkAll with true as an argument. Opening a network connection can take a significant amount of time. If you have 32 or 40 pictures to send for downloading, the time this takes can quickly add up. One trick that will help you decrease the number of network connections you have to open is to combine several smaller pictures into one big picture. This decreases the number of pictures you have to send for downloading. You can use a paint program or image editing program to create a large image that is made up of your smaller images placed side by side. You then can send for downloading the large image only. This decreases the number of network connections you need to open and might also decrease the total number of bytes contained in the image data. Depending on the type of compression used, if the smaller images that make up your larger image are similar, you probably will achieve better compression by combining them into one picture. Once the larger picture has been loaded from across the network, the smaller pictures can be extracted by using the java.awt.image.CropImageFilter class to crop the image for each of the original smaller images. Another thing that needs to be kept in mind with applets is timing. Java is remarkably fast for an interpreted language, but graphics handling usually leaves something to be desired when it comes to rendering speed. Your applet probably will be rendered inside a browser, which slows it down even more. If you are developing your applets on a state-of-the-art workstation, please keep in mind that there are a large number of people who will be running Java inside a Web browser on much slower PCs. When your applets are graphics-intensive it’s always a good idea to test them on slower machines to make sure that the performance is acceptable. If you find that an unacceptable drop in performance occurs when you switch to a slower platform, try shrinking the Component that your graphics engine draws into. You also might want to try shrinking the images used inside your movable objects because the difference in rendering time is most likely the cause of the drop in performance. Another thing to watch out for is poor threading. A top-of-the-line workstation might allow you to push your threads to the limit, but on a slow PC computation time is often far too precious, and improperly handled threading can lead to some bewildering results. Notice in the run method in Listing 32.7 that we tell the applet’s thread to sleep for 50 milliseconds. You might want to try taking this line out and seeing what happens. If you’re using the AppletViewer or a browser, it will probably lock up or at least appear to respond very slowly to mouse clicks and keystrokes. This happens because the applet’s thread, kicker, is eating up all of the computation time and there’s not much time left over for the painting thread or the user input thread. Threads can be extremely useful, but you have to make sure that they are put to sleep once in a while to give other threads a chance to run.
Summary In this chapter we developed a basic graphics engine with Java that can be used for game creation. This graphics engine incorporated movable objects with prioritization and visibility settings, double buffering, and a background image. Creating the graphics engine
Java Unleashed
Page 353
itself didn’t actually accomplish the construction of a game because constructing one game would have been of limited use. The construction of a tool that can be expanded to produce a multitude of games is far more useful. This chapter also touched on issues that need to be kept in mind when developing games with Java. It is important to remember that Java is a cross-platform language and therefore will be run on different platforms. This basically means that when you develop your games you should be aware that people will want to run them on machines that might not be as capable as your machine.
Java Unleashed
Page 354
Chapter 33 Multimedia and Java Java’s multimedia capabilities are currently primitive and sometimes inadequate or nonexistent. Nevertheless, Java is positioned as an important technology for Internet-based multimedia, particularly for distributing multimedia content on demand. Furthermore, as bandwidth increases—both within corporate intranets and throughout the Internet—and Java matures and is more widely supported, its importance will grow. Sun has already promised more extensive multimedia features in future releases of Java. This chapter presents the big picture of multimedia and how it relates to Java. You can think of this chapter as your tour guide through the world of Java multimedia. Some topics are covered here in detail, while other topics have already been covered in prior chapters. In the case of topics being covered in prior chapters, you will see clear references to those chapters in case you happened upon this chapter first. Still other topics are on the horizon and can only be covered in a general sense. Nevertheless, you should finish this chapter with a solid understanding of what type of support Java currently has for multimedia, and where it’s headed in the future. Because Java has some competition in bringing multimedia to the Web, this chapter also briefly discusses alternatives to Java. In particular, the Macromedia Director movie player, Shockwave, is covered. Macromedia Director is a popular multimedia tool that enables multimedia developers to create interactive movies. More information on Macromedia Director is available at http://www.macromedia.com.
Bandwidth Limitations Before getting into multimedia as it affects Java, it’s important to look into a major limitation in dealing with multimedia on the Web: bandwidth. Bandwidth refers to the amount of information that can be sent over a network connection at a time. The multimedia potential of any Web-based media application is directly inhibited by the bandwidth of the connection. Most users across the public Internet access the Net at between 9600 and 28,800 bps. A variety of emerging technologies in the near future will aid in providing a bandwidth that enables the delivery of multimedia applications in real time. In the meantime, you must consider all the available techniques to minimize file size and ensure that your applications are compelling. In other words, no matter how cool your multimedia content is, if it takes 10 minutes to transfer to an end user’s machine, it’s basically worthless. So, you should use all the tools at your disposal to deal with the bandwidth limitation as it exists today.
Java Although more mature tools exist for creating and viewing multimedia on the Web, Java is very important even now for distributed multimedia applications on the Internet. It will be increasingly important both as its support for multimedia grows, and as bandwidth increases on the Web. Java’s main contribution will be a robust environment delivering all code necessary to run multimedia content on demand by the user. This eliminates the growing problem of the plethora of browser and plug-in providers, with each provider having multiple versions and proprietary MIME types. Java will help simplify this by requiring the client to have only the necessary Java interpreter downloaded and configured. In addition, Java also enables lower-level data handling and network access, thus raising the level of site interactivity in terms of interaction with back-end data. Presentations using Java, unlike those based on some other technologies, need not be as standalone.
Java Multimedia Features Although primitive, Java currently has multimedia features, which include the following:
Extensive graphics support, including support for drawing primitives and bitmapped images Basic digital audio support A multithreaded environment, which aids in timing and animation A media tracker for keeping up with distributed media content
Java Unleashed
Page 355
These multimedia features are provided in the standard Java API. Sun also provides an animation class for creating simple animations in addition to the multimedia support in the API classes.
Graphics Java provides a wide range of graphics support in its standard API. Java graphics features can be broken down roughly into two groups: primitive drawing routines and bitmapped image routines.
Primitive Drawing Lest you think I’m referring to etchings on a cave wall, primitive drawing simply refers to the most basic drawing functions such as lines and points. Java provides its primitive drawing support through the Graphics class. The Java Graphics class provides support for many types of primitive drawing functions, including lines, ovals, arcs, and polygons, to name a few. The Graphics class was covered in detail in Chapter 23, “The Applet Package and Graphics.”
Building Your Own Graphics Class Using Primitives Because the Java technology is still immature, even simple tasks such as drawing a simple grid could require creating a class with several methods. In the following example, we create GridDraw.class to draw a grid using primitives. The first thing we do is to import java.applet.Applet and the java.awt.*. Then, the class GridDraw is created as an extension of Panel.class. Once all variables are initialized, the GridDraw method sets the layout using BorderLayout and sets background color to white. Public method setGrid is created as void because no result is returned. This method can then be invoked by any other class that creates an instance of type GridDraw. This method receives values to determine where to start drawing (xstart, ystart), desired width and height (width, height), and number of rows and columns (numrows, numcols). The paint( ) method is invoked to draw a rectangle starting at (xstart, ystart) with a width specified by width and a height specified by height. The grid is created by drawing horizontal and vertical lines at every height/numrows and width/numcols interval throughout the loop. The last method, Dimension preferredSize() specifies a default size for the grid, and is set to 250¥250. This method listing can be seen in Listing 33.1.
Listing 33.1. GridDraw.java.
Java Unleashed
Page 356
import java.applet.Applet; import java.awt.*; public class GridDraw extends Panel{ int i,j; public int xstart=0, ystart=0, width=0, height=0, numrows=0, numcols=0; GridDraw() { setLayout(new BorderLayout()); setBackground(Color.white); } public void setGrid(int x, int y, int w, int h, int rows, int cols){ xstart=x; ystart=y; width=w; height=h; numrows=rows; numcols=cols; } public void paint(Graphics g){ g.setColor(Color.black); g.drawRect(xstart, ystart, width, height); for (int i=1; i 3.456) Getting rid of whitespace Getting rid of unnecessary normal information (VRML Normal nodes) FIGURE 34.8. Interactive Multimedia Festival in VRML.
Rendering Speed The techniques above are useful for optimizing transmission time for users with low-speed network connections. The other main area for optimization is in rendering speed. These two concerns, transmission and rendering speeds, are sometimes at cross purposes, because improving rendering speed sometimes can result in a larger file. Three important techniques for optimizing rendering time are the use of cameras, rendering hints, and levels of detail. Another concern is when to use texture maps.
The Future of VRML, Version 2.0 and Beyond The four main enhancements that are expected for version 2.0 of Virtual Reality Modeling Language are interactivity, behaviors, 3D sound, and multiuser capabilities. The VRML community currently is considering proposals for VRML 2.0. Some of them, such as
Java Unleashed
Page 368
the Moving Worlds proposal from SGI/Sony/WorldMaker, build on VRML’s 1.0 Open Inventor-inspired format. Proposals from Apple, Microsoft, and others depart from it significantly. Let’s examine what these enhancements mean.
Interactivity In an interactive virtual world, you can open doors, and watch them slowly open. You might move around the furniture in your apartment. Or hit the snooze button on your twitching, ringing alarm clock, after you get pulled out of a dream virtual world back into your home virtual world. Designing this level of interactivity into VRML will be a challenge. It also might increase file sizes significantly.
Behaviors Version 1.1 of VRML should include some limited capacity for animation. In version 2.0 and beyond you should be able to create behaviors, so that objects tend to have a minimal life of their own. Besides having some limited movements, such as a windmill turning, we might get objects that affect each other, so that when Don Quixote tries to joust with the windmill his horse shies away, or else the windmill shreds the tip of his lance. Behaviors are where Java will become most important, since most behaviors will be Java, with calls to an API that interfaces to the VRML browser. The interface mechanism is not defined yet. The behavior of an object will most likely be like a property of the geometry, with a URL link to the Java code. There is some discussion of including Java bytecodes in the VRML source.
Sound 3D sound gets very interesting and brings in a whole range of new possibilities. Sound has been a key feature in creating good immersive environments. In fact, people experimenting in VR have found that small improvements in sound will improve the immersive experience, as perceived by the user, more than small improvements in graphics quality.
Multiuser Imagine playing football with a group of people in a virtual world in cyberspace, or imagine designing a new product with them. For these applications you need to have a multiuser capability in your virtual world. In multiuser VRML, people are shown using avatars, cyberspace representations people. VRML will allow support for multiuser worlds in the future. The initial multiuser support will most likely be through Java code, that actually sends the signals of avatar position and velocity. Some of the issues that need to be tackled include logging of who is currently in the world, including killing off vampires (people who have stopped being involved even if a message has not been sent to the server saying that they are signing off). Also, in some worlds you will want collision detection to prevent two avatars from existing in the same space. In addition, there needs to be a way to hand off avatars to other servers if someone chooses a hyperlink to another world. Other considerations will include how a person’s avatar (cyberspacerepresentation) is created, how it is logged and transmitted, and what rules it might need to follow in various worlds.
Other VRML 2.0 Issues In addition to the technical issues, probably the biggest challenge for VRML 2.0 (as Mark Pesce alludes to later) is getting a consensus for what VRML 2.0 should be. At this point, there are a lot of companies, money, and people involved in VRML, each with their own interests. It will be interesting to see how things work out.
Java Meets VRML Java will drive multiuser interactions and behaviors in VRML 2.0 worlds. It is likely that other languages will be used in VRML 2.0 in addition to Java.
How Java Will Interact with VRML Java can interact with VRML in at least two ways: describing extension nodes and as scripts loaded in-line to describe the interactions of VRML objects. The first work combining VRML and Java resulted in Liquid Reality, a VRML tool kit written in Java by Dimension X. In Liquid Reality, if the Liquid Reality browser does not recognize a node, it requests the Java code describing the node from the server that served the file.
Java Unleashed
Page 369
In most of the approaches being drafted for VRML 2.0, software code can be used to animate VRML objects. The Java code can be referenced in a URL attached to the geometry. The VRML 2.0 proposals are also considering allowing Java bytecodes to be inserted directly into a VRML file. *
*LEADING VRML/JAVA PIONEERS’ THOUGHTS ON THE INTEGRATION OF *JAVA AND VRML Mark Pesce (http://www.hyperreal.com/~mpesce) is one of the creators of VRML and is author of VRML-Browsing and Building Cyberspace (New Riders, 1995), a book about VRML. According to Mark, “Java and VRML are perfect complements. Java is all about how things behave but says very little about how they appear. VRML is all about appearances, without speaking to how things behave. You could say that VRML is, while Java does. They need each other.” Mark sees Java and VRML developing, “Into a closer relationship—one where it becomes difficult to know where VRML ends and Java begins, and vice versa. I expect that when people want to talk about imaging Java, they’ll use VRML, and when they think of motivating VRML, they’ll use Java.” Mitra, the Chief Technology Officer of WorldMaker, sees VRML and Java remaining distinct. “I believe they will remain distinguishable for the foreseeable future; the equivalent is HTML and Java, where the HTML provides the structure and Java the functionality. In fact, in 3D they are likely to remain more distinguishable because the only way for the Java applets to influence the world is by manipulating a VRML scene graph.”
Dimension X’s Liquid Reality Liquid Reality is a VRML tool kit written in Java, from Dimension X. Using extension nodes, you can create worlds with flying birds (see Figure 34.9) and bouncing apples (see Figure 34.10)! *
*INTERVIEW WITH KARL JACOBS AND SCOTT FRAIZE Karl Jacobs is CEO of Dimension X (http://www.dnx.com) and Scott Fraize is Caffeinated Alchemist. Dimension X has developed Ice, a Java API for 3D, and Liquid Reality, a VRML tool kit for Java based on Ice. Q: What is Liquid Reality? Karl: It is a VRML tool kit. It provides everything you need to read in VRML and write out VRML and extend it with Java. It has all the mechanisms to do that on the fly. It’s wrapped around Intel’s 3DSound, which only took two days to add into Liquid Reality. You just use a Directed Sound node. Here’s where you are in space, here’s the sound file and there you are. Liquid Reality is based on Ice, fully integrated with the tool kit. It differs from other APIs in that Liquid Reality is written in Java, so that it provides hardware independence. It’s not as fast as Renderware and Reality Labs [the APIs other PC VRML browsers are written in] yet. We’ll be able to go to 2.0 relatively easily, whereas others will have to redesign. We can update the tool kit without re-releasing, by putting new Java classes up on the servers. Chris [Laurel of Dimension X, a Java/VRML guru] just ported Liquid Reality to SGI and Linux over the weekend. Q: How will Sun’s efforts to add 3D support to Java affect Liquid Reality? Karl: Sun’s 3D efforts are complementary to Liquid Reality. Rather than “canvas,” you’ll say “canvas3d.” Q: Will people need to be able to program in Java to do Java-enabled VRML? Scott: Right now, you do have to program a bit. However, there’s a library of plug-ins and effects, like Rotor node, that you can use. Q: What business model do you foresee? Karl: Ice is an API; we want to get it out there quickly and as widely as possible. The idea is that even if you want to put it up on your Web page as a free game, that’s fine. Liquid Reality is a tool kit so you have to pay a fee for updates and so on. The viewer is free. It’s the try and buy that seems to be proven on the Internet. FIGURE 34.9. A Liquid Reality VRML world with flying birds, a flowing stream, and a spider that runs away from you when you get close to it.
Java Unleashed
Page 370
FIGURE 34.10. A bouncing apple in a Liquid Reality VRML world. Listing 34.5 shows an example of a VRML world that has a rotating object in it, as referenced by the Rotor node.
Listing 34.5. A Liquid Reality Java-extended VRML file containing the extension Rotor. #VRML V1.0 ascii Separator { DEF BackgroundColor Info { string “0.5 0.5 0.5” } DEF Viewer Info { string “walk” } DEF ViewerSpeed Info { string “0.3” } Clock {} # PointLight { location 0 0 10 } PerspectiveCamera { position 0 0 8 orientation 0 1 0 0 } DirectionalLight { direction 1 0 0 } Material { ambientColor [ 0 0 0 ] diffuseColor [0.1 0.5 0.2 ] # specularColor [0.8 0.8 0.8] shininess 0.25 } Separator { # Rotation { rotation 0 1 0 1.57 } Sphere { radius 1.1 } } Translation { translation -3 0 0 } Material { ambientColor [ 0 0 0 ] diffuseColor [ 0.7 0.1 0.1 ] } Cube { height 1.5 width 1.5 depth 1.5 } Translation { translation 6 0 0 } Material { ambientColor [ 0 0 0 ] diffuseColor [ 0.2 0.7 0.8 ] # specularColor [1 1 1] shininess 0.4 } Separator { Rotor { rotation 1 0 0 1 } Cone {} } # Cylinder {} }
Listing 34.6 shows the Java code that describes the Rotor extension node with calls to Liquid Reality’s API.
Listing 34.6. Java code for the Liquid Reality Rotor node.
Java Unleashed
Page 371
// RotorNode.java // // Copyright 1995 Dimension X, Inc. // Chris Laurel 8-14-95 package ice.scene; import ice.Matrix4; // Rotor { // rotation 0 0 1 0 # SFRotation // speed 1 # SFFloat // } public class RotorNode extends ModelTransformationNode { static String fieldnames[] = { “rotation”, “speed” }; static NodeField defaults[] = { new SFRotation(0, 0, 1, 0), new SFFloat(1) }; public SFRotation rotation = new SFRotation((SFRotation) defaults[0], this); public SFFloat speed = new SFFloat((SFFloat) defaults[1], this); NodeField fields[] = { rotation, speed }; public int numFields() { return fields.length; } public String fieldName(int n) { return fieldnames[n]; } public NodeField getDefault(int n) { return defaults[n]; } public NodeField getField(int n) { return fields[n]; } public void applyModelTransformation(Action a) { a.state.rotateModelMatrix(rotation.getAxisX(), rotation.getAxisY(), rotation.getAxisZ(), (float) (rotation.getAngle() + speed.getValue() * a.state.getTime() % (2 * Math.PI))); } }
Paper Software Paper Software is creating a Java API for VRML. The preliminary version is code-named Xpresso, and we’ll look at a simple example to see the direction it is heading in. Expect it to be different when it is released. *
*INTERVIEW WITH MICK MCCUE Mike McCue is the founder and CEO of Paper Software, creator of the WebFX VRML browser. Paper Software was acquired by Netscape for its VRML and VRML/Java technology. Paper Software is developing a Java API for VRML. Q: How will world creators author Java-enabled VRML? A: Our goal is that you will write a series of Java scripts that orchestrate behaviors. The behaviors themselves will probably be in DLLs initially. Right now the threading model is too slow for the performance people will want. Java will be an orchestrating mechanism for the overall world. You kick off a canned behavior that exists native in our DLLs. That’s the short term. Longer term, I think there will be a set of Java classes that will give you canned behaviors. As a world author, maybe I’ll have a canned behavior that allows a man to run. I see that happening very soon after the first versions come out.
Java Unleashed
Page 372
A few months later the canned Java behaviors will come out, and you will be able to drag and drop them onto VRML objects. Longer term to that, eventually Java itself will become the behaviors engine, and you’ll have some physics such as collision detection, gravity, and elasticity in the browser, but you’ll have Java gradually implementing that too. Q: How would you describe the future of Java and VRML? Will it even be possible to differentiate between Java and VRML? A: Yes, I think it will be possible to differentiate between the two. That’s because VRML is a file format and Java is a programming language. You’ll see VRML in OCXs and C++ APIs. There’s a life beyond Java for VRML in a big way. I think it will be incorporated into Microsoft PowerPoint and Excel. I think it will be incorporated into your mission-critical applications. Our goal is to begin with Java but to make sure we don’t back ourselves into a corner by relying on it. We want to be language neutral. On the Internet, Java and JavaScript will be the way to go. Q: You’ve mentioned an interest in VRML as a starting point for multidimensional interfaces. What do you mean by that? A: I gradually see the bulk of applications and operating systems today taking more advantage of 3D space to organize info rather than 2-D windows. I think 3D represents the next major user interface step. You will run all of these applications in a 3D environment. It will become a core, centralized part of the operating system. This example is a bouncing ball, with the Java code javaball.java. It is referenced by putting the line DEF BALL Sphere {} in the VRML file. Figures 34.11 and 34.12 show screen shots of the ball in action. FIGURE 34.11. A bouncing ball in midair created with Paper's Java API. FIGURE 34.12. The bouncing ball is now squished on the ground. This is what the Java code looks like. Notice that import xpresso brings in Paper’s VRML API. You can see the calls to the API that start with webfxObject. (See Listing 34.7.)
Listing 34.7. Java code for the bouncing ball using Paper Software’s preliminary API.
// Bring in some Java classes we will need import java.awt.Graphics; import java.util.Date; import java.lang.Math; Java Unleashed Page 373 import xpresso; // A Java class that runs in its own thread as an applet Âpublic class javaball extends java.applet.Applet implements Runnable { // variables Thread ballThread; int y = 0 ; float fTemp = 0.0f; float fHeight = 0.0f; int object_id = 0 ; int lSession = 0 ; int lObjectID = 0 ; float ff[] = new float[16] ; // Create an object to interface with VRML xpresso webfxObject = new xpresso() ; /********************************************************************** // init method ***********************************************************************/ public void init() { // Initialize the VRML interface while (lSession == 0) lSession = webfxObject.XpressoInit() ; // Get pointers to the Ball object if (lSession != 0) { String strCube = “Ball”; String strFloor = “Floor”; while (lObjectID == 0) lObjectID = webfxObject.XpressoGetObject(lSession, 0, strCube) ; } } /********************************************************************** // start method ***********************************************************************/ public void start() { if (ballThread == null) { ballThread = new Thread(this, “Ball”); ballThread.start(); } // Initialize to Identity matrix ff[0]= 1.0f; ff[1]= 0.0f; ff[2]= 0.0f; ff[3]= 0.0f; ff[4]= 0.0f; ff[5]= 1.0f; ff[6]= 0.0f; ff[7]= 0.0f; ff[8]= 0.0f; ff[9]= 0.0f; ff[10]= 1.0f; ff[11]= 0.0f; ff[12]= 0.0f; ff[13]= 0.0f; ff[14]= 0.0f; ff[15]= 1.0f; } /********************************************************************** // run method ***********************************************************************/ public void run() { while (ballThread != null) { fHeight = 0.0f; // Ball falls down in 20 steps for (y = 1; y < 20; y++)
Java Unleashed
Page 374
Summary The explosion of interest in the World Wide Web has been incredible! VRML promises to ramp this exponential growth up to a new level of interactivity and feeling. Java will be the life force and pump to drive motion and interaction in advanced VRML worlds. As VRML moves into its 2.0 version, and Java adds life to it, virtual reality on the Net will become as commonplace as HTML is today. Java-based interactivity, animations, and behaviors will enliven virtual worlds with personality and attitude. Businesses will take advantage of virtual reality on the Web, starting with marketing efforts and graduating to original content.
Java Unleashed
Page 375
Chapter 35 Multiuser programming Without doubt, Java is one of the more spectacular products to hit the computer market in recent years. Inasmuch as its initial support came from academia, most initial Java applets have been limited to decorative roles. However, now that Java’s popularity has increased, Java has recently been utilized for more practical purposes. Applets have been used to enhance the speed of search engines and to create interactive environments for such purposes as retrieving financial information. In essence, by employing the power of Java in such applets, users are now able to do much of what they would think they should be able to do on the Internet. Nevertheless, one of the more exciting powers of Java is that it gives programmers the ability to create multiuser environments in which many users can interact and share information. In a simple multiuser environment (see Figure 35.1), several users are connected to each other through a server running on a mutually accessible host. As a result, all actions by one user can instantaneously be displayed on the screens of other users across the world—without any requirement that the users know each other beforehand. FIGURE 35.1. A multiuser environment. This feature enables Java programmers to accomplish feats never before possible in a Web page. A business can now set up such a system so that people who desire information could talk with a customer service representative directly on their Web page, rather than sending e-mail to a service department. Additionally, an enterprising company could allow people from around the world to participate in a real-time live auction. In general, by facilitating multiple-user environments that enable such sharing of information, Java has the power to revolutionize the Web. *
*WHY JAVA? The concept of servers, sockets, and communication is nothing new, nor is the idea of linking many users together in a live-time environment. In fact, the Web, without Java, can be considered a multiuser environment inasmuch as multiple users can access the same information in the form of a simple HTML page. Furthermore, methods exist by which a user can directly affect what another user sees on a given page (such as the hit counters found on the bottom of many HTML pages). What, then, does Java offer that is so revolutionary? It offers the capacity to perform actions while the user is viewing the same HTML page. The architecture of the Web enables users to view Web pages from around the world. However, once this page is displayed on your screen, it cannot change. In the example of the hit counters—although more users may access the given page— you will never be informed of this fact unless you reload that page. Java, however, brings life into Web pages (by means of applets), thus enabling them to perform actions such as communicating with a server. Java enables programmers to create applets as potent and effective as the applications on your own computer, and it is this capacity that enables us to create such multiuser environments. In this chapter we discuss the elements required to create such a multiuser environment in Java. Although no entirely new topics in Java are presented, this chapter shows you how to bring several powerful aspects of Java together and highlights some of the more interesting applications of Java. Through explanation of the processes as well as sample code, this chapter enables you, the Java programmer, to code and establish your own multiuser environment. This chapter deals with subjects including the handling of the connections to the server, the graphical interface, and various animation and threading techniques necessary to make your applet Web-worthy. While each of these topics is illustrated with code, keep in mind that the essence of a multiuser environment is what you do, not how you do it. Furthermore, the code for each multiuser application is heavily dependent on the environment itself, and therefore significantly different in each. Consequently, while the code offered here can supply you with a suitable framework, it may be advisable to envision your own multiuser application. As each topic is presented, imagine how you would deal with each of the issues involved. Remember that in programming, and Java in particular, the limits of the language are the limits of your imagination. Be creative and have fun! *
*NOTE ON SOCKETS AND NETSCAPE With any new technology comes new concerns and problems. Java is definitely not an exception to this rule. The chief concern of many has been the fact that Java applets can connect to servers from around the world and transmit information from the host that its owner might not want to make public. While there are several solutions to this problem, Netscape has chosen to place stringent restrictions on Java socket connections for now. Currently, Java sockets can connect only to servers running on the host that served the applet. For example, an applet residing http://www.xyz.com/page.html can only connect back to a server running on www.xyz.com. Therefore, when developing a multiuser environment as discussed in this chapter, make sure that the server to which the applet connects is running on the same host as the
Java Unleashed
Page 376
HTML page in which the applet resides. (See Chapter 40, “Java Security,” for more details about applet security.)
Our Mission Should We Choose toAccept It… In this chapter we develop a multiuser environment for a museum that is opening an exhibition of Haphazard Art. The museum believes that the most beautiful art is created by human-controlled whims, and thus has hired you to create an environment through which users from around the world can “weave” a quilt. The user should be able to access the Web page, select a color and a blank tile, and “paint” the tile. Not only should this tile change colors on the screen of the user, but it also should instantaneously become painted on the screens of all the users around the world who are working on that quilt. (The museum will then save these designs and display them at its upcoming exhibit.) While this is a rather simplistic example of the power of multiuser environments, it is an excellent model for the explanation of the concepts involved.
Requirements of the Server Although it is not of our direct concern, the server in this environment plays an extremely large role. Because this server can be written in virtually any language, we won’t spend much time dealing with it here. However, it is necessary that we discuss the essentials for the server in a multiuser environment. As you can see from the Figure 35.1, the server acts as the intermediate agent between the various users. Thus, the server must be able to do the following:
Accept multiple connections from clients on a given port Receive information from the clients Send information to the clients As the application becomes more involved, it is necessary to add additional functionality to the server. Even in the simple example of the museum quilt, it’s necessary that the server keep track of the color of all the tiles. Furthermore, if a new client begins work on an in-progress quilt, it’s necessary for the server to inform the client of the current status of the quilt. Nevertheless, these subjects are extremely context-dependent and deal more with computer science than Java. Therefore, we now move on to more exciting matters, such as the socket communication required to provide the interaction.
Integrating a Communication Class in Your Applet For an effective implementation, it’s necessary to create a class that handles all the interaction with the server. This class manages such responsibilities as connecting and disconnecting from the server as well as the actual sending and receiving of data. By encapsulating this functionality in a separate class, we are able to deal with our problem in a more effective and logical manner. A more important reason for encapsulating this functionality in a separate class, however, is the fact that in a multiuser environment the applet must do two things at the exact same time: listen for user interactions (such as a mouse click) and listen for new information from the server. The best way to do this is to create a separate class to handle the server interaction, and make that class a thread. (See Chapter 15, “Threads and Multithreading,” for details on creating threads.) This threaded class will continually run for the lifetime of the applet, updating the quilt when required to do so. With this problem out of our hands, we can allow the applet class to respond to user interactions without worrying about the socket connection.
How to Connect to a Server Assuming that we have a suitable server running, the actual communication is not a very complicated process. In essence, the applet need only to connect to the server and read and write information to the appropriate streams. Conveniently, Sun has wrapped most of these methods and variables in the java.net.Socket class. Here is the beginning of our Client class that handles the actual communication:
Java Unleashed
Page 377
import java.net.Socket; import java.io.*; public class Client extends Thread { private Socket soc; public void connect () { String host = “www.museum.com”; int port = 2600; soc = new Socket(host, port); } } Note that it is necessary for you to know both the name of the host on which the server is running as well as the port on which the server can accept connections. *
*CHANGES IN THE JAVA LANGUAGE Those programmers who have been involved with the Java language know that there were some changes to the Java language in going from the alpha to beta standards as well as in the transition from the beta1 to beta2 standards. While the transition from the beta1 to beta2 standards was not very dramatic, there was a very important change regarding private variables and their accessibility. Consequently, while much of the code required for a multiuser environment could be developed with the sun.NetworkClient class in the beta1 version, access restrictions to the serverInput and serverOutput streams have made this approach impossible in the beta2 version and all later versions, including the 1.0 JDK. The best means to provide a multiuser environment using the 1.0 API is through the use of the java.net.Socket class. (You will note that the java.net.Socket class uses the methods getInputStream() and getOutputStream() to provide access to these restricted streams.) The code in this chapter has been tested with the 1.0 version of the JDK as well as the 2.0 version of Netscape, and should work with all later versions of Java and Netscape. While in theory the previous method is sufficient to connect to a server on a known port, any experienced programmer knows that what works in theory does not always work in practice. Consequently, it is good practice to place all communication statements in a try-catch block. (Those unfamiliar with the throw and try-catch constructs can refer to Chapter 16, “Exception Handling.”) In fact, forgetting to place all statements that deal with the server in a try-catch block will produce a compile-time error with the Java compiler. Consequently, the precious method should actually look like this: import java.net.Socket; import java.io.*; public class Client extends Thread { private Socket soc; public boolean connect () { String host = “www.museum.com”; int port = 2600; try { soc = new Socket(host, port); } catch (Exception e) return(false); return(true); } Note that the method now returns a boolean variable in accordance to whether or not it was successful in connecting.
Java Unleashed
Page 378
How to Communicate with the Server Inasmuch as different situations require different constructs, Java supplies us with several options for the syntax of communication with a server. While all such approaches can work, some are more useful and flexible than others. Consequently, what follows is a rather generic method of communicating with a server, but keep in mind that there are some nice wrapper methods in the java.io.InputStream subclasses that you may wish to use to simplify the processing of the parsing involved with the communication.
Sending Information Once we have established our connection, the output is then passed through the java.net.Socket.outputStream, which is a java.io.outputStream. Because this OutputStream is a protected variable, however, we cannot reference it as simply as one might imagine. In order to access this stream we must employ the java.net.Socket.getOutputStream() method. Thus, a simple method to send data would resemble the following: public boolean SendInfo(int choice) { OutputStream out; try { out = soc.getOutputStream(); out.write(choice); out.flush(); } catch (Exception e) return(false); return(true); } Although the preceding code is sufficient to send information across a socket stream, com-munication will not be that simple in a true multiuser environment. Because we will have multiple clients in addition to the server, it is necessary to define a common protocol that allparties will speak. While this may be as simple as a specific order to a series of integers, it is nevertheless vital to the success of the environment. *
*A NOTE ON PROTOCOLS While we will adopt a rather simple protocol for this applet, there are a few issues that one should keep in mind when developing more complex protocols: Preface each command by a title, such as a letter or short word. For example, B123 could be the command to buy property lot 123, and S123 could be the command to sell lot 123. Make all titles of the same format. This will make parsing of the input stream much easier. For example, B and S are good titles. B and Sell would cause some headaches. Terminate each command with a common character. This will signify the end of information and will also give you a starting point in the case that some of the information becomes garbled. For the sake of simplicity, the newline character (\n) is usually best. In the case of our quilt-making applet for the museum, each update packet will consist of three things: the x-coordinate of the recently painted tile, the y-coordinate of the recently painted tile, and the color that is to be applied (which could also be represented by an integer). Additionally, we will use the newline character as our terminating character. All of this can be accomplished with the following method:
Java Unleashed
Page 379
public boolean SendInfo(int x, int y, int hue) { OutputStream out; try { out = soc.getOutputStream(); out.write(x); out.write(y); out.write(hue); out.write(‘\n’); out.flush(); } catch (Exception e) return(false); return(true); }
Reading Information Another factor that complicates the reading of information from the socket is the fact that we have absolutely no idea when new information will travel across the stream. When we send data, we are in complete control of the stream and thus can employ a rather simple method as we did earlier. When reading from the socket, however, we do not know when new information will arrive. Thus we need to employ a method that will run constantly in a loop. As discussed previously, the best way to do this is to place our code in the run() method of a threaded class. *
*A REFRESHER ON THREADS Before we proceed in our development of this threaded subclass, it is important to review a key concept. As presented in Chapter 15, a class that extends the definition of the java.lang.Thread class is able to continually execute code while other portions of the applet are running. Nevertheless, this is not true of all the methods of the threaded class. Only the run() method has the power attributed to threads (namely the capacity to run independently of other operations). This method is automatically declared in every thread, but will have no functionality unless you override the original declaration in your code. While other methods can exist within the threaded subclass, remember that only the code contained in the run() method will have the capacity to execute concurrently with the rest of the processes in the application. Furthermore, remember that the run() method of class foo is begun by invoking foo.start() from the master class Also note that because we are overriding a method first declared in the java.lang.Runnable interface, we are forced to employ the original declaration: public void run(). Your code won’t compile if you choose to use another declaration, such as public boolean run(). (We deal with ways of returning data in the section on sharing information later in this chapter.) What results is the method shown in Listing 35.1. In reading it, pay attention to how we are able to stop the method from reading from the socket once the user is done; how the method would deal with garbled input; and how the method returns information to the main applet. (You will be quizzed later!).
Listing 35.1. Our method to read information from the socket.
Java Unleashed
Page 380
public void run() { int spot = 0; int stroke[]; stroke = new int[10]; // an array for storing the commands DataInputStream in; in = new DataInputStream(soc.getInputStream()); while (running) { do { // reads the information try { stroke[spot] = in.readByte(); } catch (Exception e) stroke[spot] = ‘\n’; // restarts read on error } while ( (stroke[spot] != ‘\n’) && (++spot < 9) ); // reads until the newline flag or limit of array spot = 0; // resets the counter quilt.sow(stroke[0],stroke[1],stroke[2]); } }
Okay. Time is up. Here are the answers. First of all, we must remember that we are designing this class to serve within a larger applet. It would be rather difficult for the Client class to decide when the “painting session” is over for it has no idea what is going on inside the applet. Thus, the applet, not the Client class, must have control of when the class will be looking for information. The best way to retain control of the run() method is to create a boolean field (running) inside the Client class itself. This variable can be set to true on connection to the server and can be set to false when the applet decides to close the connection. Thus, once the user has decided to close the connection, the run() method will exit the while loop and run through to its completion. Second, we must remember that while we plan on receiving three integers followed by a newline character, in real life things do not always work as we plan them. Inasmuch as we do not know what exactly the error will be, there is no ideal way of handling garbled information. The previous method uses the newline character as its guide, and will continue to read until it reaches one. If, however, there are nine characters before a newline character, it will exit the do loop regardless and call the sow() method. This means that the sow() method in our main applet must be well constructed to handle such errors. Third, and most important, is the means by which the class returns data to the applet. As we discussed earlier, this Client class will be a subclass of our main applet. Consequently, we will be able to call all public methods of the applet class. (In this example, the applet is referenced by the variable quilt.) While we will discuss this process in more detail later, keep in mind that the last line of code quilt.sow(stroke[0],stroke[1],stroke[2]); simply passes the appropriate values to a method in the applet that will in turn process them. *
*A NOTE FOR ADVANCED READERS You will notice that in this case, all the information coming across the stream is read in the form of integers. Obviously, in the real world other data types such as characters, or even mixed data types such as three numbers and a letter, could be passed. While this would require slight alterations to the above code, it would not be a dramatic hack. While you could deal with such a situation by having separate read() statements for each data type, keep in mind the ASCII relationship between characters and numbers. Because the ASCII value of a number is 48 more than that number (for example, ‘1’ = 1 + 48), you can choose to read all the data in as integers and then translate certain values to their character equivalents, or vice versa depending on how you sent the information to the server. Finally, keep in mind that serverInput is a basic java.lang.Inputstream that can be manipulated in several ways using the powers of the java.lang and java.io classes and methods. (See Chapter 18, “The Language Package,” and Chapter 20, “The I/O Package,” for more details on java.lang and java.io, respectively.)
Java Unleashed
Page 381
Disconnecting So far, we have been able to connect to the server and communicate with it. From the point of view of the user, we have begun the application and have successfully painted our quilt. We therefore have only one essential functionality that we haven’t included in our Client class thus far: the capacity to disconnect from the server. While the capacity to disconnect from the server may appear trivial, it is in fact very essential to a successful multiuser environment. A server can be constructed to accept numerous clients, but due to hardware constraints, there is an inherent limit to the number of clients that a server can have connected at the same time. Consequently, if the socket connections remain open even when the clients leave, the server will eventually become saturated with lingering sockets. In general, it’s good practice to close sockets, for it benefits all aspects of the environment, ranging from the Internet-access software that the user is running to the person managing the server. Without further ado, here is an appropriate disconnect() method for our Client class: public boolean disconnect () { running = false; try { soc.close() } catch (Exception e) return(false); return(true); } While the code itself is nothing extraordinary, do note the use of the Boolean variable running (also used in the run() method). You will remember that the function of the running variable was to serve as a flag that allowed the client to listen to the socket stream. Inasmuch as we do not want the client to be listening to the socket stream after the user has decided to disconnect, we set the variable to be false in this method. Also note that, the running = false statement comes before the soc.close() statement. This is because regardless of the success of the disconnect statement, we no longer wish to listen to the stream.
The Graphical Interface Now that we have developed the framework of our Client subclass, let’s switch gears for a moment to develop the applet class that will act as the heart of our application. While not extremely intricate, the applet must be able to respond to user input and present the user with a friendly and attractive display. Fortunately, as a Web-based programming language, Java is very well suited for this. Each applet that you create inherits various methods that enable it to respond to virtually anything that the user can do. Furthermore, the java.awt package provides us with some excellent classes for creating good-looking interactive features such as buttons and text areas.
Responding to Input The most important aspect of a graphical interface is its capacity to monitor the user’s responses to the system. As you have seen in Chapter 22, “The Windowing Package,” Java supplies us with an excellent system for doing this in the form of the java.awt.Event class. Additionally, the java.awt.Component class, of which java.applet.Applet is a subclass, provides us with many methods that we may use to catch and process events. These methods will seize control of the applet under appropriate conditions, thereby enabling the applet to react to specific user events. For our museum quilt applet, the user must be able to change the current color, select a tile, and quit the application. If we decide that the user will select the tile through a mouse click and change colors and use the keyboard to quit, we would require two interactive methods from the java.awt.Component class: keyDown(Event, int) and mouseDown(Event, int, int). In this setup, the keyboard has two purposes: to enable the user to change colors and to enable him or her to quit. If we can choose to present the user with a pallet of numbered colors (0 to 9, for example), from which he or she can select, our keyDown() method could be as simple as
Java Unleashed
Page 382
public boolean keyDown(Event evt, int key) { if ( (key >= ‘0’) && (key = xoff) && (x =< xoff + xsize) && (y >= yoff) && (y =< yoff + ysize) { // checks to see if the click was within the grid x_cord = (x - xoff)/scale; // determines the x and y coordinates y_cord = (y- yoff)/scale; // of the click museum.sendInfo(x_cord, y_cord, current_color); // makes use of the // sendInfo method to send the data return(true); // the click was valid } return(false); // the click was outside the bounds of the grid } Note that in the previous method it is necessary to have already declared the x and y offsets, the size of each tile (scale), and the size of the grid itself as global fields of the class.
Displaying Information In Chapter 23, “The Applet Package and Graphics,” you were given an overview of how to create a background and fill it in with whatever you would like. In a dynamic graphical interface, however, there will be various properties that will change and thus must be tracked in some manner. Our quilt application may consist of nothing more than a single colored background on which tiles of different colors are drawn. Consequently, the only information that we are concerned with is the color (if any) that has been assigned to the individual tiles. A simple way of doing this is to create an array of java.awt.Color (hues[ ]) and assign each color to be used a given value (hues[1] = Color.blue, for example). Thus, we can store the design in simple array of integers: tiles[ ][ ], with each integer element representing a color. If we do so, our paint() method can be accomplished by the following code: public void paint(Graphics g) { for (int i = 0; i < size; i++) { for(int j = 0; j < size; j++) { g.setColor(hues[ (tiles[i][j]) ]); g.fillRect( (i*scale+xoff) , (j*scale+yoff), scale,scale); } } } Note that in the preceding example, g.fillRect( (i*scale+xoff) , (j*scale+yoff), scale,scale); will paint squares with length and width equal to a final field, scale, that are located on the grid with respect to the initial x and y offsets.
Java Unleashed
Page 383
How to Thread the Client Class Thus far, we have been successful in creating the interface that the user will find on our Web page, as well as some of the methods necessary to facilitate the user’s interaction with the applet. We have also assembled a Client subclass that will serve as our means of communication.Our next step is to integrate the Client class within the applet class to produce a cohesiveapplication. Nevertheless, it is important to keep in mind that we are not using this subclass merely as a means of keeping the information separate. The main reason for creating a separate class is that by making it a thread as well, we are able to perform two tasks at the exact same time. *
*NOTE Although it may seem as if we are able to perform two tasks at the exact same time, this is not entirely true on a standard singleprocessor computer. What Java (and other time-sharing applications such as Windows) does is portion “time-slices” to each thread, allowing each to run for a brief moment before switching control over to the other. Because this process is automatically done by the Java virtual machine and because the time for which a given thread is not running is so small, we can think of the two threads as running at the same time. Although we almost forget about its existence, we must retain some control over the Client class. We do not want it checking for new commands before the socket connection has been opened or after it has been closed. Additionally, we want to be able to control such socket events as opening and closing in a manner that will isolate them from the main applet, but will also impact the status of our run() method. Conveniently for us, we developed the Client class in such a manner: keeping the connect() and disconnect() methods separate entities, both of which have control over the run() method (by means of the boolean variable running). Consequently, within the applet class, the code necessary to control communications is quite simple: public class Project extends Applet { public Client museum; . . . public void init() { museum = new Client(); . . museum.connect(); museum.start(); } . . public void leave() { museum.disconnect(); } } First of all, note that this is the first time that we have dealt with the applet itself. Thus far its only important property is that it is named Project. Also note the use of the constructor Client(). Like any other class, the Client class requires not only a declaration, but also a new statement to actually create and allocate memory for the class. We will deal with this constructor again in the next section. Finally, note that in the previous example we established the connection by means of the museum.connect() statement located the init() method. Most likely (and in the case of the museum applet), we will want to establish the socket stream as soon as the applet starts up. Consequently, the most logical place for the museum.connect() statement is in the init() method. Nevertheless, you may place this code anywhere you may like in your applet. (You may, for example, have a “Connect to Server” button somewhere in your applet.) *
*WARNING While giving the user the ability to initiate connection may be a good idea, be careful that you not do allow the same user to connect the server more than once without first disconnecting. This can lead to all sorts of headaches and improper results.
Java Unleashed
Page 384
How to Share Information We have created the framework of both the Project (applet) and Client classes and have begun the process of intertwining them by creating an instance of the Client class in the Project class. However, there is more to the interaction between the two classes than what we have thus far. Remember that the purpose of the Client subclass is to provide information to the applet. Nevertheless, we are required to employ the run() method of the threaded Client class, which does not allow us to return information through a simple return() statement. Also, in most cases (as well as the museum application), we must return several pieces of information (the x and y coordinates of the tile being painted as well as its color). However, with a little construction, we can easily solve this problem. First, we must enable the Client class to refer to the Project class. Thus, we must make a few alterations to the Client class. The changes will allow the Client class to accept the Project applet class in its constructor method and make use of it during its lifetime. public class Client extends Thread { private Project quilt; public Client (Project proj) { quilt = proj; } ... }
* *NOTE Note that in this setup, the parent class is required to pass itself as an argument to the constructor method. Therefore the appropriate syntax would resemble the following: museum = new Client(this); What exactly does the previous code accomplish? First, it establishes a constructor method for the Client class. (Remember that as in C++, constructor methods must have the same name as the class itself.) While this constructor may serve other purposes, its most important function is to accept a reference to a Project class as one of its arguments. Furthermore, we have created a public variable, quilt, of type Project. By doing so, each method of the Client class is now able to reference all public methods and variables in the Project class. For example, if we had the following definitions in the Project class public int tiles_remaining; public void sow(int x, int y, int hue) { ... } the Client subclass could reference quilt.tiles_remaining, and quilt.sow().
The “Translating” Method Okay, time for an assessment of where we stand. We are now able to
Connect to the server Monitor the user’s actions
Java Unleashed
Page 385
Display the current design Receive and send information Create a client “within” the main applet Enable the client to reference the main applet As of now, whenever the user clicks on a square, the “request to paint” is immediately sent to the server by the sendInfo() method. Nevertheless, we have not yet developed a true method of translating a command after it comes back across the server stream. We have, however, laid the foundation for such a process. By enabling the Client subclass to refer to all public variables and methods of the Project applet class, we have given ourselves access to all of the necessary information. In fact, we have many options for updating the data in the applet once it has been parsed from the server stream. Nevertheless, some approaches are much better than others. The most secure and flexible approach is to create a “translating” method in the applet class, such as the sow() method in the Project class—mentioned several times in this chapter. This method will serve as the bridge between the client and applet classes. Why is this approach the best? Why can’t we create some public variables in the Project class that can be changed by the Client class? There are several reasons.
Why Use a Translating Method? The first such reason is that it makes life a great deal easier for the programmer. By employing a “translating” method, we are able to maintain the encapsulation enjoyed thus far in our application. The sow() method will be contained entirely within the Project class and thus will have all the necessary resources, such as private variables that would be hidden from the Client subclass. Furthermore, when we are actually coding the application, we will be able to focus on each class independently. When we code the Client class, we can effectively forget how the sow() method will work, and when we code the Project class, we can trust that the appropriate information will be sent to it. The second reason for having the Client class rely on a foreign method is flexability. If we decided to revamp this application, thereby changing the manner in which we stored the data, we would have no need to drastically change the Client class. In fact, in the worst case, the only changes necessary would be to increase the amount of data being read from the stream and the number of parameters passed to the sow() method. The third reason for employing a translating method is that by linking the two classes by a method rather than direct access, we are able to prevent the corruption and intermingling of data that can occur when two processes attempt to access the same data at the same time. For example, if we made tiles[ ][ ], the array that contained the design in the applet class to be public, we could change the colors with a statement such as this: quilt.tiles[(stroke[0])][(stroke[1])] = stroke[2]; However, what would happen if at the exact moment that the paint() method was accessing tiles[1][2], the Client class were changing its value? What if more data had to be stored (the color, the design, the author’s name, and so on)? Would the paint() method get the old data, the new data, a mixture, or none? As you can see, this could be a catastrophic problem. However, if we use a separate “translating” method in our applet class, this problem could easily be solved through use of the synchronized modifier. If our applet grew to the point that this problem presented itself, we could make the paint() method synchronized and place any necessary statements in a synchronized block. As a result, these portions of our code would never be able to run at the exact same time, thereby solving our problem.
How to Create a Translating Method Now that we have seen why the sow() method is necessary, let us discuss its actual code. As we discussed earlier, each command in the museum application will consist of three integers: the x coordinate, the y coordinate, and the new color. Also remember that in our discussion of the Client class, we noted that the sow() method must be able to handle corrupted data. Consequently, the sow method could look something like this:
Java Unleashed
Page 386
public void sow(int x, int y, int hue) { if ( (x>=0) && (x = 0) && (y = 0) && (hue =0) && (x = 0) && (y = 0) && (hue  help ** command list ** threads [threadgroup] -- list threads thread -- set default thread suspend [thread id(s)] -- suspend threads (default: all) resume [thread id(s)] -- resume threads (default: all) where [thread id] | all -- dump a thread’s stack threadgroups -- list threadgroups threadgroup -- set current threadgroup print [id(s)] -- print object or field dump [id(s)] -- print all object information locals -- print all local variables in current stack frame classes -- list currently known classes methods -- list a class’s methods stop in .<method> -- set a breakpoint in a method stop at : -- set a breakpoint at a line up [n frames] -- move up a thread’s stack down [n frames] -- move down a thread’s stack clear : -- clear a breakpoint step -- execute current line cont -- continue execution from breakpoint catch -- break for the specified exception ignore -- ignore when the specified exception list [line number] -- print source code use [source file path] -- display or change the source path memory -- report memory usage gc -- free unused objects load classname -- load Java class to be debugged run [args] -- start execution of a loaded Java class !! -- repeat last command help (or ?) -- list commands exit (or quit) -- exit debugger >
Using JDB to Debug Your Program From our experience, the easiest way to learn JDB is to gain hands-on experience through using it. With this in mind, let’s use JDB to debug the simple class that follows. We chose a simple class for the benefit of people skipping ahead to learn how to use the basic features of the debugger. But don’t worry if you’ve read all of the previous chapters of this book—we’ll still cover all the advanced features of JDB.
Java Unleashed
Page 393
AddNumbers is a simple class that implements both the user interface and the algorithm to add two numbers together. Well, at least this is what it’s supposed to do. In reality, the class has a simple bug that will be located using JDB. On the supplied CD-ROM, there are two Java classes: StartApplet, which is the requisite subclass of the Applet class that instantiates the AddNumbers class, and StartApplet.html, which is used by AppletViewer to load the applet.
Listing 36.2. AddNumbers.java.
import java.awt.*; public class AddNumbers extends Frame { int LeftNumber = 5; int RightNumber = 2;
Java Unleashed
Page 394
TextArea taResult; public AddNumbers() { setTitle(“JDB Sample Java Program”); setLayout(new BorderLayout()); Panel p = new Panel(); p.add(new Button(“5”)); p.add(new Button(“1”)); add(“West”, p); Panel g = new Panel(); g.add(new Button(“2”)); g.add(new Button(“3”)); add(“East”, g); taResult = new TextArea(2,1); taResult.setEditable(false); add(“South”, taResult); pack(); resize(300,200); show(); } public void ComputeSum () { int Total = LeftNumber - RightNumber; String ConvLeft = String.valueOf(LeftNumber); String ConvRight = String.valueOf(RightNumber); String ConvTotal = String.valueOf(Total); taResult.setText(ConvLeft + “ + “ + ConvRight + “ = “ + } public void paint(Graphics g) { ComputeSum(); } public boolean handleEvent (Event evt) { switch (evt.id) { // Was the termination button pressed? case Event.WINDOW_DESTROY: { // Yes! So exit gracefully. System.exit(0); return true; } default: } // Was the “5” button pressed? if (“5”.equals(evt.arg)) { LeftNumber = 5; ComputeSum(); return true; } // Was the “1” button pressed? if (“1”.equals(evt.arg)) { LeftNumber = 1; ComputeSum(); return true; } // Was the “2” button pressed? if (“2”.equals(evt.arg)) { RightNumber = 2; ComputeSum(); return true; } // Was the “3” button pressed? if (“3”.equals(evt.arg)) { RightNumber = 3; ComputeSum();
ConvTotal);
Java Unleashed
Page 395
Compiling for JDB Before starting our debugging session, we need first to compile our Java applet to include extra information needed only for debugging. This information is needed so that the debugger can display information about your applet or application in a human comprehensible form, instead of a confusing wash of hexadecimal numbers (don’t laugh—the first debuggers required you to do this translation—so count your lucky stars!). In order to compile your program with debugging information enable, change to the \java\classes\AddNumbers directory and issue the following commands: C:\java\classes\AddNumbers> javac_g -g AddNumbers.java C:\java\classes\AddNumbers> javac_g -g StartApplet.java The javac_g compiler is functionally identical to the javac compiler used in previous chapters, except that it doesn’t perform any optimizations to your applet or application. Optimizations rearrange the statements in your applet or application to make them faster. This rearrangement makes it more difficult to conceptualize program flow when you are debugging, so using javac_g in conjunction with JDB is useful. The -g command-line option tells the compiler to include line number and object names in the output file. This allows the debugger to reference objects and line numbers in a program by source code names instead of the Java interpreter’s internal representations.
Setting Up a Debugging Session The next step in debugging a Java application or applet is to start JDB. There are two ways to do this, depending on whether you are debugging an applet or an application. Because we are debugging a applet in our example, we will use the AppletViewer program supplied in the Java Development Kit to load JDB indirectly. If we were trying to debug an application instead, we would use the following command to start JDB: C:\java\classes\AddNumbers> jdb MyApplication Again, because we are debugging an applet in our example and not an application, we will not start JDB in the preceding manner. However, for future reference, after invoking the debugger, using JDB on a Java application is identical to using it on an applet. With that important distinction covered, we start the AppletViewer with the following command: C:\java\classes\AddNumbers> appletviewer -debug StartApplet.html The -debug flag specifies to the AppletViewer that it should start up in JDB instead of directly executing the AddNumbers class. Once AppletViewer loads, it will open its applet window and display something similar to the following in the command-line window: C:\java\classes\AddNumbers> appletviewer -debug AddNumbers.html Initializing jdb... 0x139f2f8:class(sun.applet.Appletviewer) >_ The first thing you should notice about JDB is that it is command-line based. Although this makes the learning curve for JDB a little more steep, it doesn’t prevent us from doing anything that you might be familiar with in a visual debugging environment. Before going further, examine the third line of the preceding output. This indicates where the debugger is stopped in its execution of our applet. In this case, it is stopped during the execution of Sun’s applet.Appletviewer class. This is logical, because applet.Appletviewer is the class that is transparently loaded and executed to load an applet (you’re learning things by using JDB already!). The hexadecimal number that prefixes this on the third line is the ID number assigned to the sun.applet.Appletviewer object
Java Unleashed
Page 396
by the Java interpreter—aren’t you glad now that you can see the English version because you used the -g option for javac?). The > prompt on the fourth line indicates that there is currently no default thread that we are watching—more on this later. To understand the bug in AddNumbers, we will start the applet running in the debugger asfollows: > run run sun.applet.AppletViewer MA.html running ... main[1] The debugger should open the applet’s frame and start executing it. Because the debugger and the applet are on different threads of execution, you can interact with the debugger and the applet at the same time. The preceding main[1] prompt indicates that the debugger is monitoring the applet thread (the main thread) and the [1] indicates that we currently are positioned at the topmost stack frame on the method call stack (we’ll explain what this means later). Our applet is supposed to take the number of the button pressed on the left and add it to the number of the button pressed on the right. Try this out—press some of the buttons and check the applet’s math. FIGURE 36.1. A view of the running applet. Hmm—unless we learned math differently than the rest of you, there seems to be something wrong with the computation of the applet. Maybe we found another Pentium processor flaw!
Basic Debugger Techniques To find out what is going on, let’s examine the ComputeSum method of the AddNumbers class. We would like to stop directly in this method without having to move slowly and tediously through the rest of the code.
Setting and Clearing Breakpoints Fortunately, JDB has a set of commands called breakpoints that do exactly that. We’ll use breakpoints to stop program execution in the ComputeSum method; but first, press the 5 and 3 buttons on the applet to make sure that you see the same things as we do when the program is stopped. After pressing 5 and 3, type the following in the debugger window: main[1] stop in AddNumbers.ComputeSum Breakpoint set in AddNumbers.ComputeSum main[1] As you probably can guess, this command tells JDB to stop when the method ComputeSum in the class AddNumbers is entered. This is convenient to do because the computation part of method that we are interested in is very close to the start of the method. If instead the statement was farther down in the method, it would be tedious to manually move down to the statement of interest every time we hit the breakpoint at the beginning of the method. In this case, we would want to use the stop at command in JDB. The stop at command works exactly like the stop in command in JDB, except that you specify the line number you want JDB to stop on instead of the method. For instance, look at the handleEvent method of AddNumbers class. If we wanted to stop at the if statement where the program was checking for a push of the number 2 button, we would have entered the fol-lowing: main[1] stop at AddNumbers:90 Breakpoint set in AddNumbers:90 main[1] However, don’t do this, because we want to examine the ComputeSum method and not handleEvent. We can verify this and see all of the breakpoints currently set by using the clear command as follows: AWT-Callback-Win32[1] clear Current breakpoints set: AddNumbers:37 AWT-Callback-Win32[1]
Java Unleashed
Page 397
As expected, there is only one breakpoint set. Note that it is specified as AddNumbers:37 instead of as AddNumbers:ComputeSum. JDB converts the command stop in AddNumbers.ComputeSum to stop at AddNumbers:37 in order to make its internal bookkeeping easier. Of course, the real use of the clear command is to clear breakpoints when they have outgrown their usefulness. Don’t do this, but if you needed to clear the breakpoint we just set, the following would be entered: AWT-Callback-Win32[1] clear AddNumbers.ComputeSum Breakpoint cleared at AddNumbers.ComputeSum AWT-Callback-Win32[1] Let’s get back to debugging our applet. When we left our applet, it was still running along and there was no prompt in the JDB window. Why hasn’t JDB stopped at ComputeSum? If you look at the applet code, you notice that the ComputeSum method only is called when you press a button in the applet. Press the 2 button to provide this ComputeSum method call: main[1] Breakpoint hit: AddNumbers.ComputeSum (AddNumbers: 37) AWT-Callback-Win32[1] As you can see in the above output, when you pressed the 2 button the debugger stopped at the ComputeSum method as we instructed it to. Note that we are now in a different thread (as shown by the change in prompts from main[1] to AWT-Callback-Win32[1]) because the AWT windowing manager thread calls the handleEvent method in the AddNumbers class when a button is pressed in the applet. We know we are stopped in ComputeSum method, but let’s get a better sense of our bearings and refresh our memory by looking at where this is in the source code. Fortunately, this line number information is stored in the class when you compile with the -g option and we can access this by using the this list command of JDB as follows: AWT-Callback-Win32[1] list 33 } 34 35 public void ComputeSum() { 36 37 => int Total = LeftNumber - RightNumber; 38 39 String ConvLeft = String.valueOf(LeftNumber); 40 String ConvRight = String.valueOf(RightNumber); 41 String ConvTotal = String.valueOf(Total); AWT-Callback-Win32[1] Note that as expected, we are stopped in ComputeSum on the first statement, and as luck would have it, right before the computation statement. The observant reader probably already can tell what is wrong, but just pretend that it’s a much more complicated computation and that you can’t, OK?
Examining Objects First, let’s check our operands for the computation to make sure they are correct. JDB provides three commands to display the contents of objects: locals, print, and dump. locals displays the current values of all of the objects defined in the local scope. print and dump are very similar and are used to display the contents of any object in any scope, including objects defined in the interface for the class. The main difference is that dump displays more information about complex objects (objects with inheritance or multiple data members) than print does. Because LeftNumber and RightNumber are class members, we’ll need to use print to display them, as follows:
Java Unleashed
Page 398
AWT-Callback-Win32[1] print LeftNumber this.LeftNumber = 5 AWT-Callback-Win32[1] print RightNumber this.RightNumber = 2 AWT-Callback-Win32[1] The operands seem to be exactly as we entered them on the applet. Let’s take a look at the local objects to get a feeling for where we are by using the locals command as follows: AWT-Callback-Win32[1] locals this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout, resizable,title=JDB Sample Java Program] Total is not in scope. ConvLeft is not in scope. ConvRight is not in scope. ConvTotal is not in scope. AWT-Callback-Win32[1] As expected, JDB is telling us that none of the local objects have been instantiated yet, so none of the objects are within the local scope yet. Let’s move the execution of the method along one statement so that we can see what the value of the computation is. To do this, we need to use the JDB step command as follows: AWT-Callback-Win32[1] step AWT-Callback-Win32[1] Breakpoint hit: AddNumbers.ComputeSum (AddNumbers:39) AWT-Callback-Win32[1] JDB moves the execution along one statement and stops. Doing this also triggers another breakpoint, because we are still in AddNumbers.ComputeSum. Let’s see the following to determine how the computation turned out: AWT-Callback-Win32[1] locals this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout, resizable,title=JDB Sample Java Program] Total = 3 ConvLeft is not in scope. ConvRight is not in scope. ConvTotal is not in scope. AWT-Callback-Win32[1] We see that Total was instantiated, the addition carried out, and the result put in Total. But wait, 5 + 2 doesn’t equal 3! Take a look at the following source code: AWT-Callback-Win32[1] list 35 public void ComputeSum() { 36 37 int Total = LeftNumber - RightNumber; 38 39 => String ConvLeft = String.valueOf(LeftNumber); 40 String ConvRight = String.valueOf(RightNumber); 41 String ConvTotal = String.valueOf(Total); 42 43 taResult.setText(ConvLeft + “ + “ + ConvRight + “ = “ + AWT-Callback-Win32[1] Oops—a subtraction sign was used instead of an addition sign! So much for finding another bug in the Pentium processor, but congratulations—you’ve found your first applet bug in JDB.
Java Unleashed
Page 399
Additional JDB Functions We’ve found our bug, but don’t quit out of AppletViewer yet. We’ll use it and the AddNumbers class to demonstrate a few more features of JDB that you might find useful in future debugging sessions.
Walking the Method Call Stack with JDB In the previous section, we used the locals JDB command to look at the objects in the current scope. Using the JDB command up, it also is possible to look at the local objects in previous stack frames (which consist of all of the methods that either called ComputeSum or called a method that called ComputeSum, and so on). For example, let’s look at the following to see the state of the handleEvent method right before it called the ComputeSum method: AWT-Callback-Win32[1] up AWT-Callback-Win32[2] locals this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout, resizable,title=JDB Sample Java Program] evt = java.awt.Event[id=1001,x=246,y=28, target=java.awt.Button[5,5,20x24,label=2],arg=2] AWT-Callback-Win32[2] As you can see, the handleEvent stack frame has two objects in its local frame, the pointer to this AddNumber instance and the Event object passed to handleEvent. It’s possible to use up as many times as your method call stack is deep. To undo the up function and return to a higher method call in the stack, use the JDB down command as follows: AWT-Callback-Win32[2] down AWT-Callback-Win32[1] locals this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout, resizable,title=JDB Sample Java Program] Total = 3 ConvLeft is not in scope. ConvRight is not in scope. ConvTotal is not in scope. AWT-Callback-Win32[1] As expected, we are back in the ComputeSum’s local stack frame.
Using JDB to Get More Information about Classes JDB also has two functions for getting more information about classes: methods and classes. methods enables you display all of the methods in a class. For example, examining the AddNumbers class with the methods command: AWT-Callback-Win32[1] methods void () void ComputeSum() void paint(Graphics) boolean handleEvent(Event) AWT-Callback-Win32[1] The classes function lists all of the classes that are currently loaded in memory. Here is partial output from the execution of classes on AddNumbers (the actual output listed more than 80 classes):
Java Unleashed
Page 400
AWT-Callback-Win32[1] classes ... ... 0x13a5f70:interface(sun.awt.UpdateClient) 0x13a6160:interface(java.awt.peer.MenuPeer) 0x13a67a0:interface(java.awt.peer.ButtonPeer) 0x13a6880:class(java.lang.ClassNotFoundException) 0x13a6ea8:class(sun.tools.debug.Field) 0x13a7098:class(sun.tools.debug.BreakpointSet) 0x13a7428:class(sun.tools.debug.Stackframe) 0x13a7478:class(sun.tools.debug.LocalVariable) AWT-Callback-Win32[1]
Monitoring Memory Usage and Controlling finalize For some large applets or applications, the amount of free memory might become a concern. JDB’s memory command enable you to monitor the amount of used and free memory during your debugging session, as follows: AWT-Callback-Win32[1] memory Free: 2554472, total: 3145720 AWT-Callback-Win32[1] JDB also lets you explicitly demand that the finalize method be run on all freed objects through the gc (garbage collection) command. This is useful for proving that your applet or application correctly handles deleted objects, which can be difficult to prove normally with small applets and applications because the finalize methods are normally called only when the applet or application has run out of free memory.
Controlling Threads of Execution As you know from Chapter 15, “Threads and Multithreading,” Java applets have multiple threads of execution. Using JDB and the threads command, we can view these threads asfollows: AWT-Callback-Win32[1] threads Group sun.applet.AppletViewer.main: 1. (java.lang.Thread)0x13a3a00 AWT-Win32 2. (java.lang.Thread)0x13a2a58 AWT-Callback-Win32 3. (sun.awt.ScreenUpdater)0x13a2d98 Screen Updater Group group applet-StartApplet.class: 4. (java.lang.Thread)0x13a28f0 class running AWT-Callback-Win32[1]
running running running
As you can see from the output, there are four threads of simultaneous applet execution. Two correspond to the AWT window management system (threads 1 and 2), one for updating the screen (thread 3), and one for the actual applet itself (thread 4). JDB provides two commands for controlling the execution of threads: suspend and resume. Suspending a thread isn’t very worthwhile in our simple example, but in multithreaded applications it can be very worthwhile—you can suspend all but one thread and focus on that thread. But let’s try suspend and resume on our applet to get a feel for their use. To suspend the AWT-Win32 thread, you should note its ID from the threads list and then use this as the argument to suspend, as follows:
Java Unleashed AWT-Callback-Win32[1] threads Group sun.applet.AppletViewer.main: 1. (java.lang.Thread)0x13a3a00 AWT-Win32 ... AWT-Callback-Win32[1] suspend 1 AWT-Callback-Win32[1] threads Group sun.applet.AppletViewer.main: 1. (java.lang.Thread)0x13a3a00 AWT-Win32 2. (java.lang.Thread)0x13a2a58 AWT-Callback-Win32 3. (sun.awt.ScreenUpdater)0x13a2d98 Screen Updater Group group applet-StartApplet.class: 4. (java.lang.Thread)0x13a28f0 class AWT-Callback-Win32[1]
Page 401
running
suspended running running running
As expected, the AWT-Win32 thread is now suspended. Threads are resumed in a completely analogous manner as follows: AWT-Callback-Win32[1] resume 1 AWT-Callback-Win32[1] threads Group sun.applet.AppletViewer.main: 1. (java.lang.Thread)0x13a3a00 AWT-Win32 2. (java.lang.Thread)0x13a2a58 AWT-Callback-Win32 3. (sun.awt.ScreenUpdater)0x13a2d98 Screen Updater Group group applet-StartApplet.class: 4. (java.lang.Thread)0x13a28f0 class AWT-Callback-Win32[1]
running running running running
Using use to Point the Way to Your Java Source Code In order to execute the list command, JDB takes the line number and grabs the required lines of Java from the source file. To find that source file, JDB reads your CLASSPATH environmental variable and searches all of the paths contained in it. If that path doesn’t contain your source file, JDB will be unable to display the source for your program. This wasn’t a problem for us because our search path contained the current directory, but if you set up your applet or application and the source is located in a directory outside of the search path, you’ll need to use the use command to add to your path. The use command without any arguments displays the current search path as follows: AWT-Callback-Win32[1] use \java\classes;.;C:\JAVA\BIN\..\classes; AWT-Callback-Win32[1] Appending a directory to the search path is unfortunately slightly tedious. You have to retype the entire current path and add the new path. So to add the path \myclasses to the preceding path, we would do the following: AWT-Callback-Win32[1] use \java\classes;.;C:\JAVA\BIN\..\classes;\myclasses AWT-Callback-Win32[1]
Getting More Information About Your Objects with dump In our debugging section we had an example of how to display an object’s value using the print command. In this section, we’ll look at JDB’s dump command, which is a more useful display command for objects containing multiple data members. The AddNumbers class is a good example (note that this in this case refers to the instantiation of AddNumbers for our applet), as follows:
Java Unleashed
Page 402
AWT-Callback-Win32[1] dump this this = (AddNumbers)0x13a3000 { ComponentPeer peer = (sun.awt.win32.MFramePeer)0x13a31b0 Container parent = null int x = 0 int y = 0 int width = 300 int height = 200 Color foreground = (java.awt.Color)0x13a2bb0 Color background = (java.awt.Color)0x13a2b98 Font font = (java.awt.Font)0x13a31d0 boolean visible = true boolean enabled = true boolean valid = true int ncomponents = 3 AWT-Callback-Win32[1] _ Contrast this with the output from print: AWT-Callback-Win32[1] print this this = AddNumbers[0,0,300x200,layout=java.awt.BorderLayout, resizable,title=JDB Sample Java Program] AWT-Callback-Win32[1] As you can see, the dump command displays the data members for the class, while print displays only the key attributes for the class.
Handling Exceptions with catch and ignore JDB has two functions for dealing with exceptions: catch and ignore. catch, similar to a breakpoint, enables you to trap exceptions and stop the debugger. This is useful when debugging because it becomes much easier to diagnose an exception when you know the conditions under which it occurred. To catch an exception, simply type class and the name of the exception class. In order to trap any exception (the Exception exception base class), the following is done: AWT-Callback-Win32[1] catch Exception AWT-Callback-Win32[1] ignore does exactly the opposite of catch. It squelches the specified class of exceptions raised by an applet or application. The use of ignore is completely analogous to catch, as shown by the following: AWT-Callback-Win32[1] ignore ArrayOutOfBoundsException AWT-Callback-Win32[1]
Continuing Program Execution with cont You may be wondering how to restart execution once you reach a breakpoint and execution has stopped. The cont command does just that: AWT-Callback-Win32[1] cont As you can see, the program has resumed execution and the JDB prompt will not return until a breakpoint or exception is reached.
Leaving JDB Using the exit Command Although it may be obvious to you already, there is one final command that comes in handy once in any debugging session. The exit command lets you out of the debugger and backinto DOS.
Java Unleashed
Page 403
Summary In this chapter, you have learned through hands-on experience about Sun’s Java debugger, JDB. Debugging is a learned skill; don’t be discouraged if it takes you a long time to debug your first applet or application. As you gain experience doing it, you’ll start to recognize the effects of different classes of bugs and be able to solve each bug in shorter amounts of time. Patience is definitely a virtue in software debugging.
Java Unleashed
Page 404
Chapter 37 Java documentation The bane of any software developer’s existence is embodied by a seemingly innocuous word: documentation. The old coder’s war cry, “If it was hard to write, it should be hard to read and use,” has no doubt been heard by some readers. Part of this general malaise can be attributed to the sometimes disjointed methods by which software documentation is created—an awkward cycle of creating code, commenting, extracting comments (if any in the first place), formatting in perhaps a word-processing package, preparing, and finally distributing it internally. However, on some operating systems such as UNIX, editors exist that can be taught to perform such tasks (for example, the ubiquitous Emacs). While useful, such an approach always is parochial. There are no defined standards for commenting C++, C, or even Smalltalk code in a universally understood manner. Code documentation generally remains a weak point of the overall development process, because any code base that requires no maintenance probably is not useful anymore, and any maintenance effort is hampered by poor, incomplete, or missing documentation. The developers of Java, however, decided to address such issues. It is possible with Java to add code commentary and produce documentation simultaneously and painlessly. Additionally, documentation can be released with Java products, eliminating the time lag between software and its accompanying explanatory text. The Java API documentation is itself generated from the Java class library source code and is copiously hypertext-linked, indexed, and presented attractively. With Java, it might even be possible, in some small way, to actually enjoy this process! We shall explore some of the rationale behind good documentation, the Java tools available for documentation, and metrics that can be used to measure what is finally produced in this chapter.
Documentation: Why Bother? As mentioned, documentation still is a relatively thorny and unpopular issue. In an attempt to soothe any qualms you might have, we are going to lay out some reasons why you should document your code, and document it well.
Five Reasons to Document There are more than five reasons to document, but here is a selection of some of the more compelling ones:
Think of the next person. phrase to consider: staff turnover. You may already have had this happen to you, but imagine the frustration of encountering a large body of code that has no comments. What will be the first task you have to perform on code like this? No doubt it will be to find an obscure bug that has manifested itself after the last developer left. If programmers could be confident that any piece of software they create never will require adjustment, the documentation issue is unimportant. Unfortunately, it would take more years than man has been on the Earth to prove mathematically that a piece of software is correct, so documentation remains important. Your code can be an educational tool. value of a well-designed, well-constructed, and well-documented piece of software cannot be underestimated as an educational aid. However, given the complexity of some of the tasks programmers are called on to perform, design and implementation decisions that are made might appear obscure without commentary. Good documentation supports leveraging of intellectual effort. No language is self-documenting. phrase, “the language is self-documenting,” used to be heard to justify the lack of code comments and explanatory text. If only this were true, but we know differently. Some features of even Java could need to be highlighted for less-experienced developers, including perhaps some in your own organization. An example of this highlighting might be to state in a comment that the implementation of an abstract
Java Unleashed
Page 405
function is the responsibility of a subclass (that is, a derived class). This fact might seem obvious after a few days, weeks, or months with Java, but it can mean an awful lot to someone just getting started. You can comply with Java coding standards. class, method, parameter, and variable comments might just form part of a coding standard that you follow, or might wish to follow. This is not to say that draconian measures are called for—just some standards that fall into the general area of “making your life easier.” Some suggested standards are described later on. You can automate its production. Java, by utilizing the javadoc utility it is possible to create slick HTML documentation with hypertext links and inline images by adhering to the defined javadoc markup. The markup is the collection of tokens that partially represent the Java API structure. These tokens are optional text tags that allow this generation to be performed. One supposes that it would be possible to integrate such documentation into the language itself. Java is still young—there is time. Guidelines for Successful Java Documentation Because you made it this far, we hope that you might also be interested in some guidelines that have proven useful empirically over the course of a number of mostly object-oriented projects. These guidelines, however, are for the most part paradigm- and languageindependent. They are offered to augment the basic Java facilities for documentation.
Copyright statement you are publishing your code on your web server, a public site such as Gamelan, or just allowing people to access it using anonymous FTP, be sure to include a copyright statement. Copyright statements should include items such as the conditions to impose on users of your software and what kind of warranty you do or do not imply. Pro forma text might be, “ hereby release the software into the public domain, and assume or assert no ownership or copyright over said software, permit free and unhindered source code modification and distribution, completely releasing all intellectual and commercial rights,” and so on. A more conservative approach would be to allow it to be distributed and reused freely as long as the code derived from your code carries an acknowledgment. This is certainly a more prevalent approach and one hopes that, by and large, it is honored. Modification history you are using some form of version control system, you might already have this in place and can easily integrate it with your existing code base. However, if you are not using a tool, it might be germane to include a modification history, in chronological order, for maintenance purposes. Such a history should probably mention the author, date, and a brief but complete description of the changes, additions, and deletions. Because we primarily are interested in javadoc, you also can include some HTML markup tags if desired to spice up the details. Remember not to include items such as line rulers or anchors in your comments, though. javadoc markup covered before, because you can simplify the code, comment, and documentation phase with an automatic generator, you should! Java markup is relatively simple and self-consistent, so it presents no barrier to implementation. When writing comments, all you have to do is add a few additional characters and you are there. javadoc itself manages the more complex tasks of rendering a textual representation of the inheritance trees of your classes, the ordering of private, protected, and public members, indexes for constructors and member functions, and many others. Documentation this easy cannot be ignored!
Java Unleashed
Page 406
Looking ahead you are not using a version control tool for your source management, you ought to consider using one of the freely available products such as SCCS (Source Code Control System), RCS (Revision Control System), or CVCS (Concurrent Version Control System). To retrieve these, you can start your search on http://www.yahoo.com and go from there (search for RCS or GNU, perhaps). The details of such packages are beyond the scope of this chapter, but by including their markup as well as javadoc’s, you can integrate the two. For example, RCS has keywords such as $Id$ that it will convert to a date, version, time, and so on when you update your source to the RCS repository (a holding pen for master copies of source code and history). If you combine this with the javadoc @version tag covered later, you can have your version control tool fill in the version field for you as a by-product of good source management. Coding standards could devote many pages to a consistent and semantically rich coding standards scheme, but will content ourselves with an aspect of coding practice that can increase your code comprehension rate and that of your colleagues: variable naming conventions. These include Hungarian notation, no notation, and various other schemes. The table below shows a simple system that is straightforward, concise, and quick to implement. Table 37.1. Sample variable naming scheme for Java.*
*Variable Prefix I l f d c s t r x b
*Meaning Integer variable Long integer Floating-point variable Double variable Character String or string buffer Thread Reference to variable (all variables in Java) An exception Boolean
*Example iNumberOfEvents lNumberOfEvents fNumberOfStars dNumberOfNebulae cSmallCharacter sLabel tAnimationThread rMemberFunction xIoException bMorePages
The javadoc Utility The thrust of this chapter is to encourage the use of the javadoc utility as a useful aid during software development. This section covers the use of HTML as a help system, navigating the generated documentation such as the Java API documents, the tags used to mark up code, and the command-line usage of javadoc itself.
HTML as a Help System Although it seems the obvious choice as a supporting help system for a web programming language, HTML actually offers certain advantages as an API help system. Some are obvious, such as general formatting, graphics inclusion, hypertext linking, and the ability to link to remote sites through the general web structure. Other less obvious advantages include portability, symmetry with one of the intentions of Java (architectural neutrality), and the ability to recite the whole document structure without comprising the links therein (in the case of javadoc-generated documentation). The advantage of portability is sort of obvious, but you can see its value when trying to use an RTF or Windows help file under Solaris.
Java Unleashed
Page 407
Navigating the javadoc-Generated Documentation The HTML documentation that composes the Java API was generated using the javadoc utility with a few exceptions edited by hand that will be discussed later. A discussion of the underlying structure of these documents—a separately obtainable part of the Java Development Kit—and their navigation covers both the online documentation and your own documentation in one fell swoop. In the text that follows, represents the base directory into which you uncompressed the API documentation. After a little while, you may begin to wonder why there is no applet content in the generated pages—as it would not have been difficult to include it.
API User’s Guide Entry Screen Figure 37.1 shows the first screen that you will see when perusing the Java API User’s Guide.
FIGURE 37.1.
The opening screen for the API User's Guide. Figure 37.1 is the file /api/API_users_guide.html, the HTML document that is the entryway to the user’s guide. Click the top hypertext link, Java API, to progress to the next page, as shown in Figure 37.2. FIGURE 37.2. The package index document, showing the applet packages and others. This file, located in /api/Packages.html, has been manually edited post production. Each package as listed corresponds to those present in the java.* and sun.tools.debug packages. Each of these packages is a hypertext link to the classes, interfaces, exceptions, and errors of that package. So, by clicking the java.awt link, you would see Figure 37.3. FIGURE 37.3. The java.awt package API document. Figure 37.3, /api/Package-java.awt.html), contains a number of interesting navigational features. As you can see, the document is partitioned into a number of indexes, asfollows:
Interface: All links listed here will jump to interfaces defined in this package. Class: Hypertext jumps to the individual classes are shown here. Exception: The very important exceptions category, where exceptions thrown by classes in this package are detailed. Error: A special category of throwable objects. At the top of the document, you will see the following links:
All Packages: Returns you to the document shown in Figure 37.1, displaying all of the packages available for perusal. Class Hierarchy: Invokes the display of the document /api/tree.html, which is a hypertext list of the Java class hierarchy, including the package that you are examining. You will see something similar to Figure 37.4. FIGURE 37.4. Class hierarchy document tree.html.
Index: Displays the file /api/AllNames.html, which is an alphabetical index allowing searches using the initial character of a variable, method, interface, exception, class, or package. Entries in the list are hypertext-linked to the corresponding explanatory documents. Figure 37.5 shows the top of the index for the Java API documentation. FIGURE 37.5. Index document AllNames.html.
Java Unleashed
Page 408
Class Document Each class in a package will have its documentation present in a file called .html, where denotes a full package name specification. Each file has a common structure consisting of the following:
Class name: Fully qualified class name. Class hierarchy: Preformatted inheritance tree for the class. Each superclass is a hypertext link to the corresponding details. Class Details: A textual description of the current class, including its immediate superclass and what interfaces it implements, if any. Class comment: The descriptive class comment. Indexes: The variable, constructor, and method indexes. A one sentence description follows the index entry. For variable indices, types are excluded. For methods, return type is excluded. For both methods and constructors, parameter types only are listed. Each abridged entry in the index is a hypertext link to the expanded description. Descriptions: Each particular entity (variable, constructor, method) has a different possible documentation display. For more details on this, see Table 37.2. The method descriptions, apart from any comments or tags that have been defined, also might include a hypertext entry to a method that is being overridden by this method. This could be of assistance in a number of situations, including the determination of the rationale for doing so and checking design decisions. To also aid in navigation, the top of the document has hypertext links that enable you to jump to the following:
the All Packages document Class Hierarchy the contents of the package of this class the previous and next class, interface, exception, or error in the package, determined alphabetically within each type of package component the Index document All of these document types have been covered in previous sections. Figure 37.6 shows the top of the java.awt.Panel class page. FIGURE 37.6. The AWT Panel class help page, java.awt.Panel.html.
javadoc Markup We have mentioned already that documentation generation is achieved by executing the javadoc utility. However, we have only briefly touched upon the fact that a tag-marking scheme is required for source code files to be suitable candidates for parsing. This section covers all of the details regarding this text markup, and reiterates some of the ways in which you can enhance the basic javadoc scheme.*
*NOTE Even though javadoc will generate linked and attractive documentation, it is not a substitute for well-written comments. The command-line usage of javadoc is covered later.
What Documentation javadoc Will Generate Documentation can be generated for the following:
Java Unleashed
Page 409
Packages Classes Interfaces Exceptions Methods Variables Note that only text will be generated for public and protected aspects of objects. This is in accordance with the basic object-oriented principle of encapsulation—a human API browser should not be aware necessarily of the internals of class, for example. Do not take this as a method to avoid documenting implementations, though!
Embedding HTML Sequences Virtually any of the standard HTML tags can be embedded inside source text for eventual inclusion into the generated documents. Common sense dictates (as does Sun Microsystems) that formatting tags, anchors, lists, horizontal rulers, and other tags might interfere with the documents that are produced by javadoc. It is a reasonably good way of adding some extra information in a visually striking way, though—such things are best determined empirically.
Available Tags The following table defines each currently available tag, its scope, and associated semantics. Note that they are only significant when enclosed by the special comment-opening characters /** and closing characters */ (like a fancy C comment, really). The semantics are expanded later along with some code examples. (Note: The letters fq in the following table are an acronym for fully qualified.) Table 37.2. Defined javadoc markup tags.*
*Tag/string @see classname
*Scope Class
@see fq-classname @see fq-classname#method-name
Class Class
@version version text @author author @see classname
Class Class Variable
@see fq-classname
Variable
@see fq-classname#method-name
Variable
@param parameter-name description
Method
@return description
Method
@exception fq-class-name description
Method
*Semantics See also” hypertext link to the class specified Hyperlinked “see also” entry Hyperlinked “see also” entry to the method in the given class “Version” entry Creates an “Author” entry “See also” hypertext link to the named class Hyperlinked “see also” entry to the given class Hyperlinked “see also” entry to the method in the named class Parameter specification for the “Parameters” section “Returns” section, specifying the return value “Throws” entry, which contains the name of the exception that might be thrown by the method
There are some conditions that apply to the use of tags, and they are as follows:
Class names: javadoc will prepend the name of the current package to a bare class name if the source itself is resident in a package. There capability also exists to fully qualify class names if it’s necessary to reference a class in a package other than the current one.
Java Unleashed
Page 410
Tag grouping: Repeated tags must be sequentially ordered. Keep all like tags together or javadoc will become confused. Format: All tags are prepended with the @ character and are placed at the beginning of a comment line. Leading white space and asterisks are ignored when the tag is searched for. The examples later show this clearly. Length: All comments can be spread over a number of lines. In fact, a one-line comment (certainly for class scope) is too short. Have you anything else to say to enlighten your reader? Class Comment Example A class comment is arguably the most important you can make! Be sure to include a general description of the intent, general behavior, version, authors—perhaps including some of the look ahead tokens for version control, date, and any reference to related classes. Listing 37.1 shows a rather short class comment.
Listing 37.1. Example class comment. //================================================================================= /** * This class is part of a subsystem to parse a configurable grammar token stream * using various recovery schemes, as detailed in the specification, under section * 2.3, Finite State Machine recovery. An internally-mounted Web page * details this. *
* @see org.parser.fsm for the finite state machine details * @see java.io.InputStrean for the protocol our token stream adheres to * @author T Beveridge * @Author Natasha Brown * @version 12/10/95, 1.00 */ //================================================================================= class StackedTokenStream extends org.tokenStream implements Runnable {
Note the placement of comment lines and text. There also are some extra features here, in that we have included our own hypertext reference to a specification document on our internal web server. The use of the
tag also inserts an empty line for neatness. Multiple tags have been used to reflect the two authors of the class, and our version details will show as a date followed by our own version number.
Method Comment Example Remember that only public or protected methods will have documentation created. It is, of course, your decision whether to include comments for private methods, but for all except the most trivial (such as accessors), it is good software engineering practice to include comments.*
*TIP javadoc takes the first sentence of a method comment and uses it for the index entry. Therefore, it is a good idea to make this sentence descriptive. For a constructor, rather than having, “Constructor. Taking X arguments…”, use, “Constructor taking X arguments, …”. Describing parameters is helpful in Java, because there is no syntactic means of ascribing the C++ concept of ascribing state constancy to a parameter; this even though some parameters might never be modified when used by the method. The description of exceptions also is excellent practice because they will either have to be caught by your client’s code, caught and re-thrown, or passed on. Listing 37.2 illustrates a helpful method comment.
Listing 37.2. Example method comment.
Java Unleashed
Page 411
//================================================================================= /** * This method determines if the Finite State Machine is in one of the 2 * (desirable) states given. The StringBuffer parameter is the only one of the 3 * that may change during the execution of this call. Note that this method may * become private, so it may be a good idea to avoid it !
* Has not been modified for some considerable time so could be considered stable * (or obsolete).
@param rStateOne An instance of FsmState. The order is * irrelevant @param rStateTwo An instance of FsmState. The order is irrelevant * @param rStrBuffer volatile This wil be updated during this call with the * state actually encountered if this function returns true * @return true if one of the two states has been encountered ; false (!) otherwise * @exception java.io.IOException Thrown when the Fsm delegator attempts to fetch a * token and fails. Unusual * @exception org.exceptions.FsmDead Thrown when the Finite State Machine is in a * GC’ablestate. */ //================================================================================= protected boolean duplexState(FsmState rStateOne, FsmState rStateTwo, StringBuffer ÂrStrBuffer) { if (bTerminated) throw new org.exceptions.FsmDead(“FSM: Dead state entered”);
Again, note the inclusion of
tags to introduce blank lines and the use of a boldface “volatile” comment on the rStrBuffer description to indicate its potential value change.
Variable Comment Example Because only the comments of protected and public variables are candidates for documentation inclusion, you might not see many of these. They seem to appear mainly for FINAL variables, which are really just a Java method for introducing constant values. Remember that variable comments for javadoc can only contain @see directives. Listing 37.3 shows a sample variable comment.
Listing 37.3. Sample variable comment. //================================================================================= /** * This boolean variable holds our live/dead status. It is held to be true through * out the useful life of the Finite State Machine, being rendered false only when * the associated token stream is in error or throws an exception. * @see org.io.streams.StackedTokenStream */ //================================================================================= protected boolean bTerminated;
Generating Documentation Now we cut to the chase. We will discuss how to generate your documentation, where it can reside, platform differences, and errors that can occur.
Where do documents go on creation? go wherever you happen to be, actually. What this means is best explained with an example. If you are in directory X, and you generate documentation for your package org.utilities, javadoc will dump its output in directory X. This is not particularly unfriendly unless you have manually edited
Java Unleashed
Page 412
Packages.html—if you have, the javadoc process will overwrite your changes! The moral of this example is that javadoc is essentially a batch utility if you don’t want to change directories too often. There also is a command-line option you can supply to alter this behavior. Merging your own packages with the Java API documentation is entirely possible though not necessarily advisable. All you really have to do is edit the Packages.html file and insert your own hypertext links to your package documents. Note that merging your own documents this way will not include them in either the index or class hierarchy. Platform differences depends very much upon your own system setup. For example, suppose you are using Windows NT javadoc to generate documents and these documents are targeted to be stored on a network drive whose native type is UNIX. This will generate documents that can be accessed under NT but not under a UNIX-based browser. Why is this? Case sensitivity. Generating from NT, javadoc seems to ignore case and the result is hypertext-linked documents that show up as errors under UNIX. This is a minor point, but if you are batch-creating documentation for a large package or number of packages, it might save you some time. In this sort of environment, maybe you should just go with UNIX-based generation! Other errors out for the command-line syntax. You must specify a full package name, and the source must be available and readable. Any other course of action equals frustration. Make sure that you can write in the directory you are currently in or are directing the javadoc HTML documents to. javadoc: Command-Line Usage javadoc is just about the simplest utility in the JDK. The syntax is as follows: javaDoc [options] {Package Name or File Name}......... Available options are as follows: -d This instructs javadoc to redirect output to . The default is the current directory. -classpath This allows the specification of a classpath. It is the same as javac and overrides the CLASSPATH environment variable. -verbose This instructs javadoc to print a list of files loaded during execution. From JDK Beta 2, this might include files loaded from a ZIP file.
Measuring Your Documentation Quality javadoc certainly is a tremendous help when it comes to automating the sometimes arduous task of creating software documentation. One aspect it does not cover is that of quality. This section attempts to provide some heuristics that can be applied to determine if the documents you are producing are worthy of a readership. Please note that a subset of this information also is included in the book Object Oriented Software Metrics by Mark Lorenz and Jeff Kidd, which is a recommended read for those interested in object-oriented metrics. The list that follows presents metrics that you might consider adopting:
Java Unleashed
Page 413
Size/existence of class comment the object was important enough to warrant a class, it certainly has a purpose, provides some service, collaborates with others, or acts autonomously. Otherwise you probably would not have bothered, so it therefore deserves a comment—and not just one that mentions the date and version, either! As an API reader, you should be able to read the overall class comment and either discard the class from consideration or investigate further. Without a class comment of some depth, such a task is rendered more difficult. At least 50 words would be required for a small class, such as a support class. Size/existence of method comments for similar reasons to the above, a method should be commented if it is regarded as central to the public or protected behavior of a class. Implementation methods are arguably more important to comment, but remember—they won’t be visible to your readers. Around 20 to 25 words is a good baseline for method comments. Number of methods/classes/interfaces/variables with comments public or protected methods, classes and others, a coverage of less than 80 percent should be a cause for concern. Unless you are creating a popular class such as a string, for example, its use might not necessarily jump out at your audience. Comment it and they might just want to use it. Currently, there is no way in Java to automate this process of documentation-quality measurement. However, if you avail yourself of the Beta 2 source code and adhere to the source license agreement, it would be possible to modify the javadoc source code to incorporate such abilities. This will at least give you timely statistics on quality automatically if you require them. The javadoc source code itself is in the package java.tools.javadoc.
Summary This summary presents some possible improvements in javadoc—though not exhaustively—and a final endorsement of its use.
Potential Improvements As with all tools, there always are improvements that could be made. Some of the more interesting ones include the following:
Package comments is no facility currently for package comments. Incremental updates allow the documentation for just a particular class to be generated in the target directory without rewriting some of the reference files. Tag granularity example, it is possible only to provide authors on a class basis. It sometimes is desirable to note authors of methods, too, in a consistent fashion. Object Oriented Analysis/Object Oriented Design concepts tags would be reasonably useful to provide some way of noting design decisions in a way that is separate from the rest of the documentation. An example might be that a particular class is tightly coupled with certain other classes, or that it might be only ever part of an aggregation. Metrics is no provision for integration or production of metric information regarding quality.
Java Unleashed
Page 414
Liven up the documents one of the objectives of Java is to make Web pages come alive, some applets that augment the utility of the API documents would be good! This chapter has shown that javadoc is a simple and reasonably effective tool for generating API documentation quickly and effectively. Used properly, it can create useful documents to aid the users of packages. It is recommended.
Java Unleashed
Page 415
Chapter 38 Native methods and libraries In this chapter we’ll discuss all the reasons you might (or might not) want to write native methods in Java, all of Java’s built-in optimizations, and the tricks you can use to make your programs faster. You’ll also learn the procedure for creating, making headers and stubs for, and linking native methods into a dynamically loadable library. Let’s begin, however, with the reasons that you might want to implement native methods in the first place. There are only two reasons that you might need to declare some of your methods native—that is, implemented by a language other than Java. The first, and by far the best reason to do so, is because you need to utilize a special capability of your computer or operating system that the Java class library does not already provide for you. Such capabilities include interfacing to new peripheral devices or plug-in cards, accessing a different type of networking, or using a unique and valuable feature of your particular operating system. Two more concrete examples are acquiring real-time audio input from a microphone or using 3D “accelerator” hardware in a 3D library. Neither of these is provided to you by the current Java environment, so you must implement them outside Java, in some other language (currently C or any language that can link with C). The second, and often illusory, reason to implement native methods is speed—illusory, because you rarely need the raw speeds gained by this approach. It’s even more rare to not be able to gain that speed-up in other ways (as you’ll see later in the chapter). Using native methods in this case takes advantage of the fact that, at present, the Java release does not perform as well as, for example, an optimized C program on many tasks. For those tasks, you can write the “needs to be fast” part (critical, inner loops, for example) in C, and still use a larger Java shell of classes to hide this “trick” from your users. In fact, the Java class library uses this approach for certain critical system classes to raise the overall level of efficiency in the system. As a user of the Java environment, you don’t even know (or see) any side effects of this (except, perhaps, a few classes or methods that are final that might not be otherwise).
Disadvantages of native Methods Once you decide you’d like to, or must, use native methods in your program, this choice costs you dearly. Although you gain the advantages mentioned earlier, you lose the portability of your Java code. Before, you had a program (or applet) that could travel to any Java environment in the world, now and forever. Any new architectures created—or new operating systems written—were irrelevant to your code. All it required was that the (tiny) Java Virtual Machine (or a browser that had one inside it) be available, and it could run anywhere, anytime—now and in the future. Now, however, you’ve created a library of native code that must be linked with your program to make it work properly. The first thing you lose is the ability to “travel” as an applet; you simply can’t be one! No Java-capable browser currently in existence allows native code to be loaded with an applet, for security reasons (and these are good reasons). The Java team has struggled to place as much as possible into the java packages because they are the only environment you can count on as an applet. (The sun packages, shipped primarily for use with stand-alone Java programs, are not always available to applets.)*
*NOTE Actually, any classes that anyone writes without native code should be able to be loaded with an applet, as long as they depend only on the java packages. Unfortunately, many of the sun packages contain classes that must use native code to provide crucial, missing functionality from the java packages. All these missing pieces, and some additional multimedia and sound capabilities, will be added to the java packages in the future. (This has been informally promised in discussions I’ve had with the Java team.) Losing the ability to travel anywhere across the Net, into any browser written now or in the future, is bad enough. What’s worse, now that you can’t be an applet, you have further limited yourself to only those machines that have had the Java Virtual Machine ported to their operating system. (Applets automatically benefit from the wide number of machines and operating systems that any Java-capable browser is ported to, but now you do not.) Even worse, you have assumed something about that machine and operating system by the implementation of your native methods. This often means that you have to write different source code for some (or all) of the machines and operating systems on which you want to be able to run. You’re already forced, by using native methods, to produce a separate binary library for every machine and
Java Unleashed
Page 416
operating system pair in the world (or at least, wherever you plan to run), and you must continue to do so forever. If changing the source is also necessary, you can see that this is not a pleasant situation for you and your Java program.
The Illusion of Required Efficiency If, even after the previous discussion, you must use native methods anyway, there’s help for you later in this chapter—but what if you’re still thinking you need to use them for efficiency reasons? You are in a grand tradition of programmers throughout the (relatively few) ages of computing. It is exciting, and intellectually challenging, to program with constraints. If you believe efficiency is always required, it makes your job a little more interesting—you get to consider all sorts of baroque ways to accomplish tasks, because it is the efficient way to do it. I myself was caught up in this euphoria of creativity when I first began programming, but it is creativity misapplied. When you design your program, all that energy and creativity should be directed at the design of a tight, concise, minimal set of classes and methods that are maximally general, abstract, and reusable. (If you think that is easy, look around for a few years and see how bad most software is.) If you spend most of your programming time on thinking and rethinking these fundamental goals and how to achieve them, you are preparing for the future. A future where software is assembled as needed from small components swimming in a sea of network facilities, and anyone can write a component seen by millions (and reused in their programs) in minutes. If, instead, you spend your energy worrying about the speed your software will run right now on some computer, your work will be irrelevant after the 18 to 36 months it will take hardware to be fast enough to hide that minor inefficiency in your program. Am I saying that you should ignore efficiency altogether? Of course not! Some of the great algorithms of computer science deal with solving hard or “impossible” problems in reasonable amounts of time—and writing your programs carelessly can lead to remarkably slow results. Carelessness, however, can as easily lead to incorrect, fragile, or nonreusable results. If you correct all these latter problems first, the resulting software will be clean, will naturally reflect the structure of the problem you’re trying to solve, and thus will be amenable to “speeding up” later.*
*NOTE There are always cases where you must be fanatical about efficiency in many parts of a set of classes. The Java class library itself is such a case, as is anything that must run in real-time for some critical real-world application (such as flying a plane). Such applications are rare, however.When speaking of a new kind of programming that must soon emerge, Bill Joy (one of Sun’s founders) likes to invoke the four S’s of Java: small, simple, safe, and secure. The “feel” of the Java language itself encourages the pursuit of clarity and the reduction of complexity. The intense pursuit of efficiency, which increases complexity and reduces clarity, is antithetical to these goals. Once you build a solid foundation and debug your classes, and your program (or applet) works as you’d like it to, then it’s time to begin optimizing it. If it’s just a user interface applet, you may need to do nothing at all. The user is very slow compared to modern computers (and getting relatively slower every 18 months). The odds are that your applet is already fast enough—but suppose it isn’t.
Built-In Optimizations Your next job is to see whether your release supports turning on the “just-in-time” compiler, or using the java2c tool. The first of these is an experimental technology that, while a method’s bytecodes are running in the Java Virtual Machine, translates each bytecode into the native binary code equivalent for the local computer, and then keeps this native code around as a cache for the next time that method is run. This trick is completely transparent to the Java code you write. You need know nothing about whether or not it’s being done—your code can still “travel” anywhere, anytime. On any system with “just-in-time” technology in place, however, it runs a lot faster. Experience with experimental versions of this technology shows that, after paying a small cost the first time a method is run, this technique can reach the speed of compiled C code.*
*NOTE More details on this technique, and the java2c tool, are presented in the next chapter. As of the 1.0 release, neither of these tools are in the Java environment, but both are expected in a later release (perhaps 1.1). The java2c translator takes a whole .class file full of the bytecodes for a class and translates them (all at once) into a portable C source code version. This version can then be compiled by a traditional C compiler on your computer to produce a native-method-like cached library of fast code. This large cache of native code will be used whenever the class’s methods are called, but only on the local computer. Your original Java code can still travel as bytecodes and run on any other computer system. If the virtual machine
Java Unleashed
Page 417
automatically takes these steps whenever it makes sense for a given class, this can be as transparent as the just-in-time technology. Experience with an experimental version of this tool shows that fully optimized C performance is achievable. (This is the best anyone can hope to do!) So you see, even without taking any further steps to optimize your program, you may discover that for your release of Java (or for releases elsewhere or coming in the near future), your code is already fast enough. If it is not, remember that the world craves speed. Java will only get faster, and the tools will only get better. Your code is the only permanent thing in this new world—it should be the best you can make it, with no compromises.
Simple Optimization Tricks Suppose that these technologies aren’t available or don’t optimize your program far enough for your taste. You can profile your applet or program as it runs, to see in which methods it spends the most time. Once you know this crucial information, you can begin to make targeted changes to your classes.*
*TIP Use java -prof ... to produce this profile information. In an earlier release (and, presumably, some later release) the javaprof tool can “pretty-print” this information in a more readable format. (javaprof is not in the 1.0 release—but try the latest Java release’s documentation for details.) Before you begin making optimizations, you also may want to save a copy of your “clean” classes. As soon as computer speeds allow it (or a major rewrite necessitates it), you can revert to these classes, which embody the “best” implementation of your program. First, identify the crucial few methods that take most of the time (there are almost always just a few, and often just one, that take up the majority of your program’s time). If they contain loops, examine the inner loops to see whether they: call methods that can be made final, call a group of methods that can be collapsed into a single method, or create objects that can be reused rather than created anew each loop. If you notice that a long chain of, for example, four or more method calls is needed to reach a destination method’s code, and this execution path is in one of the critical sections of the program, you can “short-circuit” directly to that destination method in the topmost method. This may require adding a new instance variable to reference the object for that method call directly. This quite often violates layering or encapsulation constraints. This violation, and any added complexity, is the price you pay for efficiency. If, after all these tricks (and the numerous others you should try that have been collected over the years into various programming books), your Java code is still just too slow, you will have to use native methods after all.
Writing native Methods For whatever reasons, you’ve decided to add native methods to your program. You’ve already decided which methods need to be native, and in which classes, and you’re rarin’ to go. First, on the Java side, all you need to do is delete the method bodies (all the code between the brackets—{ and }—and the brackets themselves) of each method you picked and replace them with a single semicolon (;). Then add the modifier native to the method’s existing modifiers. Finally, add a static (class) initializer to each class that now contains native methods to load the native code library you’re about to build. (You can pick any name you like for this library—details follow.) You’re done! That’s all you need to do in Java to specify a native method. Subclasses of any class containing your new native methods can still override them, and these new Java methods are called for instances of the new subclasses (just as you’d expect). Unfortunately, what needs to be done in your native language environment is not so simple.*
*NOTE
The following discussion assumes that C and UNIX are your language and environment. This means that some of the steps may differ slightly on your actual system, but such differences will be outlined in the
Java Unleashed
Page 418
notes surrounding the native method documentation in your release (in the document called “Implementing Native Methods” in the alpha, but folded into the programmer’s tutorial in 1.0). The following discussion is purposely parallel to this documentation. The Example Class Imagine a version of the Java environment that does not provide file I/O. Any Java program needing to use the file system would first have to write native methods to get access to the operating system primitives needed to do file I/O. This example combines simplified versions of two actual Java library classes, java.io.File and java.io.RandomAccessFile, into a single new class, SimpleFile: public class SimpleFile { public static final char separatorChar = ‘>’; private protected String path; private protected int fd; public SimpleFile(String s) { path = s; } public String getFileName() { int index = path.lastIndexOf(separatorChar); return (index < 0) ? path : path.substring(index + 1); } public String getPath() { return path; } public native boolean open(); public native void close(); public native int read(byte[] buffer, int length); public native int write(byte[] buffer, int length); static { System.loadLibrary(“simple”); // runs when class first loaded } }
* *NOTE The unusual separatorChar (>) is used simply to demonstrate what an implementation might look like on some strange computer whose file system didn’t use any of the more common path separator conventions. Early Xerox computers used > as a separator, and several existing computer systems still use strange separators today, so this is not all that farfetched. SimpleFiles can be created and used in the usual way: SimpleFile f = new SimpleFile(“>some>path>and>fileName”); f.open(); f.read(...); f.write(...); f.close(); The first thing you notice about SimpleFile’s implementation is how unremarkable the first two-thirds of its Java code is! It looks just like any other class, with a class and an instance variable, a constructor, and two normal method implementations. Then there are four native method declarations. You’ll recognize these, from previous discussions, as being just a normal method declaration with the code block replaced by a semicolon and the modifier native added. These are the methods you have to implement in C code later. Finally, there is a somewhat mysterious code fragment at the very end of the class. You might recognize the general construct here as a static initializer. Any code between the brackets—{ and }—is executed exactly once, when the class is first loaded into the system. You take advantage of that fact to run something you want to run only once—the loading of the native code library you’ll create later
Java Unleashed
Page 419
in this chapter. This ties together the loading of the class itself with the loading of its native code. If either fails for some reason, the other fails as well, guaranteeing that no “half-set-up” version of the class can ever be created.
Generating Header and Stub Files In order to get your hands on Java objects and data types, and to be able to manipulate them in your C code, you need to include some special .h files. Most of these will be located in your release directory under the subdirectory called include. (In particular, look at native.h in that directory, and all the headers it points to, if you’re a glutton for detail punishment.) Some of the special forms you need must be tailored to fit your class’s methods precisely. That’s where the javah tool comes in.
Using javah To generate the headers you need for your native methods, first compile SimpleFile with javac, just as you normally would. This produces a file named SimpleFile.class. This file must be fed to the javah tool, which then generates the header file you need (SimpleFile.h).* TIP If the class handed to javah is inside a package, it prepends the package name to the header filename (and to the structure names it generates inside that file), after replacing all the dots (.) with underscores (_) in the package’s full name. If SimpleFile had been contained in a hypothetical package called acme.widgets.files, javah would have generated a header file named acme_widgets_files_SimpleFile.h, and the various names within it would have been renamed in a similar manner. When running javah, you should pass it only the class name itself, and not the full filename, which has .class on the end.
The Header File
Here’s the output of javah SimpleFile: /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class SimpleFile */ #ifndef _Included_SimpleFile #define _Included_SimpleFile struct Hjava_lang_String; typedef struct ClassSimpleFile { #define SimpleFile_separatorChar 62L struct Hjava_lang_String *path; long fd; } ClassSimpleFile; HandleTo(SimpleFile); extern /*boolean*/ long SimpleFile_open(struct HSimpleFile *); extern void SimpleFile_close(struct HSimpleFile *); extern long SimpleFile_read(struct HSimpleFile *,HArrayOfByte *,long); extern long SimpleFile_write(struct HSimpleFile *,HArrayOfByte *,long); #endif
* *NOTE HandleTo() is a “magic” macro that uses the structures created at runtime by the stubs you’ll generate later in this chapter. The members of the struct generated above are in a one-to-one correspondence with the variables of your class. In order to “massage” an instance of your class gently into the land of C, use the macro unhand() (as in “unhand that Object!”). For example, the this pseudo-variable in Java appears as a struct HSimpleFile * in the land of C, and to use any variables inside this instance (you), you must unhand() yourself first. You’ll see some examples of this in a later section in this chapter.
Java Unleashed
Page 420
Using javah -stubs To “run interference” between the Java world of Objects, arrays, and other high-level constructs and the lower-level world of C, you need stubs. (Stubs are pieces of “glue” code that automatically translate arguments and return values back and forth between the worlds of Java and C.) Stubs can be automatically generated by javah, just like the headers. There isn’t much you need to know about the stubs file, just that it has to be compiled and linked with the C code you write to allow it to interface with Java properly. A stubs file (SimpleFile.c) is created by running javah on your class by using the option -stubs.*
*NOTE One interesting side-effect of stub generation is the creation of method signatures, informally called method descriptions elsewhere. These signatures are quite useful—they can be passed to special C functions that allow you to call back into the Java world from C. You can use stub generation to learn what these signatures look like for different method arguments and return values, and then use that knowledge to call arbitrary Java methods from within your C code. (Brief descriptions of these special C functions, along with further details, appear later in this chapter.) The Stubs File
Here’s the result of running javah -stubs SimpleFile: /* DO NOT EDIT THIS FILE - it is machine generated */ #include <StubPreamble.h> /* Stubs for class SimpleFile */ /* SYMBOL: 93"SimpleFile/open()Z”, Java_SimpleFile_open_stub */ stack_item *Java_SimpleFile_open_stub(stack_item *_P_,struct execenv *_EE_) { extern long SimpleFile_open(void *); _P_[0].i = SimpleFile_open(_P_[0].p); return _P_ + 1; } /* SYMBOL: “SimpleFile/close()V”, Java_SimpleFile_close_stub */ stack_item *Java_SimpleFile_close_stub(stack_item *_P_,struct execenv *_EE_) { extern void SimpleFile_close(void *); (void) SimpleFile_close(_P_[0].p); return _P_; } /* SYMBOL: “SimpleFile/read([BI)I”, Java_SimpleFile_read_stub */ stack_item *Java_SimpleFile_read_stub(stack_item *_P_,struct execenv *_EE_) { extern long SimpleFile_read(void *,void *,long); _P_[0].i = SimpleFile_read(_P_[0].p,((_P_[1].p)),((_P_[2].i))); return _P_ + 1; } /* SYMBOL: “SimpleFile/write([BI)I”, Java_SimpleFile_write_stub */ stack_item *Java_SimpleFile_write_stub(stack_item *_P_,struct execenv *_EE_) { extern long SimpleFile_write(void *,void *,long); _P_[0].i = SimpleFile_write(_P_[0].p,((_P_[1].p)),((_P_[2].i))); return _P_ + 1; } Each comment line contains the method signature for one of the four native methods you’re implementing. You can use one of these signatures to call into Java and run, for example, a subclass’s overriding implementation of one of your native methods. More often, you’d learn and use a different signature to call some useful Java method from within C to get something done in the Java world. You do this by calling a special C function that is part of the Java run-time called execute_java_dynamic_method(). Its arguments include the target object of the method call and the method’s signature. The general form of a fully qualified method signature is any/package/name/ClassName/methodName(...)X. (You can see several in the stub’s output’s comments, where SimpleFile is the class name and there is no package name.) The X is a letter (or string) that represents the return type, and the ... contains a string that
Java Unleashed
Page 421
represents each of the argument’s types in turn. (Here are the letters —and strings—used, and the types they represent, in the example: [T is array of type T, B is byte, I is int, V is void, and Z is boolean.) The method close(), which takes no arguments and returns void, is represented by the string “SimpleFile/close()V” and its inverse, open(), that returns a boolean instead, is represented by “SimpleFile/open()Z“. Finally, read(), which takes an array of bytes and an int as its two arguments and returns an int, is “SimpleFile/read([BI)I“. (See the “Method Signatures” section in the next chapter for the full details.)
Creating SimpleFileNative.c Now you can, at last, write the C code for your Java native methods. The header file generated by javah, SimpleFile.h, gives you the prototypes of the four C functions you need to implement to make your native code complete. You then write some C code that provides the native facilities that your Java class needs (in this case, some low-level file I/O routines). Finally, you assemble all the C code into a new file, include a bunch of required (or useful) .h files, and name it SimpleFileNative.c. Here’s the result: #include “SimpleFile.h” /* for unhand(), among other things */ #include <sys/param.h> /* for MAXPATHLEN */ #include /* for O_RDWR and O_CREAT */ #define LOCAL_PATH_SEPARATOR ‘/’ /* UNIX */ static void fixSeparators(char *p) { for (; *p != ‘\0’; ++p) if (*p == SimpleFile_separatorChar) *p = LOCAL_PATH_SEPARATOR; } long SimpleFile_open(struct HSimpleFile *this) { int fd; char buffer[MAXPATHLEN]; javaString2CString(unhand(this)->path, buffer, sizeof(buffer)); fixSeparators(buffer); if ((fd = open(buffer, O_RDWR | O_CREAT, 0664)) < 0) /* UNIX open */ return(FALSE); /* or, SignalError() could “throw” an exception */ unhand(this)->fd = fd; /* save fd in the Java world */ return(TRUE); } void SimpleFile_close(struct HSimpleFile *this) { close(unhand(this)->fd); unhand(this)->fd = -1; } long SimpleFile_read(struct HSimpleFile *this, HArrayOfByte *buffer, Â long count) { char *data = unhand(buffer)->body; /* get array data */ int len = obj_length(buffer); /* get array length */ int numBytes = (len < count ? len : count); if ((numBytes = read(unhand(this)->fd, data, numBytes)) == 0) return(-1); return(numBytes); /* the number of bytes actually read */ } long SimpleFile_write(struct HSimpleFile *this, HArrayOfByte *buffer, Â long count) { char *data = unhand(buffer)->body; int len = obj_length(buffer); return(write(unhand(this)->fd, data, (len < count ? len : count))); } Once you finish writing your .c file, compile it by using your local C compiler (usually called cc or gcc). On some systems, you may need to specify special compilation flags that mean “make it relocatable and dynamically linkable.”*
Java Unleashed
Page 422 *NOTE
If you don’t have a C compiler on your computer, you can always buy one. You also could get a copy of the GNU C compiler (gcc), one of the best C compilers in the world, which runs on almost every machine and operating system on the planet. The best way to get gcc is to buy the “GNU release” on CD-ROM, the profits of which go to support the Free Software Foundation. You can find both the GNU CD-ROM and the Linux CD-ROM (which includes GNU) in select places that sell software or technical books, or you can contact the F.S.F. directly. The GNU CD-ROM is a bit pricey, and, though the Linux CD-ROM is very inexpensive, if you can’t afford either, or want the latest version and already own a CD-ROM, you can download the gzip file ftp://prep.ai.mit.edu/pub/gnu/gcc-2.7.2.tar.gz, which contains all 7M of the latest gcc release. (If you’d like to make a donation to, or buy gcc or its manual from, the F.S.F., you can e-mail them at [email protected] or call 617-542-5942.)
Some Useful Functions When writing the C code for native implementations, a whole set of useful (internal) macros and functions is available for accessing Java run-time structures. (Several of them were used in SimpleFileNative.c.) Let’s take a brief digression to understand some of them a little better.*
*WARNING Don’t rely on the exact form given for any of the following macros and functions. Because they’re all internal to the Java run-time, they’re subject to change at any moment. Check to see what the latest versions of them look like in your Java release before using them. * *NOTE The following brief descriptions are taken from an alpha release of Java, because descriptions of them for the 1.0 release were not available as of this writing. How Java data types map into C types, and vice versa, will be detailed in future documentation. Refer to it for more details on that or on any of the sparsely documented items below. (Many are listed just to give you a taste of the capabilities of the available functions.) The following Object int
*unhand(Handle *) obj_length(HArray *)
returns a pointer to the data portion of an object and returns the length of an array. The actual pointer type returned is not always Object *, but varies, depending on the type of Handle (or HArray). While the following: ClassClass HArrayOfChar Handle
*FindClass(struct execenv *e, char *name, bool_t *MakeString(char *string, long length) *ArrayAlloc(int type, int length)
resolve)
finds a class (given its name), makes an array of characters of length length, and allocates an array of the length and type. Use the function: long
execute_java_dynamic_method(ExecEnv *e, HObject *obj, char *method_name, Âchar *signature, ...);
to call a Java method from C. e is NULL to use the current environment. The target of the method call is obj. The method method_name has the given method signature. It can have any number of arguments and returns a 32-bit value (int, Handle *, or any 32-bit C type). Use the following:
Java Unleashed
Page 423
HObject long
*execute_java_constructor(ExecEnv *e, char *classname, ClassClass *c, Âchar *signature, ...); execute_java_static_method(ExecEnv *e, ClassClass *c, char *method_name, Âchar *signature, ...);
to call a Java constructor from C and call a class method from C. c is the target class; the rest are as in execute_java_dynamic_method(). Calling this: SignalError(0, JAVAPKG “ExceptionClassName”, “message”); posts a Java exception that will be thrown when your native method returns. It is somewhat like the Java code: throw new ExceptionClassName(“message”); Finally, here are some useful string functions: void javaStringPrint(Hjava_lang_String *s) int javaStringLength(Hjava_lang_String *s) Hjava_lang_String *makeJavaString(char *string, int length) char *makeCString(Hjava_lang_String *s) char *allocCString(Hjava_lang_String *s) unicode *javaString2unicode(Hjava_lang_String *s, unicode *buf, int char *javaString2CString(Hjava_lang_String *s, char *buf, int
len) len)
The first two methods print a Java String (like System.out.print()), and get its length, respectively. The third makes a Java String out of a C string. The fourth and fifth do the reverse, turning a Java String into a C string (allocated from temporary or heap storage, respectively). The final two methods copy Java Strings into preexisting Unicode or ASCII C buffers.
Compiling the Stubs File The final step you need to take in the C world is to compile the stubs file SimpleFile.c by using the same compilation flags you used for SimpleFileNative.c.*
*NOTE If you have several classes with native methods, you can include all their stubs in the same .c file, if you like. Of course you might want to name it something else, such as Stubs.c, in that case. You’re now finished with all the C code that must be written (and compiled) to make your loadable native library.
A Native Library Now you’ll finally be able to tie everything together and create the native library, simple, that was assumed to exist at the beginning of this chapter.
Linking It All It’s time to link everything you’ve done into a single library file. This looks a little different on each system that Java runs on, but here’s the basic idea, in UNIX syntax: cc -G SimpleFile.o SimpleFileNative.o -o simple The -G flag tells the linker that you’re creating a dynamically linkable library; the details differ from system to system.*
*NOTE By naming the library simple, you’re disobeying a UNIX convention that dynamic library names should have the prefix lib and the suffix .so (on your system, these prefixes and suffixes may differ). You can call your library libsimple.so to obey the convention, if
Java Unleashed
Page 424
you like, but just for the clarity of this example, the simpler name was used.
Using Your Library Now, when the Java class SimpleFile is first loaded into your program, the System class attempts to load the library named simple, which (luckily) you just created. Look back at the Java code for SimpleFile to remind yourself. How does it locate it? It calls the dynamic linker, which consults an environment variable named LD_LIBRARY_PATH that tells it which sequence of directories to search when loading new libraries of native code. Because the current directory is in Java’s load path by default, you can leave “simple” in the current directory, and it will work just fine.
Summary This chapter discussed the numerous disadvantages of using native methods, about the many ways that Java (and you) can make your programs run faster, and also about the often illusory need for efficiency. It detailed the procedure for creating native methods, from both the Java and the C sides, in detail 97Äby generating header files and stubs, and by compiling and linking a full example. After working your way through this difficult material, you’ve mastered one of the most complex parts of the Java language. You now know how the Java run-time environment itself was created, and how to extend that powerful environment yourself, at its lowest levels.
Java Unleashed
Page 425
Chapter 39 Java's virtual machine, bytecodes, and more This chapter reveals the inner workings of Java. You’ll find out all about Java’s vision, Java’s virtual machine, those bytecodes you’ve heard so much about, that mysterious garbage collector, and why you might worry about security but don’t have to. Let’s begin, however, with the big picture.
The Big Picture The Java team is very ambitious. Their ultimate goal is nothing less than to revolutionize the way software is written and distributed. They’ve started with the Internet, where they believe much of the interesting software of the future will live. To achieve such an ambitious goal, a large fraction of the Internet programming community itself must be marshaled behind a similar goal and given the tools to help achieve it. The Java language, with its four S’s (small, simple, safe, secure), and its flexible, Netoriented environment, hopes to become the focal point for the rallying of this new legion of programmers. To this end, Sun Microsystems has done something rather gutsy. What was originally a secret, tens-of-millions-of-dollars research and development project, and 100 percent proprietary, has become a free, open, and relatively unencumbered technology standard upon which anyone can build. They are literally giving it away and reserving only the rights they need to maintain and grow the standard.*
*NOTE Actually, as Sun’s lawyers have had more and more time to think, the original intentions of the Java team get further obscured by legal details. It is still relatively unencumbered, but its earlier releases were completely unencumbered. Let’s hope that this is not a pattern that will continue. Any truly open standard must be supported by at least one excellent, freely available “demonstration” implementation. Sun has already shipped the final release of the API. In parallel, several universities, companies, and individuals have already expressed their intention to duplicate the Java environment, based on the open API that Sun has created. Several other languages are even contemplating compiling down to Java bytecodes, to help support them in becoming a more robust and widespread standard for moving executable content around on the Net.
Why It’s a Powerful Vision One of the reasons this brilliant move on Sun’s part has a real chance of success is the pent-up frustration of literally a whole generation of programmers who desperately want to share their code with one another. Right now, the computer science world is balkanized into factions at universities and companies all over the world, with hundreds of languages, dozens of them widely used, dividing and separating us all. It’s the worst sort of Tower of Babel. Java hopes to build some bridges and help tear down that tower. Because it is so simple, because it’s so useful for programming over the Internet, and because the Internet is so “hot” right now, this confluence of forces should help propel Java onto center stage. It deserves to be there. It is the natural outgrowth of ideas that, since the early 1970s inside the Smalltalk group at Xerox PARC, have lain relatively dormant in the mainstream. Smalltalk, in fact, invented the first object-oriented bytecode interpreter and pioneered many of the deep ideas that Java builds on today. Those efforts were not embraced over the intervening decades as a solution to the general problems of software, however. Today, with those problems becoming so much more obvious, and with the Net crying out for a new kind of programming, the soil is fertile to grow something stronger from those old roots, something that just might spread like wildfire. (Is it a coincidence that Java’s previous internal names were Green and OAK?) This new vision of software is one in which the Net becomes an ocean of objects, classes, and the open APIs between them. Traditional applications have vanished, replaced by skeletal frameworks like the Eiffel Tower, into which can be fitted any parts from this ocean, on demand, to suit any purpose. User interfaces will be mixed and matched, built in pieces, and constructed to taste— whenever the need arises—by their own users. Menus of choices will be filled by dynamic lists of all the choices available for that function, at that exact moment, across the entire ocean (of the Net).
Java Unleashed
Page 426
In such a world, software distribution is no longer an issue. Software will be everywhere and will be paid for through a plethora of new micro-accounting models, which charge tiny fractions of cents for the parts as they are assembled and used. Frameworks will come into existence to support entertainment, business, and the social (cyber-)spaces of the near future. This is a dream that many of us have waited all our lives to be a part of. There are tremendous challenges to making it all come true, but the powerful winds of change we all feel must stir us into action, because, at last, there is a base on which to build that dream— Java.
The Java Virtual Machine To make visions like this possible, Java must be ubiquitous. It must be able to run on any computer and any operating system—now, and in the future. In order to achieve this level of portability, Java must be very precise not only about the language itself, but about the environment in which the language lives. You can see, from earlier in the book and Appendix B, that the Java environment includes a generally useful set of packages of classes and a freely available implementation of them. This takes care of a part of what is needed, but it is crucial also to specify exactly how the run-time environment of Java behaves. This final requirement is what has stymied many attempts at ubiquity in the past. If you base your system on any assumptions about what is “beneath” the run-time system, you lose. If you depend in any way on the computer or operating system below, you lose. Java solves this problem by inventing an abstract computer of its own and running on that. This “virtual” machine runs a special set of “instructions” called bytecodes that are simply a stream of formatted bytes, each of which has a precise specification of exactly what each bytecode does to this virtual machine. The virtual machine is also responsible for certain fundamental capabilities of Java, such as object creation and garbage collection. Finally, in order to be able to move bytecodes safely across the Internet, you need a bulletproof model of security—and how to maintain it—and a precise format for how this stream of bytecodes can be sent from one virtual machine to another. (For more on security issues, see Chapter 40, “Java Security.”) Each of these requirements is addressed in this chapter. *
*NOTE This discussion blurs the distinction between the run-time and the virtual machine of Java. This is intentional but a little unconventional. Think of the virtual machine as providing all the capabilities, even those that are conventionally assigned to the runtime. This book uses the words “run-time” and “virtual machine” interchangeably. Equating the two highlights the single environment that must be created to support Java. Much of the following description is based closely on the latest “Virtual Machine Specifications” documents (and the 1.0 bytecodes, which had just emerged as of this writing), so if you delve more deeply into the details online, you should cover some familiar ground.
An Overview It is worth quoting the introduction to the Java virtual machine documentation here, because it is so relevant to the vision outlined earlier: The Java virtual machine specification has a purpose that is both like and unlike equivalent documents for other languages and abstract machines. It is intended to present an abstract, logical machine design free from the distraction of inconsequential details of any implementation. It does not anticipate an implementation technology, or an implementation host. At the same time it gives a reader sufficient information to allow implementation of the abstract design in a range of technologies. However, the intent of the … Java project is to create a language … that will allow the interchange over the Internet of “executable content,” which will be embodied by compiled Java code. The project specifically does not want Java to be a proprietary language, and does not want to be the sole purveyor of Java language implementations. Rather, we hope to make documents like this one, and source code for our implementation, freely available for people to use as they choose. This vision … can be achieved only if the executable content can be reliably shared between different Java implementations. These intentions prohibit the definition of the Java virtual machine from being fully abstract. Rather, relevant logical elements of the design have to be made sufficiently concrete to allow the interchange of compiled Java code. This does not
Java Unleashed
Page 427
collapse the Java virtual machine specification to a description of a Java implementation; elements of the design that do not play a part in the interchange of executable content remain abstract. But it does force us to specify, in addition to the abstract machine design, a concrete interchange format for compiled Java code. The Java virtual machine specification consists of the following:
The bytecode syntax, including opcode and operand sizes, values, and types, and their alignment and endian-ness The values of any identifiers (for example, type identifiers) in bytecodes or in supporting structures The layout of the supporting structures that appear in compiled Java code (for example, the constant pool) The Java .class file format Each of these is covered in this chapter. Despite this degree of specificity, there are still several elements of the design that remain (purposely) abstract, including the following:
The layout and management of the run-time data areas The particular garbage-collection algorithms, strategies, and constraints used The compiler, development environment, and run-time extensions (apart from the need to generate and read valid Java bytecodes) Any optimizations performed, once valid bytecodes are received These places are where the creativity of a virtual machine implementor has full rein.
The Fundamental Parts The Java virtual machine can be divided into five fundamental pieces:
A bytecode instruction set A set of registers A stack A garbage-collected heap An area for storing methods Some of these might be implemented by using an interpreter, a native binary code compiler, or even a hardware chip—but all these logical, abstract components of the virtual machine must be supplied in some form in every Java system. *
*NOTE The memory areas used by the Java virtual machine are not required to be at any particular place in memory, to be in any particular order, or even to use contiguous memory. However, all but the method area must be able to represent align 32-bit values (for example, the Java stack is 32 bits wide). The virtual machine, and its supporting code, is often referred to as the run-time environment, and when this book refers to something being done at run-time, the virtual machine is what’s doing it.
Java Bytecodes The Java virtual machine instruction set is optimized to be small and compact. It is designed to travel across the Net, and so has traded off speed-of-interpretation for space. (Given that both Net bandwidth and mass storage speeds increase less rapidly than CPU speed, this seems like an appropriate trade-off.)
Java Unleashed
Page 428
As mentioned, Java source code is “compiled” into bytecodes and stored in a .class file. On Sun’s Java system, this is performed using the javac tool. It is not exactly a traditional “compiler,” because javac translates source code into bytecodes, a lower-level format that cannot be run directly, but must be further interpreted by each computer. Of course, it is exactly this level of “indirection” that buys you the power, flexibility, and extreme portability of Java code.*
*NOTE Quotation marks are used around the word “compiler” when talking about javac because later in this chapter you will also learn about the “just-in-time” compiler, which acts more like the back end of a traditional compiler. The use of the same word “compiler” for these two different pieces of Java technology is unfortunate, but somewhat reasonable, because each is really one-half (either the front or the back end) of a more traditional compiler. A bytecode instruction consists of a one-byte opcode that serves to identify the instruction involved and zero or more operands, each of which may be more than one byte long, that encode the parameters the opcode requires. *
*NOTE When operands are more than one byte long, they are stored in big-endian order, high-order byte first. These operands must be assembled from the byte stream at run-time. For example, a 16-bit parameter appears in the stream as two bytes so that its value is first_byte * 256 + second_byte. The bytecode instruction stream is only byte-aligned, and alignment of any larger quantities is not guaranteed (except for “within” the special bytecodes lookupswitch and tableswitch, which have special alignment rules of their own). Bytecodes interpret data in the run-time memory areas as belonging to a fixed set of types: the primitives types you’ve seen several times before, consisting of several signed integer types (8-bit byte, 16-bit short, 32-bit int, 64-bit long), one unsigned integer type (16bit char), and two signed floating-point types (32-bit float, 64-bit double), plus the type “reference to an object” (a 32-bit pointer-like type). Some special bytecodes (for example, the dup instructions), treat run-time memory areas as raw data, without regard to type. This is the exception, however, not the rule. These primitives types are distinguished and managed by the compiler, javac, not by the Java run-time environment. These types are not “tagged” in memory, and thus cannot be distinguished at run-time. Different bytecodes are designed to handle each of the various primitive types uniquely, and the compiler carefully chooses from this palette based on its knowledge of the actual types stored in the various memory areas. For example, when adding two integers, the compiler generates an iadd bytecode; for adding two floats, fadd is generated. (You’ll see all this in gruesome detail later.)
Registers The registers of the Java virtual machine are just like the registers inside a “real” computer. *
*NOTE Registers hold the machine’s state, affect its operation, and are updated after each byte-code is executed. The following are the Java registers:
pc, the program counter, which indicates what bytecode is being executed optop, a pointer to the top of the operand stack, which is used to evaluate all arithmetic expressions frame, a pointer to the execution environment of the current method, which includes an activation record for this method call and any associated debugging information vars, a pointer to the first local variable of the currently executing method The virtual machine defines these registers to be 32 bits wide.*
*NOTE Because the virtual machine is primarily stack-based, it does not use any registers for passing or receiving arguments. This is a conscious choice skewed toward bytecode simplicity and compactness. It also aids efficient implementation on register-poor architectures, which most of today’s computers, unfortunately, are. Perhaps when the majority of CPUs out there are a little more sophisticated, this choice will be reexamined, though simplicity and compactness may still be reason enough! By the way, the pc register is also used when the run-time handles exceptions; catch clauses are (ultimately) associated with ranges
Java Unleashed
Page 429
of the pc within a method’s bytecodes.
The Stack The Java virtual machine is stack-based. A Java stack frame is similar to the stack frame of a conventional programming language—it holds the state for a single method call. Frames for nested method calls are stacked on top of this frame.*
*NOTE The stack is used to supply parameters to bytecodes and methods, and to receive results back from them. Each stack frame contains three (possibly empty) sets of data: the local variables for the method call, its execution environment, and its operand stack. The sizes of these first two are fixed at the start of a method call, but the operand stack varies in size as bytecodes are executed in the method. Local variables are stored in an array of 32-bit slots, indexed by the register vars. Most types take up one slot in the array, but the long and double types each take up two slots.*
*NOTE long and double values, stored or referenced through an index N, take up the (32-bit) slots N and N + 1. These 64-bit values are thus not guaranteed to be 64-bit-aligned. Implementors are free to decide the appropriate way to divide these values among the two slots. The execution environment in a stack frame helps to maintain the stack itself. It contains a pointer to the previous stack frame, a pointer to the local variables of the method call, and pointers to the stack’s current “base” and “top.” Additional debugging information can also be placed into the execution environment. The operand stack, a 32-bit first-in-first-out (FIFO) stack, is used to store the parameters and return values of most bytecode instructions. For example, the iadd bytecode expects two integers to be stored on the top of the stack. It pops them, adds them together, and pushes the resulting sum back onto the stack. Each primitive data type has unique instructions that know how to extract, operate, and push back operands of that type. For example, long and double operands take two “slots” on the stack, and the special bytecodes that handle these operands take this into account. It is illegal for the types on the stack and the instruction operating on them to be incompatible (javac outputs bytecodes that always obey this rule). *
*NOTE The top of the operand stack and the top of the overall Java stack are almost always the same. Thus, “the stack,” refers to both stacks, collectively. The Heap The heap is that part of memory from which newly created instances (objects) are allocated. The heap is often assigned a large, fixed size when the Java run-time system is started, but on systems that support virtual memory, it can grow as needed, in a nearly unbounded fashion. Because objects are automatically garbage-collected in Java, programmers do not have to (and, in fact, cannot) manually free the memory allocated to an object when they are finished using it. Java objects are referenced indirectly in the run-time, via handles, which are a kind of pointer into the heap. Because objects are never referenced directly, parallel garbage collectors can be written that operate independently of your program, moving around objects in the heap at will. You’ll learn more about garbage collection later.
The Method Area Like the compiled code areas of conventional programming language environments, or the TEXT segment in a UNIX process, the method area stores the Java bytecodes that implement almost every method in the Java system. (Remember that some methods might be native, and thus implemented, for example, in C.) The method area also stores the symbol tables needed for dynamic linking, and
Java Unleashed
Page 430
any other additional information debuggers or development environments might want to associate with each method’s implementation. Because bytecodes are stored as byte streams, the method area is aligned on byte boundaries. (The other areas are all aligned on 32-bit word boundaries.)
The Constant Pool In the heap, each class has a constant pool “attached” to it. Usually created by javac, these constants encode all the names (of variables, methods, and so forth) used by any method in a class. The class contains a count of how many constants there are and an offset that specifies how far into the class description itself the array of constants begins. These constants are typed through specially coded bytes, and have a precisely defined format when they appear in the .class file for a class. Later in this chapter, a little of this file format is covered, but everything is fully specified by the virtual machine specifications in your Java release.
Limitations The virtual machine, as currently defined, places some restrictions on legal Java programs by virtue of the choices it has made (some were previously described, and more will be detailed later in this chapter). These limitations and their implications are
32-bit pointers, which imply that the virtual machine can address only 4GB of memory (this may be relaxed in later releases) Unsigned 16-bit indices into the exception, line number, and local variable tables, which limit the size of a method’s bytecode implementation to 64K (this limitation may be eliminated in the final release) Unsigned 16-bit indices into the constant pool, which limits the number of constants in a class to 64K (a limit on the complexity of a class)
Bytecodes in More Detail One of the main tasks of the virtual machine is the fast, efficient execution of the Java bytecodes in methods. Unlike in a discussion about generality versus efficiency, this is a case where speed is of the utmost importance. Every Java program suffers from a slow implementation here, so the run-time must use as many “tricks” as possible to make bytecodes run fast. The only other goal (or limitation) is that Java programmers must not be able to see these tricks in the behavior of their programs. A Java run-time implementer must be extremely clever to satisfy both these goals.
The Bytecode Interpreter A bytecode interpreter examines each opcode byte (bytecode) in a method’s bytecode stream, in turn, and executes a unique action for that bytecode. This might consume further bytes for the operands of the bytecode and might affect which bytecode will be examined next. It operates like the hardware CPU in a computer, which examines memory for instructions to carry out in exactly the same manner. It is the software CPU of the Java virtual machine. Your first, naive attempt to write such a bytecode interpreter will almost certainly be disastrously slow. The inner loop, which dispatches one bytecode each time through the loop, is notoriously difficult to optimize. In fact, smart people have been thinking about this problem, in one form or another, for more than 20 years. Luckily, they’ve gotten results, all of which can be applied to Java. The final result is that the interpreter shipped in the current release of Java has an extremely fast inner loop. In fact, on even a relatively slow computer, this interpreter can perform more than 590,000 bytecodes per second! This is really quite good, because the CPU in that computer does only about 30 times better using hardware. This interpreter is fast enough for most Java programs (and for those requiring more speed, they can always use native methods), but what if a smart implementor wants to do better?
Java Unleashed
Page 431
The “Just-in-Time” Compiler About a decade ago, a really clever trick was discovered by L. Peter Deutsch while trying to make Smalltalk run faster. He called it “dynamic translation” during interpretation. Sun calls it “just-in-time” compiling. The trick is to notice that the really fast interpreter you’ve just written—in C, for example—already has a useful sequence of native binary code for each bytecode that it interprets: the binary code that the interpreter itself is executing. Because the interpreter has already been compiled from C into native binary code, for each bytecode that it interprets, it passes through a sequence of native code instructions for the hardware CPU on which it is running. By saving a copy of each binary instruction as it “goes by,” the interpreter can keep a running log of the binary code it itself has run to interpret a bytecode. It can just as easily keep a log of the set of bytecodes that it ran to interpret an entire method. You take that log of instructions and “peephole-optimize” it, just as a smart compiler does. This eliminates redundant or unnecessary instructions from the log, and makes it look just like the optimized binary code that a good compiler might have produced. *
*NOTE This is where the name compiler comes from, in “just-in-time” compiler, but it 92's really only the back end of a traditional compiler—the part that does code generation. By the way, the front end here is javac. Here’s where the trick comes in. The next time that method is run (in exactly the same way), the interpreter can now simply execute directly the stored log of binary native code. Because this optimizes out the inner-loop overhead of each bytecode, as well as any other redundancies between the bytecodes in a method, it can gain a factor of 10 or more in speed. In fact, an experimental version of this technology at Sun has shown that Java programs using it can run as fast as compiled C programs.*
*NOTE The parenthetical in the last paragraph is needed because if anything is different about the input to the method, it takes a different path through the interpreter and must be relogged. (There are sophisticated versions of this technology that solve this, and other, difficulties.) The cache of native code for a method must be invalidated whenever the method has changed, and the interpreter must pay a small cost up front each time a method is run for the first time. However, these small bookkeeping costs are far outweighed by the amazing gains in speed possible. The java2c Translator Another, simpler, trick, which works well whenever you have a good, portable C compiler on each system that runs your program, is to translate the bytecodes into C and then compile the C into binary native code. If you wait until the first use of a method or class, and then perform this as an “invisible” optimization, it gains you an additional speedup over the approach outlined previously, without the Java programmer needing to know about it. Of course, this does limit you to systems with a C compiler, but there are extremely good, freely available C compilers. In theory, your Java code might be able to travel with its own C compiler, or know where to pull one from the Net as needed, for each new computer and operating system it faced. (Because this violates some of the rules of normal Java code movement over the Net, though, it should be used sparingly.) If you’re using Java, for example, to write a server that lives only on your computer, it might be appropriate to use Java for its flexibility in writing and maintaining the server (and for its capability of dynamically linking new Java code on the fly), and then to run java2c by hand to translate the basic server itself entirely into native code. You’d link the Java run-time environment into that code so that your server remains a fully capable Java program, but it’s now an extremely fast one. In fact, an experimental version of the java2c translator inside Sun shows that it can reach the speed of compiled and optimized C code. This is the best that you can hope to do!*
*NOTE Unfortunately, as of the 1.0 release, there is still no publicly available java2c tool, and Sun’s virtual machine does not perform “justin-time” compilation. Both of these have been promised in a later release (perhaps 1.1). The Bytecodes Themselves Let’s look at a (progressively less and less) detailed description of each class of bytecodes.
Java Unleashed
Page 432
For each bytecode, some brief text describes its function, and a textual “picture” of the stack, both before and after the bytecode has been executed, is shown. This text picture will look like the following: ..., value1, value2 => ..., value3 This means that the bytecode expects two operands—value1 and value2—to be on the top of the stack, pops them both off the stack, operates on them to produce value3, and pushes value3 back onto the top of the stack. You should read each stack from right to left, with the rightmost value being the top of the stack. The ... is read as “the rest of the stack below,” which is irrelevant to the current bytecode. All operands on the stack are 32-bits wide. Because most bytecodes take their arguments from the stack and place their results back there, the brief text descriptions that follow only say something about the source or destination of values if they are not on the stack. For example, the description “Load integer from local variable.” means that the integer is loaded onto the stack, and “Integer add.” intends its integers to be taken from—and the result returned to—the stack. Bytecodes that don’t affect control flow simply move the pc onto the next bytecode that follows in sequence. Those that do affect the pc say so explicitly. Whenever you see byte1, byte2, and so forth, it refers to the first byte, second byte, and so on, that follow the opcode byte itself. After such a bytecode is executed, the pc automatically advances over these operand bytes to start the next bytecode in sequence.*
*NOTE The next few sections are in “reference manual style,” presenting each bytecode separately in all its (often redundant) detail. Later sections begin to collapse and coalesce this verbose style into something shorter, and more readable. The verbose form is shown at first because the online reference manuals will look more like it, and because it drives home the point that each bytecode “function” comes in many nearly identical bytecodes, one for each primitive type in Java. Pushing Constants onto the Stack bipush
... => ..., value
Push one-byte signed integer. byte1 is interpreted as a signed 8-bit value. This value is expanded to an int and pushed onto the operand stack. sipush
... => ..., value
Push two-byte signed integer. byte1 and byte2 are assembled into a signed 16-bit value. This value is expanded to an int and pushed onto the operand stack. ldc1
... => ..., item
Push item from constant pool. byte1 is used as an unsigned 8-bit index into the constant pool of the current class. The item at that index is resolved and pushed onto the stack. ldc2
... => ..., item
Push item from constant pool. byte1 and byte2 are used to construct an unsigned 16-bit index into the constant pool of the current class. The item at that index is resolved and pushed onto the stack. ldc2w
... => ..., constant-word1, constant-word2
Push long or double from constant pool. byte1 and byte2 are used to construct an unsigned 16-bit index into the constant pool of the current class. The two-word constant at that index is resolved and pushed onto the stack. aconst_null
... => ..., null
Java Unleashed
Page 433
Push the null object reference onto the stack. iconst_m1
... => ..., -1
Push the int -1 onto the stack. iconst_
... => ...,
Push the int onto the stack. There are six of these bytecodes, one for each of the integers 0-5: iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, and iconst_5. lconst_
... => ..., -word1, -word2
Push the long onto the stack. There are two of these bytecodes, one for each of the integers 0 and 1: lconst_0, and lconst_1. fconst_
... => ...,
Push the float onto the stack. There are three of these bytecodes, one for each of the integers 0-2: fconst_0, fconst_1, and fconst_2. dconst_
... => ..., -word1, -word2
Push the double onto the stack. There are two of these bytecodes, one for each of the integers 0 and 1: dconst_0, and dconst_1.
Loading Local Variables onto the Stack iload
... => ..., value
Load int from local variable. Local variable byte1 in the current Java frame must contain an int. The value of that variable is pushed onto the operand stack. iload_
... => ..., value
Load int from local variable. Local variable in the current Java frame must contain an int. The value of that variable is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: iload_0, iload_1, iload_2, and iload_3. lload
... => ..., value-word1, value-word2
Load long from local variable. Local variables byte1 and byte1 + 1 in the current Java frame must together contain a long integer. The values contained in those variables are pushed onto the operand stack. lload_
... => ..., value-word1, value-word2
Load long from local variable. Local variables and + 1 in the current Java frame must together contain a long integer. The value contained in those variables is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 03: lload_0, lload_1, lload_2, and lload_3. fload
... => ..., value
Load float from local variable. Local variable byte1 in the current Java frame must contain a single precision floating-point number. The value of that variable is pushed onto the operand stack.
Java Unleashed fload_
Page 434
... => ..., value
Load float from local variable. Local variable in the current Java frame must contain a single precision floating-point number. The value of that variable is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: fload_0, fload_1, fload_2, and fload_3. dload
... => ..., value-word1, value-word2
Load double from local variable. Local variables byte1 and byte1 + 1 in the current Java frame must together contain a double precision floating-point number. The value contained in those variables is pushed onto the operand stack. dload_
... => ..., value-word1, value-word2
Load double from local variable. Local variables and + 1 in the current Java frame must together contain a double precision floating-point number. The value contained in those variables is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: dload_0, dload1, dload_2, and dload_3. aload
... => ..., value
Load object reference from local variable. Local variable byte1 in the current Java frame must contain a return address or reference to an object. The value of that variable is pushed onto the operand stack. aload_
... => ..., value
Load object reference from local variable. Local variable in the current Java frame must contain a return address or reference to an object. The value of that variable is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: aload_0, aload_1, aload_2, and aload_3.
Storing Stack Values into Local Variables istore
..., value => ...
Store int into local variable. value must be an int. Local variable byte1 in the current Java frame is set to value. istore_
..., value => ...
Store int into local variable. value must be an int. Local variable in the current Java frame is set to value. There are four of these bytecodes, one for each of the integers 0-3: istore_0, istore_1, istore_2, and istore_3. lstore
..., value-word1, value-word2 => ...
Store long into local variable. value must be a long integer. Local variables byte1 and byte1 + 1 in the current Java frame are set to value. lstore_
..., value-word1, value-word2 => ...
Store long into local variable. value must be a long integer. Local variables and + 1 in the current Java frame are set to value. There are four of these bytecodes, one for each of the integers 0-3: lstore_0, lstore_1, lstore_2, and lstore_3. fstore
..., value => ...
Store float into local variable. value must be a single precision floating-point number. Local variable byte1 in the current Java frame is set to value.
Java Unleashed fstore_
Page 435
..., value => ...
Store float into local variable. value must be a single precision floating-point number. Local variable in the current Java frame is set to value. There are four of these bytecodes, one for each of the integers 0-3: fstore_0, fstore_1, fstore_2, and fstore_3. dstore
..., value-word1, value-word2 => ...
Store double into local variable. value must be a double precision floating-point number. Local variables byte1 and byte1 + 1 in the current Java frame are set to value. dstore_
..., value-word1, value-word2 => ...
Store double into local variable. value must be a double precision floating-point number. Local variables and + 1 in the current Java frame are set to value. There are four of these bytecodes, one for each of the integers 0-3: dstore_0, dstore_1, dstore_2, and dstore_3. astore
..., handle => ...
Store object reference into local variable. handle must be a return address or a reference to an object. Local variable byte1 in the current Java frame is set to value. astore_
..., handle => ...
Store object reference into local variable. handle must be a return address or a reference to an object. Local variable in the current Java frame is set to value. There are four of these bytecodes, one for each of the integers 0-3: astore_0, astore_1, astore_2, and astore_3. iinc
-no change-
Increment local variable by constant. Local variable byte1 in the current Java frame must contain an int. Its value is incremented by the value byte2, where byte2 is treated as a signed 8-bit quantity. Wide
-no change-
Precedes iload, lload, fload, dload, aload, istore, lstore, fstore, dstore, astore, or iinc. byte1 and byte 2 of the following bytecode form an unsigned 16-bit index that replaces the 8-bit index of the bytecode following.
Managing Arrays newarray
..., size => result
Allocate new array. size must be an int. It represents the number of elements in the new array. byte1 is an internal code that indicates the type of array to allocate. Possible values for byte1 are as follows: T_BOOLEAN (4), T_CHAR (5), T_FLOAT (6), T_DOUBLE (7), T_BYTE (8), T_SHORT (9), T_INT (10), and T_LONG (11). An attempt is made to allocate a new array of the indicated type, capable of holding size elements. This will be the result. If size is less than zero, a NegativeArraySizeException is thrown. If there is not enough memory to allocate the array, an OutOfMemoryError is thrown. All elements of the array are initialized to their default values. anewarray
..., size => result
Allocate new array of objects. size must be an int. It represents the number of elements in the new array. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index is resolved. The resulting entry must be a class.
Java Unleashed
Page 436
An attempt is made to allocate a new array of the indicated class type, capable of holding size elements. This will be the result. If size is less than zero, a NegativeArraySizeException is thrown. If there is not enough memory to allocate the array, an OutOfMemoryError is thrown. All elements of the array are initialized to null. *
*NOTE anewarray is used to create a single dimension of an array of objects. For example, the request new Thread[7] generates the following bytecodes: bipush 7 anewarray anewarray can also be used to create the outermost dimension of a multidimensional array. For example, the array declaration new int[6][] generates this: bipush 6 anewarray (See the section “Method Signatures” for more information on strings such as [I.) multianewarray
..., size1 size2...sizeN => result
Allocate new multidimensional array. Each size must be an int. Each represents the number of elements in a dimension of the array. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index is resolved. The resulting entry must be an array class of one or more dimensions. byte3 is a postive integer representing the number of dimensions being created. It must be less than or equal to the number of dimensions of the array class. byte3 is also the number of elements that are popped off the stack. All must be ints greater than or equal to zero. These are used as the sizes of the dimensions. An attempt is made to allocate a new array of the indicated class type, capable of holding size * size * ... * <sizeN> elements. This will be the result. If any of the size arguments on the stack is less than zero, a NegativeArraySizeException is thrown. If there is not enough memory to allocate the array, an OutOfMemoryError is thrown.*
*NOTE new int[6][3][] generates these bytecodes: bipush 6 bipush 3 multianewarray 2 It’s more efficient to use newarray or anewarray when creating arrays of single dimension. arraylength
..., array => ..., length
Get length of array. array must be a reference to an array object. The length of the array is determined and replaces array on the top of the stack. If array is null, a NullPointerException is thrown. iaload laload faload daload aaload baload caload saload
..., ..., ..., ..., ..., ..., ..., ...,
array, array, array, array, array, array, array, array,
index index index index index index index index
=> => => => => => => =>
..., ..., ..., ..., ..., ..., ..., ...,
value value-word1, value-word2 value value-word1, value-word2 value value value value
Load from array. array must be an array of s. index must be an int. The value at position number index in array is retrieved and pushed onto the top of the stack. If array is null, a NullPointerException is thrown. If index is not within the bounds of array, an ArrayIndexOutOfBoundsException is thrown. is, in turn, int, long, float, double, object reference, byte, char, and short. s long and double have two word values, as you’ve seen in previous load bytecodes.
Java Unleashed iastore lastore fastore dastore aastore bastore castore sastore
Page 437 ..., ..., ..., ..., ..., ..., ..., ...,
array, array, array, array, array, array, array, array,
index, index, index, index, index, index, index, index,
value => ... value-word1, value-word2 => ... value => ... value-word1, value-word2 => ... value => ... value => ... value => ... value => ...
Store into array. array must be an array of s, index must be an int, and value a . The value is stored at position index in array. If array is null, a NullPointerException is thrown. If index is not within the bounds of array, an ArrayIndexOutOfBoundsException is thrown. is, in turn, int, long, float, double, object reference, byte, char, and short. s long and double have two word values, as you’ve seen in previous store bytecodes.
Stack Operations nop
-no change-
Do nothing. pop
..., any => ...
Pop the top word from the stack. pop2
..., any2, any1 => ...
Pop the top two words from the stack. dup
..., any => ..., any, any
Duplicate the top word on the stack. dup2
..., any2, any1 => ..., any2, any1, any2,any1
Duplicate the top two words on the stack. dup_x1
..., any2, any1 => ..., any1, any2,any1
Duplicate the top word on the stack and insert the copy two words down in the stack. dup2_x1
..., any3, any2, any1 => ..., any2, any1, any3,any2,any1
Duplicate the top two words on the stack and insert the copies two words down in the stack. dup_x2
..., any3, any2, any1 => ..., any1, any3,any2,any1
Duplicate the top word on the stack and insert the copy three words down in the stack. dup2_x2
..., any4, any3, any2, any1 => ..., any2, any1, any4,any3,any2,any1
Duplicate the top two words on the stack and insert the copies three words down in the stack. swap
..., any2, any1 => ..., any1, any2
Java Unleashed
Page 438
Swap the top two elements on the stack.
Arithmetic Operations iadd ladd fadd dadd
..., ..., ..., ...,
v1, v2 => v1-word1, v1, v2 => v1-word1,
..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
v1 and v2 must be s. The vs are added and are replaced on the stack by their sum. is, in turn, int, long, float, and double. isub lsub fsub dsub
..., ..., ..., ...,
v1, v2 => v1-word1, v1, v2 => v1-word1,
..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
v1 and v2 must be s. v2 is subtracted from v1, and both vs are replaced on the stack by their difference. is, in turn, int, long, float, and double. imul lmul fmul dmul
..., ..., ..., ...,
v1, v2 => v1-word1, v1, v2 => v1-word1,
..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
v1 and v2 must be s. Both vs are replaced on the stack by their product. is, in turn, int, long, float, and double. idiv ldiv fdiv ddiv
..., ..., ..., ...,
v1, v2 => v1-word1, v1, v2 => v1-word1,
..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
v1 and v2 must be s. v2 is divided by v1, and both vs are replaced on the stack by their quotient. An attempt to divide by zero results in an ArithmeticException being thrown. is, in turn, int, long, float, and double. irem lrem frem drem
..., ..., ..., ...,
v1, v2 => v1-word1, v1, v2 => v1-word1,
..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
v1 and v2 must be s. v2 is divided by v1, and both vs are replaced on the stack by their remainder. An attempt to divide by zero results in an ArithmeticException being thrown. is, in turn, int, long, float, and double. ineg lneg fneg dneg
..., ..., ..., ...,
value => ..., result value-word1, value-word2 => ..., result-word1, result-word2 value => ..., result value-word1, value-word2 => ..., result-word1, result-word2
value must be a . It is replaced on the stack by its arithmetic negation. is, in turn, int, long, float, and double. *
*NOTE Now that you’re familiar with the look of the bytecodes, the summaries that follow will become shorter and shorter (for space reasons). You can always get any desired level of detail from the full virtual machine specification in the latest Java release.
Java Unleashed
Page 439
Logical Operations ishl lshl ishr lshr iushr lushr
..., ..., ..., ..., ..., ...,
v1, v2 => v1-word1, v1, v2 => v1-word1, v1, v2 => v1-word1,
..., result v1-word2, v2 => ..., r-word1, r-word2 ..., result v1-word2, v2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
For types int and long: arithmetic shift-left, shift-right, and logical shift-right. iand land ior lor ixor lxor
..., ..., ..., ..., ..., ...,
v1, v2 => v1-word1, v1, v2 => v1-word1, v1, v2 => v1-word1,
..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2 ..., result v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
For types int and long: bitwise AND, OR, and XOR.
Conversion Operations i2l i2f i2d l2i l2f l2d f2i f2l f2d d2i d2l d2f int2byte int2char int2short
..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ...,
value => ..., result-word1, value => ..., result value => ..., result-word1, value-word1, value-word2 => value-word1, value-word2 => value-word1, value-word2 => value => ..., result value => ..., result-word1, value => ..., result-word1, value-word1, value-word2 => value-word1, value-word2 => value-word1, value-word2 => value => ..., result value => ..., result value => ..., result
result-word2 result-word2 ..., result ..., result ..., result-word1, result-word2 result-word2 result-word2 ..., result ..., result-word1, result-word2 ..., result
These bytecodes convert from a value of type to a result of type . and can be any of i, l, f, and d, which represent int, long, float, and double, respectively. The final three bytecodes convert types that are self-explanatory.
Transfer of Control ifeq ifne iflt ifgt ifle ifge if_icmpeq if_icmpne if_icmplt if_icmpgt if_icmple if_icmpge ifnull ifnonnull
..., value => ... ..., value => ... ..., value => ... ..., value => ... ..., value => ... ..., value => ... ..., value1, value2 ..., value1, value2 ..., value1, value2 ..., value1, value2 ..., value1, value2 ..., value1, value2 ..., value => ... ..., value => ...
=> => => => => =>
... ... ... ... ... ...
Java Unleashed
Page 440
When value 0 is true in the first set of bytecodes, value1 value2 is true in the second set, or value is null (or not null) in the third, byte1 and byte2 are used to construct a signed 16-bit offset. Execution proceeds at that offset from the pc. Otherwise, execution proceeds at the bytecode following. is one of eq, ne, lt, gt, le, and ge, which represent equal, not equal, less than, greater than, less than or equal, and greater than or equal, respectively. lcmp fcmpl dcmpl fcmpg dcmpg
..., ..., ..., ..., ...,
v1-word1, v1, v2 => v1-word1, v1, v2 => v1-word1,
v1-word2, v2-word1, v2-word2 => ..., result ..., result v1-word2, v2-word1, v2-word2 => ..., result ..., result v1-word2, v2-word1, v2-word2 => ..., result
v1 and v2 must be long, float, or double. They are both popped from the stack and compared. If v1 is greater than v2, the int value 1 is pushed onto the stack. If v1 is equal to v2, 0 is pushed onto the stack. If v1 is less than v2, -1 is pushed onto the stack. For floatingpoint, if either v1 or v2 is NaN, -1 is pushed onto the stack for the first pair of bytecodes, +1 for the second pair. if_acmpeq if_acmpne
..., value1, value2 => ... ..., value1, value2 => ...
Branch if object references are equal/not equal. value1 and value2 must be references to objects. They are both popped from the stack. If value1 is equal/not equal to value2, byte1 and byte2 are used to construct a signed 16-bit offset. Execution proceeds at that offset from the pc. Otherwise, execution proceeds at the bytecode following. goto goto_w
-no change-no change-
Branch always. byte1 and byte2 (plus byte3 and byte4 for goto_w) are used to construct a signed 16-bit (32-bit) offset. Execution proceeds at that offset from the pc. jsr jsr_w
... => ..., return-address ... => ..., return-address
Jump subroutine. The address of the bytecode immediately following the jsr is pushed onto the stack. byte1 and byte2 (plus byte3 and byte4 for goto_w) are used to construct a signed 16-bit (32-bit) offset. Execution proceeds at that offset from the pc. ret ret_w
-no change-no change-
Return from subroutine. Local variable byte1 (plus byte2 for ret_w are assembled into a 16-bit index) in the current Java frame must contain a return address. The contents of that local variable are written into the pc.*
*NOTE jsr pushes the address onto the stack, and ret gets it out of a local variable. This asymmetry is intentional. The jsr and ret bytecodes are used in the implementation of Java’s finally keyword.
Method Return return
... => [empty]
Return (void) from method. All values on the operand stack are discarded. The interpreter then returns control to its caller.
Java Unleashed ireturn lreturn freturn dreturn areturn
..., ..., ..., ..., ...,
Page 441 value => [empty] value-word1, value-word2 => [empty] value => [empty] value-word1, value-word2 => [empty] value => [empty]
Return from method. value must be a . The value is pushed onto the stack of the previous execution environment. Any other values on the operand stack are discarded. The interpreter then returns control to its caller. is, in turn, int, long, float, double, and object reference.*
*NOTE The stack behavior of the “return” bytecodes may be confusing to anyone expecting the Java operand stack to be just like the C stack. Java’s operand stack actually consists of a number of discontiguous segments, each corresponding to a method call. A return bytecode empties the Java operand stack segment corresponding to the frame of the returning call, but does not affect the segment of any parent calls. Table Jumping tableswitch
..., index => ...
tableswitch is a variable-length bytecode. Immediately after the tableswitch opcode, zero to three 0 bytes are inserted as padding so that the next byte begins at an address that is a multiple of four. After the padding are a series of signed 4-byte quantities: defaultoffset, low, high, and then high-low+1 further signed 4-byte offsets. These offsets are treated as a 0-based jump table. The index must be an int. If index is less than low or index is greater than high, default-offset is added to the pc. Otherwise, the indexlow’th element of the jump table is extracted and added to the pc. lookupswitch ..., key => ... lookupswitch is a variable-length bytecode. Immediately after the lookupswitch opcode, zero to three 0-bytes are inserted as padding so that the next byte begins at an address that is a multiple of four. Immediately after the padding is a series of pairs of signed 4-byte quantities. The first pair is special; it contains the default-offset and the number of pairs that follow. Each subsequent pair consists of a match and an offset. The key on the stack must be an int. This key is compared to each of the matches. If it is equal to one of them, the corresponding offset is added to the pc. If the key does not match any of the matches, the default-offset is added to the pc.
Manipulating Object Fields putfield putfield
..., handle, value => ... ..., handle, value-word1,value-word2 => ...
Set field in object. byte1 and byte2 are used to construct and index into the constant pool of the current class. The constant pool item is a field reference to a class name and a field name. The item is resolved to a field block pointer containing the field’s width and offset both in bytes. The field at that offset from the start of the object referenced by handle will be set to the value on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If handle is null, a NullPointerException is thrown. If the specified field is a static field, an IncompatibleClassChangeError is thrown. getfield getfield
...,handle => ..., value ...,handle => ..., value-word1,value-word2
Java Unleashed
Page 442
Fetch field from object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The constant pool item will be a field reference to a class name and a field name. The item is resolved to a field block pointer containing the field’s width and offset both in bytes. handle must be a reference to an object. The value at offset into the object referenced by handle replaces handle on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If the specified field is a static field, an IncompatibleClassChangeError is thrown. putstatic putstatic
..., value => ... ..., value-word1, value-word2 => ...
Set static field in class. byte1 and byte2 are used to construct an index into the constant pool of the current class. The constant pool item will be a field reference to a static field of a class. That field will be set to have the value on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If the specified field is not a static field, an IncompatibleClassChangeError is thrown. getstatic getstatic
..., => ..., value ..., => ..., value-word1, value-word2
Get static field from class. byte1 and byte2 are used to construct an index into the constant pool of the current class. The constant pool item will be a field reference to a static field of a class. The value of that field is placed on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If the specified field is not a static field, an IncompatibleClassChangeError is thrown.
Method Invocation invokevirtual
..., handle, [arg1, [arg2 ...]], ... => ...
Invoke instance method based on run-time type. The operand stack must contain a reference (handle) to an object and some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature. A pointer to the object’s method table is retrieved from the object reference. The method signature is looked up in the method table. The method signature is guaranteed to exactly match one of the method signatures in the table exactly. The result of the lookup is an index into the method table of the named class that is used to look in the method table of the object’s run-time type, where a pointer to the method block for the matched method is found. The method block indicates the type of method (native, synchronized, and so on) and the number of arguments (nargs) expected on the operand stack. If the method is marked synchronized, the monitor associated with handle is entered. The base of the local variables array for the new Java stack frame is set to point to handle on the stack, making handle and the supplied arguments (arg1, arg2, ...) the first nargs local variables of the new frame. The total number of local variables used by the method is determined, and the execution environment of the new frame is pushed after leaving sufficient room for the locals. The base of the operand stack for this method invocation is set to the first word after the execution environment. Finally, execution continues with the first bytecode of the matched method. If handle is null, a NullPointerException is thrown. If during the method invocation a stack overflow is detected, a StackOverflowError is thrown. invokenonvirtual
..., handle, [arg1, [arg2, ...]], ... => ...
Invoke instance method based on compile-time type. The operand stack must contain a reference (handle) to an object and some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature and class. The method signature is looked up in the method table of the class indicated. The method signature is guaranteed to exactly match one of the method signatures in the table.
Java Unleashed
Page 443
The result of the lookup is a method block. The method block indicates the type of method (native, synchronized, and so on) and the number of arguments (nargs) expected on the operand stack. (The last three paragraphs are identical to the previous bytecode.) invokestatic
..., , [arg1, [arg2, ...]], ... => ...
Invoke class (static) method. The operand stack must contain some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature and class. The method signature is looked up in the the method table of the class indicated. The method signature is guaranteed to match one of the method signatures in the class’s method table exactly. The result of the lookup is a method block. The method block indicates the type of method (native, synchronized, and so on) and the number of arguments (nargs) expected on the operand stack. If the method is marked synchronized, the monitor associated with the class is entered. (The last two paragraphs are identical to those in invokevirtual, except that no NullPointerException can be thrown.) invokeinterface
..., handle, [arg1, [arg2, ...]], ... => ...
Invoke interface method. The operand stack must contain a reference (handle) to an object and some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature. A pointer to the object’s method table is retrieved from the object reference. The method signature is looked up in the method table. The method signature is guaranteed to exactly match one of the method signatures in the table. The result of the lookup is a method block. The method block indicates the type of method (native, synchronized, and so on) but, unlike the other “invoke” bytecodes, the number of available arguments (nargs) is taken from byte3; byte4 is reserved for future use. (The last three paragraphs are identical to those in invokevirtual.)
Exception Handling athrow
..., handle => [undefined]
Throw exception. handle must be a handle to an exception object. That exception, which must be an instance of Throwable (or a subclass) is thrown. The current Java stack frame is searched for the most recent catch clause that handles the exception. If a matching “catch-list” entry is found, the pc is reset to the address indicated by the catch-list pointer, and execution continues there. If no appropriate catch clause is found in the current stack frame, that frame is popped and the exception is rethrown, starting the process all over again in the parent frame. If handle is null, then a NullPointerException is thrown instead.
Miscellaneous Object Operations new
... => ..., handle
Create new object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index must be a class name that can be resolved to a class pointer, class. A new instance of that class is then created and a reference (handle) for the instance is placed on the top of the stack. checkcast
..., handle => ..., [handle | ...]
Make sure object is of given type. handle must be a reference to an object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The string at that index of the constant pool is presumed to be a class name that can be resolved to a class pointer, class. checkcast determines whether handle can be cast to a reference to an object of that class. (A null handle can be cast to any class.) If handle can be legally cast, execution proceeds at the next bytecode, and the handle remains on the stack. If not, a ClassCastException is thrown and the stack is emptied.
Java Unleashed instanceof
Page 444 ..., handle => ..., result
Determine whether object is of given type. handle must be a reference to an object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The string at that index of the constant pool is presumed to be a class name that can be resolved to a class pointer, class. If handle is null, the result is 0 (false). Otherwise, instanceof determines whether handle can be cast to a reference to an object of that class. The result is 1 (true) if it can, and 0 (false) otherwise.
Monitors monitorenter
..., handle => ...
Enter monitored region of code. handle must be a reference to an object. The interpreter attempts to obtain exclusive access via a lock mechanism to handle. If another thread already has handle locked, the current thread waits until the handle is unlocked. If the current thread already has handle locked, execution continues normally. If handle has no lock on it, this bytecode obtains an exclusive lock. (A null in either bytecode throws NullPointerException.) monitorexit
..., handle => ...
Exit monitored region of code. handle must be a reference to an object. The lock on handle is released. If this is the last lock that this thread has on that handle (one thread is allowed to have multiple locks on a single handle), other threads that are waiting for handle are allowed to proceed. (A null in either bytecode throws NullPointerException.)
Debugging breakpoint
-no change-
Call breakpoint handler. The breakpoint bytecode is used to overwrite a bytecode to force control temporarily back to the debugger prior to the effect of the overwritten bytecode. The original bytecode’s operands (if any) are not overwritten, and the original bytecode is restored when the breakpoint bytecode is removed.
The _quick Bytecodes The following discussion, straight out of the Java virtual machine documentation, shows you an example of the cleverness mentioned earlier that’s needed to make a bytecode interpreter fast: The following set of pseudo-bytecodes, suffixed by _quick, are all variants of standard Java bytecodes. They are used by the run-time to improve the execution speed of the bytecode interpreter. They aren’t officially part of the virtual machine specification and are invisible outside a Java virtual machine implementation. However, inside that implementation they have proven to be an effective optimization. First, you should know that javac still generates only non-_quick bytecodes. Second, all bytecodes that have a _quick variant reference the constant pool. When _quick optimization is turned on, each non-_quick bytecode (that has a _quick variant) resolves the specified item in the constant pool, signals an error if the item in the constant pool could not be resolved for some reason, turns itself into the _quick variant of itself, and then performs its intended operation. This is identical to the actions of the non-_quick bytecode, except for the step of overwriting itself with its _quick variant. The _quick variant of a bytecode assumes that the item in the constant pool has already been resolved, and that this resolution did not produce any errors. It simply performs the intended operation on the resolved item. Thus, as your bytecodes are being interpreted, they are automatically getting faster and faster! Here are all the _quick variants in the current Java run-time:
Java Unleashed
Page 445
ldc1_quick ldc2_quick ldc2w_quick anewarray_quick multinewarray_quick putfield_quick putfield2_quick getfield_quick getfield2_quick putstatic_quick putstatic2_quick getstatic_quick getstatic2_quick invokevirtual_quick invokevirtualobject_quick invokenonvirtual_quick invokestatic_quick invokeinterface_quick new_quick checkcast_quick instanceof_quick If you’d like to go back in this chapter and look at what each of these does, you can find the name of the original bytecode on which a _quick variant is based by simply removing the _quick from its name. The bytecodes putstatic, getstatic, putfield, and getfield have two _quick variants each, one for each stack picture in their original descriptions. invokevirtual has two variants: one for objects and for arrays (to do fast lookups in java.lang.Object).*
*NOTE One last note on the _quick optimization, regarding the unusual handling of the constant pool (for detail fanatics only): When a class is read in, an array constant_pool[] of size nconstants is created and assigned to a field in the class. constant_pool[0] is set to point to a dynamically allocated array that indicates which fields in the constant_pool have already been resolved. constant_pool[1] through constant_pool[nconstants - 1] are set to point at the “type” field that corresponds to this constant item. When a bytecode is executed that references the constant pool, an index is generated, and constant_pool[0] is checked to see whether the index has already been resolved. If so, the value of constant_pool[index] is returned. If not, the value of constant_pool[index] is resolved to be the actual pointer or data, and overwrites whatever value was already in constant_pool[index].
The .class File Format You won’t be given the entire .class file format here, only a taste of what it’s like. (You can read all about it in the release documentation.) It’s mentioned here because it is one of the parts of Java that needs to be specified carefully if all Java implementations are to be compatible with one another, and if Java bytecodes are expected to travel across arbitrary networks—to and from arbitrary computers and operating systems—and yet arrive safely. The rest of this section paraphrases, and extensively condenses, the latest release of the .class documentation. .class files are used to hold the compiled versions of both Java classes and Java interfaces. Compliant Java interpreters must be capable of dealing with all .class files that conform to the following specification. (Use java.io.DataInput and java.io.DataOutput to read and write .class files.) A Java .class file consists of a stream of 8-bit bytes. All 16-bit and 32-bit quantities are constructed by reading in two or four 8-bit bytes, respectively. The bytes are joined together in big-endian order. The class file format is presented below as a series of C-struct-like structures. However, unlike a C struct, there is no padding or alignment between pieces of the structure, each field of the structure may be of variable size, and an array may be of variable size (in
Java Unleashed
Page 446
this case, some field prior to the array gives the array’s dimension). The types u1, u2, and u4 represent an unsigned one-, two-, or four-byte quantity, respectively. Attributes are used at several different places in the .class format. All attributes have the following format: GenericAttribute_info { u2 attribute_name; u4 attribute_length; u1 info[attribute_length]; } The attribute_name is a 16-bit index into the class’s constant pool; the value of constant_pool[attribute_name] is a string giving the name of the attribute. The field attribute_length gives the length of the subsequent information in bytes. This length does not include the four bytes needed to store attribute_name and attribute_length. In the following text, whenever an attribute is required, names of all the attributes that are currently understood are listed. In the future, more attributes will be added. Class file readers are expected to skip over and ignore the information in any attributes that they do not understand. The following pseudo-structure gives a top-level description of the format of a class file: ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count - 1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attribute_count]; } Here’s one of the smaller structures used: method_info { u2 access_flags; u2 name_index; u2 signature_index; u2 attributes_count; attribute_info attributes[attribute_count]; } Finally, here’s a sample of one of the later structures in the .class file description:
Java Unleashed
Page 447
Code_attribute { u2 attribute_name_index; u2 attribute_length; u1 max_stack; u1 max_locals; u2 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attribute_count]; } None of this is meant to be completely comprehensible (though you might be able to guess at what a lot of the structure members are for), but just suggestive of the sort of structures that live inside .class files. Because the compiler and run-time sources are available, you can always begin with them if you actually have to read or write .class files yourself. Thus, you don’t need to have a deep understanding of the details, even in that case.
Method Signatures Because methods signatures are used in .class files, now is an appropriate time to explore them in detail—but they’re probably most useful to you when writing the native methods.*
*NOTE A signature is a string representing the type of a method, field, or array. A field signature represents the value of an argument to a method or the value of a variable and is a series of bytes in the following grammar: <array_type>
:= := := := := :=
| | <array_type> B | C | D | F | I | J | S | Z L ; [ [0-9]*
Here are the meanings of the base types: B (byte), C (char), D (double), F (float), I (int), J (long), S (short), and Z (boolean). A return-type signature represents the return value from a method and is a series of bytes in the following grammar:
:= | V
The character V (void) indicates that the method returns no value. Otherwise, the signature indicates the type of the return value. An argument signature represents an argument passed to a method: <argument signature>
:=
Finally, a method signature represents the arguments that the method expects, and the value that it returns: <method_signature> := (<arguments signature>) <arguments signature> := <argument signature>*
Java Unleashed
Page 448
Now, let’s try out the new rules: a method called complexMethod() in the class my.package.name.ComplexClass takes three arguments—a long, a boolean, and a two-dimensional array of shorts—and returns this. Then, (JZ[[S)Lmy.package.name.ComplexClass; is its method signature. A method signature is often prefixed by the name of the method, or by its full package (using an underscore in the place of dots) and its class name followed by a slash (/) and the name of the method, to form a complete method signature. Now, at last, you have the full story! Thus, the following my_package_name_ComplexClass/complexMethod(JZ[[S)Lmy.package.name.ComplexClass; is the full, complete method signature of complexMethod(). (Phew!)
The Garbage Collector Decades ago, programmers in both the Lisp and the Smalltalk community realized how extremely valuable it is to be able to ignore memory deallocation. They realized that, although allocation is fundamental, deallocation is forced on the programmer by the laziness of the system—it should be able to figure out what is no longer useful, and get rid of it. In relative obscurity, these pioneering programmers developed a whole series of garbage collectors to perform this job, each getting more sophisticated and efficient as the years went by. Finally, now that the mainstream programming community has begun to recognize the value of this automated technique, Java can become the first really widespread application of the technology those pioneers developed.
The Problem Imagine that you’re a programmer in a C-like language (probably not too difficult for you, because these languages are the dominant ones right now). Each time you create something, anything, dynamically in such a language, you are completely responsible for tracking the life of this object throughout your program and mentally deciding when it will be safe to deallocate it. This can be quite a difficult (sometimes impossible) task, because any of the other libraries or methods you’ve called might have “squirreled away” a pointer to the object, unbeknownst to you. When it becomes impossible to know, you simply choose never to deallocate the object, or at least to wait until every library and method call involved has completed, which could be nearly as long. The uneasy feeling you get when writing such code is a natural, healthy response to what is inherently an unsafe and unreliable style of programming. If you have tremendous discipline—and so does everyone who writes every library and method you call—you can, in principle, survive this responsibility without too many mishaps. But aren’t you human? Aren’t they? There must be some small slips in this perfect discipline due to error. What’s worse, such errors are virtually undetectable, as anyone who’s tried to hunt down a stray pointer problem in C will tell you. What about the thousands of programmers who don’t have that sort of discipline? Another way to ask this question is: Why should any programmers be forced to have this discipline, when it is entirely possible for the system to remove this heavy burden from their shoulders? Software engineering estimates have recently shown that for every 55 lines of production C-like code in the world, there is one bug. This means that your electric razor has about 80 bugs, and your TV, 400. Soon they will have even more, because the size of this kind of embedded computer software is growing exponentially. When you begin to think of how much C-like code is in your car’s engine, it should give you pause. Many of these errors are due to the misuse of pointers, by misunderstanding or by accident, and to the early, incorrect freeing of allocated objects in memory. Java addresses both of these—the former, by eliminating explicit pointers from the Java language altogether and the latter, by including, in every Java system, a garbage collector that solves the problem.
The Solution Imagine a run-time system that tracks each object you create, notices when the last reference to it has vanished, and frees the object for you. How could such a thing actually work? One brute-force approach, tried early in the days of garbage collecting, is to attach a reference counter to every object. When the object is created, the counter is set to 1. Each time a new reference to the object is made, the counter is incremented, and each time such a reference disappears, the counter is decremented. Because all such references are controlled by the language—as variables and assignments, for example—the compiler can tell whenever an object reference might be created or destroyed, just as it does in handling the scoping of local variables, and thus it can assist with this task. The system itself “holds onto” a set of root objects that are
Java Unleashed
Page 449
considered too important to be freed. The class Object is one example of such a V.I.P. object. (V.I.O.?) Finally, all that’s needed is to test, after each decrement, whether the counter has hit 0. If it has, the object is freed. If you think carefully about this approach, you will soon convince yourself that it is definitely correct when it decides to free anything. It is so simple that you can immediately tell that it will work. The low-level hacker in you might also feel that if it’s that simple, it’s probably not fast enough to run at the lowest level of the system—and you’d be right. Think about all the stack frames, local variables, method arguments, return values, and local variables created in the course of even a few hundred milliseconds of a program’s life. For each of these tiny, nano-steps in the program, an extra increment—at best—or decrement, test, and deallocation—at worst—will be added to the running time of the program. In fact, the first garbage collectors were slow enough that many predicted they could never be used at all! Luckily, a whole generation of smart programmers has invented a big bag of tricks to solve these overhead problems. One trick is to introduce special “transient object” areas that don’t need to be reference counted. The best of these generational scavenging garbage collectors today can take less than 3 percent of the total time of your program—a remarkable feat if you realize that many other language features, such as loop overheads, can be as large or larger! There are other problems with garbage collection. If you are constantly freeing and reclaiming space in a program, won’t the heap of objects soon become fragmented, with small holes everywhere and no room to create new, large objects? Because the programmer is now free from the chains of manual deallocation, won’t they create even more objects than usual? What’s worse, there is another way that this simple reference counting scheme is inefficient, in space rather than time. If a long chain of object references eventually comes full circle, back to the starting object, each object’s reference count remains at least 1 forever. None of these objects will ever be freed! Together, these problems imply that a good garbage collector must, every once in a while, step back to compact or to clean up wasted memory.*
*NOTE Compaction occurs when a garbage collector steps back and reorganizes memory, eliminating the holes created by fragmentation. Compacting memory is simply a matter of repositioning objects one-by-one into a new, compact grouping that places them all in a row, leaving all the free memory in the heap in one big piece. Cleaning up the circular garbage still lying around after reference counting is called marking and sweeping. A mark-and-sweep of memory involves first marking every root object in the system and then following all the object references inside those objects to new objects to mark, and so on, recursively. Then, when you have no more references to follow, you “sweep away” all the unmarked objects, and compact memory as before. The good news is that this solves the space problems you were having. The bad news is that when the garbage collector “steps back” and does these operations, a nontrivial amount of time passes during which your program is unable to run—all its objects are being marked, swept, rearranged, and so forth, in what seems like an uninterruptible procedure. Your first hint to a solution is the word “seems.” Garbage collecting can actually be done a little at a time, between or in parallel with normal program execution, thus dividing up the large time needed to “step back” into numerous so-small-you-don’t-notice-them chunks of time that happen between the cracks. (Of course, years of smart thinking went into the abstruse algorithms that make all this possible!) One final problem that might worry you a little has to do with these object references. Aren’t these “pointers” scattered throughout your program and not just buried in objects? Even if they’re only in objects, don’t they have to be changed whenever the object they point to is moved by these procedures? The answer to both of these questions is a resounding yes, and overcoming them is the final hurdle to making an efficient garbage collector. There are really only two choices. The first, brute force, assumes that all the memory containing object references needs to be searched on a regular basis, and whenever the object references found by this search match objects that have moved, the old reference is changed. This assumes that there are “hard” pointers in the heap’s memory—ones that point directly to other objects. By introducing various kinds of “soft” pointers, including pointers that are like forwarding addresses, the algorithm improves greatly. Although these brute-force approaches sound slow, it turns out that modern computers can do them fast enough to be useful.*
Java Unleashed
Page 450 *NOTE
You might wonder how the brute-force techniques identify object references. In early systems, references were specially tagged with a “pointer bit,” so they could be unambiguously located. Now, so-called conservative garbage collectors simply assume that if it looks like an object reference, it is—at least for the purposes of the mark and sweep. Later, when actually trying to update it, they can find out whether it really is an object reference or not. The final approach to handling object references, and the one Java currently uses, is also one of the very first ones tried. It involves using 100 percent “soft” pointers. An object reference is actually a handle, sometimes called an “OOP,” to the real pointer, and a large object table exists to map these handles into the actual object reference. Although this does introduce extra overhead on almost every object reference (some of which can be eliminated by clever tricks, as you might guess), it’s not too high a price to pay for this incredibly valuable level of indirection. This indirection allows the garbage collector, for example, to mark, sweep, move, or examine one object at a time. Each object can be independently moved “out from under” a running Java program by changing only the object table entries. This not only allows the “step back” phase to happen in the tiniest steps, but it makes a garbage collector that runs literally in parallel with your program much easier to write. This is what the Java garbage collector does.*
*WARNING You need to be very careful about garbage collection when you’re doing critical, real-time programs (such as those mentioned in Chapter 37 that legitimately require native methods)—but how often will your Java code be flying a commercial airliner in real-time, anyway?
Java’s Parallel Garbage Collector Java applies almost all these advanced techniques to give you a fast, efficient, parallel garbage collector. Running in a separate thread, it cleans up the Java environment of almost all trash (it is conservative), silently and in the background, is efficient in both space and time, and never steps back for more than a small amount of time. You should never need to know it’s there. By the way, if you want to force a full mark-and-sweep garbage collection to happen soon, you can do so simply by calling the System.gc() method. You might want to do this if you just freed up a majority of the heap’s memory in circular garbage, and want it all taken away quickly. You might also call this whenever you’re idle, as a hint to the system about when it would be best to come and collect the garbage. This “meta knowledge” is rarely needed by the system, however. Ideally, you’ll never notice the garbage collector, and all those decades of programmers beating their brains out on your behalf will simply let you sleep better at night—and what’s wrong with that?
The Security Story Speaking of sleeping well at night, if you haven’t stepped back yet and said, “My Goodness! You mean Java programs will be running rampant on the Internet?” you better do so now, for it is a legitimate concern. In fact, it is one of the major technical stumbling blocks (the others being mostly social and economic) to achieving the dream of ubiquity and code sharing mentioned earlier in this chapter.
Why You Should Worry Any powerful, flexible technology can be abused. As the Net becomes mainstream and widespread, it, too, will be abused. Already, there have been many blips on the security radar screens of those of us who worry about such things, warning that (at least until today), not enough attention has been paid by the computer industry (or the media) to constructively solving some of the problems that this new world brings with it. One of the benefits of solving security once and for all will be a flowering unseen before in the virtual communities of the Net; whole new economies based on people’s attention and creativity will spring to life, rapidly transforming our world in new and positive ways. The downside to all this new technology, is that we (or someone!) must worry long and hard about how to make the playgrounds of the future safe for our children, and for us. Fortunately, Java is a big part of the answer.*
*NOTE For more information about security issues with Java, please see Chapter 40, “Java Security.”
Java Unleashed
Page 451
Why You Might Not Have To What gives me any confidence that the Java language and environment will be safe, that it will solve the technically daunting and extremely thorny problems inherent in any good form of security, especially for networks? One simple reason is the history of the people, and the company, that created Java. Many of them are the very smart programmers referred to throughout the book, who helped pioneer many of the ideas that make Java great and who have worked hard over the decades to make techniques such as garbage collection a mainstream reality. They are technically capable of tackling and solving the hard problems that need to be solved. In particular, from discussions with Chuck McManis, one of Java’s security gurus, I have confidence that he has thought through these hard problems deeply, and that he knows what needs to be done. Sun Microsystems, the company, has been pushing networks as the central theme of all its software for more than a decade. Sun has the engineers and the commitment needed to solve these hard problems, because these same problems are at the very center of both its future business and its vision of the future, in which networking is the center of everything—and global networks are nearly useless without good security. Just this year, Sun has advanced the state of the art in easy-to-use Internet security with its new SunScreen products, and it has assigned Whitfield Diffie to oversee them, who is the man who discovered the underlying ideas on which essentially all interesting forms of modern encryption are based. Enough on “deep background.” What does the Java environment provide right now that helps me feel secure?
Java’s Security Model Java protects you against potential “nasty” Java code via a series of interlocking defenses that, together, form an imposing barrier to any and all such attacks.*
*CAUTION Of course, no one can protect you from your own ignorance or carelessness. If you’re the kind of person who blindly downloads binary executables from your Internet browser and runs them, you need read no further! You are already in more danger than Java will ever pose. As a user of this powerful new medium, the Internet, you should educate yourself to the possible threats this new and exciting world entails. In particular, downloading “auto-running macros” or reading e-mail with “executable attachments” is just as much a threat as downloading binaries from the Net and running them. Java does not introduce any new dangers here, but by being the first mainstream use of executable and mobile code on the Net, it is responsible for making people suddenly aware of the dangers that have always been there. Java is already, as you will soon see, much less dangerous than any of these common activities on the Net, and can be made safer still over time. Most of these other (dangerous) activities can never be made safe. So please, do not do them! A good rule of thumb on the Net is this: Don’t download anything that you plan to execute (or that will be automatically executed for you) except from someone (or some company) you know well and with whom you’ve had positive, personal experience. If you don’t care about losing all the data on your hard drive, or about your privacy, you can do anything you like, but for most of us, this rule should be law. Fortunately, Java allows you to relax that law. You can run Java applets from anyone, anywhere, in relative safety. Java’s powerful security mechanisms act at four different levels of the system architecture. First, the Java language itself was designed to be safe, and the Java compiler ensures that source code doesn’t violate these safety rules. Second, all bytecodes executed by the run-time are screened to be sure that they also obey these rules. (This layer guards against having an altered compiler produce code that violates the safety rules.) Third, the class loader ensures that classes don’t violate name space or access restrictions when they are loaded into the system. Finally, API-specific security prevents applets from doing destructive things. This final layer depends on the security and integrity guarantees from the other three layers. Let’s now examine each of these layers in turn.
The Language and the Compiler The Java language and its compiler are the first line of defense. Java was designed to be a safe language.
Java Unleashed
Page 452
Most other C-like languages have facilities to control access to “objects,” but also have ways to “forge” access to objects (or to parts of objects), usually by (mis-)using pointers. This introduces two fatal security flaws to any system built on these languages. One is that no object can protect itself from outside modification, duplication, or “spoofing” (others pretending to be that object). Another is that a language with powerful pointers is more likely to have serious bugs that compromise security. These pointer bugs, where a “runaway pointer” starts modifying some other object’s memory, were responsible for most of the public (and not-so-public) security problems on the Internet this past decade. Java eliminates these threats in one stroke by eliminating pointers from the language altogether. There are still pointers of a kind— object references—but these are carefully controlled to be safe: they are unforgeable, and all casts are checked for legality before being allowed. In addition, powerful new array facilities in Java not only help to offset the loss of pointers, but add additional safety by strictly enforcing array bounds, catching more bugs for the programmer (bugs that, in other languages, might lead to unexpected and, thus, bad-guy-exploitable problems). The language definition, and the compilers that enforce it, create a powerful barrier to any “nasty” Java programmer. Because an overwhelming majority of the “Net-savvy” software on the Internet may soon be Java, its safe language definition and compilers help to guarantee that most of this software has a solid, secure base. With fewer bugs, Net software will be more predictable—a property that thwarts attacks.
Verifying the Bytecodes What if that “nasty” programmer gets a little more determined, and rewrites the Java compiler to suit his nefarious purposes? The Java run-time, getting the lion’s share of its bytecodes from the Net, can never tell whether those bytecodes were generated by a “trustworthy” compiler. Therefore, it must verify that they meet all the safety requirements. Before running any bytecodes, the run-time subjects them to a rigorous series of tests that vary in complexity from simple format checks all the way to running a theorem prover, to make certain that they are playing by the rules. These tests verify that the bytecodes do not forge pointers, violate access restrictions, access objects as other than what they are (InputStreams are always used as InputStreams, and never as anything else), call methods with inappropriate argument values or types, or overflow the stack. Consider the following Java code sample: public class VectorTest { public int array[]; public int sum() { int[] localArray = array; int sum = 0; for (int i = localArray.length; sum += localArray[i]; return sum; } }
--i >= 0;
)
The bytecodes generated when this code is compiled look something like the following: aload_0 Load this getfield #10 Load this.array astore_1 Store in localArray iconst_0 Load 0 istore_2 Store in sum aload_1 Load localArray arraylength Gets its length istore_3 Store in i
Java Unleashed
Page 453
A: iinc 3 -1 Subtract 1 from i iload_3 Load i iflt B Exit loop if < 0 iload_2 Load sum aload_1 Load localArray iload_3 Load i iaload Load localArray[i] iadd Add sum istore_2 Store in sum goto A Do it again B: iload_2 Load sum ireturn Return it*
*NOTE The excellent examples and descriptions in this section of the book are paraphrased from the tremendously informative security paper in the (alpha) Java release. I’d encourage you to read whatever the latest version of this document is in newer releases, if you want to follow the ongoing Java security story. Also, check out the next chapter for more information. Extra Type Information and Requirements
Java bytecodes encode more type information than is strictly necessary for the interpreter. Even though, for example, the aload and iload opcodes do exactly the same thing, aload is always used to load an object reference and iload used to load an integer. Some bytecodes (such as getfield) include a symbol table reference—and that symbol table has even more type information. This extra type information allows the run-time system to guarantee that Java objects and data aren’t illegally manipulated. Conceptually, before and after each bytecode is executed, every slot in the stack and every local variable has some type. This collection of type information—all the slots and local variables—is called the type state of the execution environment. An important requirement of the Java type state is that it must be determinable statically by induction—that is, before any program code is executed. As a result, as the run-time systems read bytecodes, each is required to have the following inductive property: given only the type state before the execution of the bytecode, the type state afterward must be fully determined. Given “straight-line” bytecodes (no branches), and starting with a known stack state, the state of each slot in the stack is therefore always known. For example, starting with an empty stack: iload_1 Load integer variable. Stack type state is I. iconst 5 Load integer constant. Stack type state is II. iadd Add two integers, producing an integer. Stack type state is I.*
*NOTE Smalltalk and PostScript bytecodes do not have this restriction. Their more dynamic type behavior does create additional flexibility in those systems, but Java needs to provide a secure execution environment. It must therefore know all types at all times, in order to guarantee a certain level of security. Another requirement made by the Java run-time is that when a set of bytecodes can take more than one path to arrive at the same point, all such paths must arrive there with exactly the same type state. This is a strict requirement, and implies, for example, that compilers cannot generate bytecodes that load all the elements of an array onto the stack. (Because each time through such a loop the stack’s type state changes, the start of the loop—“the same point” in multiple paths—would have more than one type state, which is not allowed.)
Java Unleashed
Page 454
The Verifier
Bytecodes are checked for compliance with all these requirements, using the extra type information in a .class file, by a part of the run-time called the verifier. It examines each bytecode in turn, constructing the full type state as it goes, and verifies that all the types of parameters, arguments, and results are correct. Thus, the verifier acts as a gatekeeper to your run-time environment, letting in only those bytecodes that pass muster.*
*WARNING The verifier is the crucial piece of Java’s security, and it depends on your having a correctly implemented (no bugs, intentional or otherwise) run-time system. As of this writing, only Sun is producing Java run-times, and its are secure. In the future, however, you should be careful when downloading or buying another company’s (or individual’s) version of the Java run-time environment. Eventually, Sun will implement validation suites for run-times, compilers, and so forth to be sure that they are safe and correct. In the meantime, caveat emptor! Your run-time is the base on which all the rest of Java’s security is built, so make sure it is a good, solid, secure base. When bytecodes have passed the verifier, they are guaranteed not to cause any operand stack under- or overflows, use parameter, argument, or return types incorrectly, illegally convert data from one type to another (from an integer to a pointer, for example), nor access any object’s fields illegally (that is, the verifier checks that the rules for public, private, package, and protected are obeyed). As an added bonus, because the interpreter can now count on all these facts being true, it can run much faster than before. All the required checks for safety have been done up front, so it can run at full throttle. In addition, object references can now be treated as capabilities, because they are unforgeable—capabilities allow, for example, advanced security models for file I/O and authentication to be safely built on top of Java.*
*NOTE Because you can now trust that a private variable really is private, and that no bytecode can perform some magic with casts to extract information from it (such as your credit card number), many of the security problems that might arise in other, less safe environments simply vanish! These guarantees also make erecting barriers against destructive applets possible, and easier. Because the Java system doesn’t have to worry about “nasty” bytecodes, it can get on with creating the other levels of security it wants to provide to you.
The Class Loader The class loader is another kind of gatekeeper, albeit a higher-level one. The verifier was the security of last resort. The class loader is the security of first resort. When a new class is loaded into the system, it is placed into (lives in) one of several different “realms.” In the current release, there are three possible realms: your local computer, the firewall-guarded local network on which your computer is located, and the Internet (the global Net). Each of these realms is treated differently by the class loader.*
*NOTE Actually, there can be as many realms as your desired level of security (or paranoia) requires. This is because the class loader is under your control. As a programmer, you can make your own class loader that implements your own peculiar brand of security. (This is a radical step: you may have to give the users of your program a whole bunch of classes—and they give you a whole lot of trust—to accomplish this.) As a user, you can tell your Java-capable browser, or Java system, what realm of security (of the three) you’d like it to implement for you right now, or from now on. As a system administrator, Java has global security policies that you can set up to help guide your users to not “give away the store” (that is, set all their preferences to be unrestricted, promiscuous, “hurt me please!”). In particular, the class loader never allows a class from a “less protected” realm to replace a class from a more protected realm. The file system’s I/O primitives, about which you should be very worried (and rightly so), are all defined in a local Java class, which means that they all live in the local-computer realm. Thus, no class from outside your computer (from either the supposedly trustworthy local network or from the Internet) can take the place of these classes and “spoof” Java code into using “nasty” versions
Java Unleashed
Page 455
of these primitives. In addition, classes in one realm cannot call upon the methods of classes in other realms, unless those classes have explicitly declared those methods public. This implies that classes from other than your local computer cannot even see the file system I/O methods, much less call them, unless you or the system wants them to. In addition, every new applet loaded from the network is placed into a separate package-like name space. This means that applets are protected even from each other! No applet can access another’s methods (or variables) without its cooperation. Applets from inside the firewall can even be treated differently from those outside the firewall, if you like.*
*NOTE Actually, it’s all a little more complex than this. In the current release, an applet is in a package “namespace” along with any other applets from that source. This source, or origin, is most often a host (domain name) on the Internet. This special “subrealm” is used extensively in the next section. Depending on where the source is located, outside the firewall (or inside), further restrictions may apply (or be removed entirely). This model is likely to be extended in future releases of Java, providing an even finer degree of control over which classes get to do what. The class loader essentially partitions the world of Java classes into small, protected little groups, about which you can safely make assumptions that will always be true. This type of predictability is the key to well-behaved and secure programs. You’ve now seen the full lifetime of a method. It starts as source code on some computer, is compiled into bytecodes on some (possibly different) computer, and can then travel (as a .class file) into any file system or network anywhere in the world. When you run an applet in a Java-capable browser (or download a class and run it by hand using java), the method’s bytecodes are extracted from its .class file and carefully looked over by the verifier. Once they are declared safe, the interpreter can execute them for you (or a code generator can generate native binary code for them using either the “just-in-time” compiler or java2c, and then run that native code directly). At each stage, more and more security is added. The final level of that security is the Java class library itself, which has several carefully designed classes and APIs that add the final touches to the security of the system.
The Security Manager SecurityManager is an abstract class that was recently added to the Java system to collect, in one place, all the security policy decisions that the system has to make as bytecodes run. You learned before that you can create your own class loader. In fact, you may not have to, because you can subclass SecurityManager to perform most of the same customizations. An instance of some subclass of SecurityManager is always installed as the current security manager. It has complete control over which of a well-defined set of “dangerous” methods are allowed to be called by any given class. It takes the realms from the last section into account, the source (origin) of the class, and the type of the class (stand-alone, or loaded by an applet). Each of these can be separately configured to have the effect you (the programmer) like on your Java system. For nonprogrammers, the system provides several levels of default security policies from which you can choose. What is this “well-defined set” of methods that are protected? File I/O is a part of the set, for obvious reasons. Applets, by default, can open, read, or write files only with the express permission of the user—and even then, only in certain restricted directories. (Of course, users can always be stupid about this, but that’s what system administrators are for!) Also in this protected set are the methods that create and use network connections, both incoming and outgoing. The final members of the set are those methods that allow one thread to access, control, and manipulate other threads. (Of course, additional methods can be protected as well, by creating a new subclass of SecurityManager that handles them.) For both file and network access, the user of a Java-capable browser can choose between three realms (and one subrealm) of protection:
unrestricted (allows applets to do anything) firewall (allows applets within the firewall to do anything) source (allows applets to do things only with their origin Internet host, or with other applets from there)
Java Unleashed
Page 456
local (disallows all file and network access) For file access, the source subrealm is not meaningful, so it really has only three realms of protection. (As a programmer, of course, you have full access to the security manager and can set up your own peculiar criteria for granting and revoking privileges to your heart’s content.) For network access, you can imagine wanting many more realms. For example, you might specify different groups of trusted domains (companies), each of which is allowed added privileges when applets from that group are loaded. Some groups can be more trusted than others, and you might even allow groups to grow automatically by allowing existing members to recommend new members for admission. (The Java seal of approval?) In any case, the possibilities are endless, as long as there is a secure way of recognizing the original creator of an applet. You might think this problem has already been solved, because classes are tagged with their origin. In fact, the Java run-time goes far out of its way to be sure that that origin information is never lost—any executing method can be dynamically restricted by this information anywhere in the call chain. So why isn’t this enough? Because what you’d really like to be able to do is permanently “tag” an applet with its original creator (its true origin), and no matter where it has traveled, a browser could verify the integrity and authenticate the creator of that applet. Just because you don’t know the company or individual that operates a particular server machine doesn’t mean that you want to mistrust every applet stored on that machine. It’s just that, currently, to be really safe, you should mistrust those applets. If somehow those applets were irrevocably tagged with a digital signature by their creator, and that signature could also guarantee that the applet had not been tampered with, you’d be golden. *
*NOTE Luckily, Sun is planning to do exactly that for Java, as soon as export restrictions can be resolved. Here’s a helpful hint of where the team would like to go, from the security documentation: “...a mechanism exists whereby public keys and cryptographic message digests can be securely attached to code fragments that not only identify who originated the code, but guarantee its integrity as well. This latter mechanism will be implemented in future releases.” Look for these sorts of features in every release of Java; they will be a key part of the future of the Internet! One final note about security. Despite the best efforts of the Java team, there is always a trade-off between useful functionality and absolute security. For example, Java applets can create windows, an extremely useful capability, but a “nasty” applet could use this to spoof the user into typing private password information, by showing a familiar program (or operating system) window and then asking an expected, legitimate-looking question in it. (The 1.0 release adds a special banner to applet-created windows to solve this problem.) Flexibility and security can’t both be maximized. Thus far on the Net, people have chosen maximum flexibility, and have lived with the minimal security the Net now provides. Let’s hope that Java can help tip the scales a bit, enabling much better security, while sacrificing only a minimal amount of the flexibility that has drawn so many to the Net.
Summary In this chapter we discussed the grand vision that some of us have for Java, and about the exciting future it promises. Under the hood, the inner workings of the virtual machine, the bytecode interpreter (and all its bytecodes), the garbage collector, the class loader, the verifier, the security manager, and the powerful security features of Java were all revealed. You now know almost enough to write a Java run-time environment of your own—but luckily, you don’t have to. You can simply download the latest release of Java—or use a Java-capable browser to enjoy most of the benefits of Java right away. I hope that Java ends up opening new roads in your mind, as it has in mine.
Java Unleashed
Page 457
Chapter 40 Java security The reaction to Java varies from person to person. Everyone is excited about the functionality that Java provides, but the security considerations polarize people. Either the reaction is “Oh no! you can’t do that” or “What is the big deal? It is just a new language.” In this chapter we hope to explain the “Oh No!” reaction by discussing in depth the security risk involved in Java. Then, you can decide if Java security is important for you. The answer to the first question, “Isn’t Java just another computer language?” is yes, but it’s how Java is used that opens up the question of security. Java interpreters make it easier than ever before to run new programs on your system; just point and click. (As the warning in Figure 40.1. shows, there are security concerns.) FIGURE 40.1. All Java applets in standalone windows come with a warning that shows they are risky. Once we have discussed the security issues we discuss the Java security mechanisms in detail.
Why Is Security an Issue with Java? There are no known security vulnerabilities in Java. It is too new, and the developers are working hard to make sure none arise. Experts are skeptical about how secure Java can ever be, however, given how it is planned to be used. We hope to explain these issues so that you can understand them and make your own choice. Consider the rate at which you currently introduce new programs to your system. If you are purchasing programs, you have to take a trip to the corner software market, and unless you are Mr. Gates, you are going to be able to purchase only a couple of titles. Now think of the time it takes to install the program. If you are surfing the net and bringing back non-Java programs, you may have to compile and then install them. Figuring in work and a social life, that is maybe five to ten programs a week. Compare that with Java. Once the number of Java applets builds up, you could run new programs on your system as fast as you can find them and click on them. Even figuring in a generous social life, that could easily be hundreds of programs a week. While the sheer numbers add to some of the risk, as we discuss latter, the biggest risk comes from the fact that you are not going to know who wrote all these programs. Not that you need to know the author of every program you run personally, but the easiest attack on your system is to get you to run a piece of hostile code. If someone with a mischievous and malevolent bent from, say, Dubuque, Iowa can place a program on a general server, thousands of people may download the program before the undesired side effect of wiping out the hard disk is found. If the perpetrator was unfortunate enough to leave his or her name in the source code, he will be thoroughly chastised. While a reprisal over the Internet is only mildly annoying, it won’t take too many malicious shareware incidents until the victims jump in their cars and drive to Dubuque with pitchforks and burning torches in hand (see Figure 40.2). When the program is anonymous, however, the real-world consequences are null. There is no place for the angry mob to congregate and vent its frustration. FIGURE 40.2. If you write malicious programs, beware of mobs of angry computer users seeking vengeance! Assuming evolution takes care of the stupid perpetrators of mayhem, that leaves the smart perpetrators who post their programs anonymously. They will be able to post their increasingly sophisticated pranks unhindered. Why? Because people love free software. If you tell me a program balances my check book and whitens my teeth for only $299.95 I will scorn it as cheap marketing. However, if the same program is free, I will download a copy ASAP, because, hey, you never know. Effective anonymity from anonymous remailers and by people posting from compromised accounts is going to be a part of the Internet for a long time. Corporations and other organizations have a reputation to maintain, and as a result, they police themselves and remove any malicious programs from their archives. But in the end you are faced with the decision while your mouse is hovering over some unknown applet, “Is this applet going to sauté my system?” Creating a hostile applet is not easy. A lot of effort has been placed in making Java as safe as possible. Many will argue that creating a hostile applet is not even possible. However, for those that have seen many a “secure” program fall under the sheer pressure of constant probing over time, the possibility of an application being secure when it is first released is extremely low.
Java Unleashed
Page 458
Next, instead of getting lost in the nuts and bolts of Java security right away, we are going to discuss some security concepts. Then we show you how Java security maps into these concepts.
Kinds of Attack The form of attack that is going to be used the most against a system using Java is the “Trojan horse” attack. The old “Cool, they left us this huge wooden horse with wheels. Let’s take it inside” becomes “Cool, here’s a Java applet that whitens teeth. Let’s run it.” The parallels between the two attacks are astounding. In both cases the attackers found it was too difficult to breach the outer defenses. In the Troy example, it was the city wall. In the computer example it was the connection from your system to the network. Just what can this applet do once it gets inside? There are three broad classifications of malicious behavior:
Disclosure of information Compromising the information integrity Denial of service We will take a moment to look at each one of these to see just what damage a piece of malicious code running on your system can do.
Denial of Service One of the easiest attacks to perform and one of the most difficult ones to stop is denial of service. Denial of service, in a nutshell, is that some resource of the system that you were depending on is no longer available to you. This can range from simply crashing the computer (where everything is unavailable) to just eating up all your CPU time and slowing your machine to a crawl. Say the applet begs you to store some configuration information on your hard disk. (It is true that applets can beg; see the “Security Manager” section.) You think, “I will only let it read and write the one file. What harm can that do?” The applet can now write one very large file, however, filling up your disk and preventing you from saving other work. Once you realize it was the rogue applet, it’s an easy fix, but tracking the problem down is very annoying. Consider this: once you click on that button you don’t know what that applet is doing. Is the applet really calculating a personal whitening formula based on your tooth enamel density or is it trying to calculate Pi to the last digit? (A classic denial of service attack—one used by countless science fiction heroes against malevolent computers.) Again, all this attack does is annoy you, but there is nothing in Java to prevent it. (Note that you can set the process priority for the applet thread low, but if you really believe the applet is doing useful work, you won’t.) *
*NOTE Some computer systems are highly important to organizations. Thus, denial of some service would be catastrophic. If, for example, the malicious program could clog up the computer used for the stock exchange, the costs to investors could be staggering. Crashing the whole computer from Java is going to be extremely difficult because a great deal of care has gone into creating and testing the Java exception handlers that catch faults in Java applets.
Compromising Information Integrity This is a more insidious attack (assuming you have valuable data on the system). What is it worth to you to make sure that information remains as accurate as it was when you entered it? Consider, for example, your personal budget. What if a hostile program modified the balance of your checking account so you thought you afford that copy of DOOM with smell-o-vision. Most likely the cost to you is some embarrassment and an overdraft checking penalty. For an organization, however, the costs could be much larger. The malicious code could modify the financial statements for a company’s prospectus, which could cause lawsuits for misrepresentation. If the malicious code modifies a patient’s record or the software that is used in a pacemaker, the consequences could be death. In terms of Java, every possible step has been taken to control that a Java applet cannot modify files on the client system.
Java Unleashed
Page 459
Disclosure of Information Another serious attack is disclosure of information. If the information is important to the success of an organization, consider what a competitor can do with that information. Corporate espionage is a real threat, especially from foreign companies where the legal reprisals are much more difficult to enforce. Assuming the computer is hooked to the Internet, it is as easy as pie for the malicious program to send the information home to the evil-doers. The program could use e-mail or communicate with an Internet server. In terms of Java, once again every possible precaution has been taken to ensure that an applet cannot read other files on the system. Targeting specific sensitive files is even more problematic, because it’s difficult for a malicious program to know what is sensitive and what isn’t.
The Information Bucket Now that you have seen some of the attacks, we describe how those attacks are stopped in traditional secure systems. One of the basic premises of computer security is to contain information so that you know where it came from, who has modified it, and where the information can go. Let’s call this concept an “information bucket.” The idea is to put all the related information into the same bucket, and then control who can access that bucket. See Figure 40.3 for some examples. The bucket has also been called the access class, security perimeter, or in DoD systems, the security level. FIGURE 40.3. Security starts with clearly labeling and storing information into separate buckets. For example, most computer systems have the concept of users. Each user gets his or her own little bucket to play in. All the user’s files reside in that bucket, and the user controls access to them. The system needs to control not only who can access a bucket, but which programs can run in that bucket and what those programs can access. Communication between programs must also be controlled. Programs could signal the information to other programs, which then write the information down in another bucket, as illustrated in Figure 40.4. So which programs can talk to each other must be strictly controlled as well. To summarize, a bucket has a set of programs and a set of files the programs can access. If there is no overlap between buckets, the system is very secure. No one could read or modify data, or consume system resources from another bucket. FIGURE 40.4. Communication between programs running in different buckets must be controlled as well. This would be the equivalent of giving everyone their own computer and not letting them talk to each other. This is obviously an overly restrictive way to solve the security problem. People need to share information. As long as everyone knows what resources are in their bucket and carefully share their information with others, the system is still relatively secure. The problem comes when the bucket boundaries have not been defined, people are not aware of them, or the bucket boundaries overlap. For example, if two different buckets can read and write the same file, information can flow between the two buckets. In other words, the bucket leaks. Leaky buckets are an indication of potential security problems. Combine leaky/overlapping buckets with a complex system where the number of buckets is very large and it becomes difficult to even discuss how secure the system is. Once you start opening up the system to allow information to flow between buckets, a new problem raises its head: transitive information flow between buckets. Consider if you give Sally information from your bucket. How do you know that Sally is going to keep your information secret? She may give it to Tom, and because Tom doesn’t know any better he gives it to Polly, your arch enemy (see Figure 40.5). FIGURE 40.5. Once information is allowed to move between buckets it is difficult to know were it is going to stop. As we said earlier, some information needs to be shared between buckets. That is, some leaks are necessary. Special programs can be set up that monitor data being transferred between buckets to ensure only the proper data is leaving the bucket. These programs are trusted to only let the proper data through. It is their job to make sure the entire bucket is not drained. Writing a program that comes with a guarantee is a difficult thing to do. The easiest approach is to make the trusted program simple so that the program can be analyzed for correctness. So, you can get a rough measure of the security of a system by considering these three factors:
Java Unleashed I. II. III.
Page 460
how many buckets there are how much overlap there is between buckets how trusted the programs are protecting those data channels (if information is allowed to move between buckets)
The more overlap between buckets, the more information can flow through the system, and the more analysis is required to ensure the system is secure. The mechanism that enforces the separation between buckets must also be scrutinized, because it is one of those trusted programs. If information flows between the buckets in ways that was not intended, the system has a covert channel. If any one of the security components that are discussed in the “Security Protection” section has a flaw your system could be compromised. Remember, your Java browser is your friend; it is the only thing protecting you from those potentially hostile applets. Another consideration for the security of a system is “Are there any exceptions to the bucket policy?” For example, many systems let an administrator play around in any bucket on the system. The problem is not that we don’t trust administrators, but rather that it gives attackers an opening. Now, rather than trying to find a covert channel to peek into another bucket, the attackers try to trick the system into thinking they are the administrator.
Java and the Buckets Let’s see how Java relates to the information bucket concept. Java has several different kinds of buckets.
Namespace Method Interfaces Inter-Process Communication Memory Namespace Buckets The first kind of buckets is the namespace buckets. There are two types of namespace buckets: local and network. The rationale for this split is that all applets originating from the localsystem can be given special privileges because the administrator checked them out before installing them. The network bucket is divided up into smaller buckets: one for each network address. (See Figure 40.6.) FIGURE 40.6. The Java namespace is divided into local and network buckets, with network being further subdivided into a names space for each network address. This means that applets that are from different classes but from the same network site are in the same bucket. This raises a red flag because we have different applets sharing the same bucket. Obviously, the applets are coming from the same network site, so you may think they should be compatible. However, if the applets are from a general server such as a university where a number of people can download applets, or a large service provider where you can rent space for a Web page (such as America Online), the applets could be written by different authors with very different intents. (See Figure 40.7.) FIGURE 40.7. Applets loaded from the same network location are in the same nameespace, even though the applets could be written by a diverse set of authors. An applet may be able to trick another applet at the same site into using its code rather the code of an applet from a different site. Note that the internal class definitions and library calls are always checked first so they can never be replaced by code from an outsider applet.
Method Interface Buckets The second kind of bucket in Java is the object-oriented interfaces to the Java applets. Each applet can have a public interface that other applets can call. If an applet declares a method as private, it is the only applet that can access the method (see Figure 40.8). The problem with this approach is the buckets are designed by the applications builders. Not all of them are conscientious or trustworthy.
Java Unleashed
Page 461
In fact, many applets and code fragments are going to be written by people not concerned with security. People will also integrate applets and code fragments together as they need them. Who knows what the resulting security of these franken-applets will be. All you know for sure is that the applet is contained into one of the namespace buckets. FIGURE 40.8. Java methods can be public or be hidden from other applets by being declared private.
Interprocess Communication Bucket Who can the applet talk to? An applet can almost always talk to one server on the Internet. Thus, there is almost always a channel for the applet to disclose information. Access to other applets or programs is strictly controlled. Access to the operating system is also tightly controlled. This can be used to prevent the applet from learning your name or other system attributes.
Memory Bucket When it all comes down to it, the only real buckets are the chunks of memory that the Java system manipulates. Extra precautions have been taken to ensure applets cannot poke around in memory that has not been allocated to them. This is done by strictly controlling access to the pointer type. Pointers cannot be manipulated by the applet. If an applet could modify a pointer directly, they could point the pointer off into the memory of another applet or even a different namespace bucket. This would be a serious covert channel. One denial-of-service attack that works against Java is to repeatedly create new objects until all the available memory is gone. This is why people concerned about security are worried about Java. Although Java separates data into different buckets, some buckets overlap. Also, a great deal of trust must be placed in code that controls pointers.
Improving Java Security Outside of Java Now that you have an idea of what some of the vulnerabilities are, we look at what can be done outside of Java to protect against them. As pointed out, some buckets overlap, but the overlap is difficult to exploit. Thus, the attacks to be concerned about are covert channels and attacks that use the administrator interface. The covert channels could only be exploited by malicious code. If you can block malicious code you can prevent the covert channels from being exploited. Detecting malicious code is a challenging job, but that is what the Java Verifier does. We discuss the Java Verifier, with its strengths and weaknesses, in the “Security Protection” section.
Digital Signatures Another alternative to avoid malicious code is to only run applets that are written without malicious intent. Obviously you can’t quickly and accurately determine the intent of a program. However, if you can be one-hundred percent guaranteed that the applet was written by a person you trust, you know the program was not written with malicious intent. However, you must also guarantee that the code was not modified after your trusted friend finished it. Crypto-seals or digital signatures allow this by detecting if any changes have been made once your friend signed the applet. Digital signatures even detect if the applet was modified en route to your workstation from the server.
Software Engineering Simple errors are a common source of security vulnerabilities. You know your trusted friend didn’t put in any intentional security violations, but did he or she put one in accidentally? Consider this scenario. Bobby’s friend Peter writes an applet that turns scanned images into electronic postcards. Peter makes the applet publicly available on the local school server. Greg notices that Peter made the getImage() method public. So Greg writes an applet that puts a canceled stamp on the postcard. The idea is that Greg wants people to use his applet at the same time. Greg’s applet also copies the image back to Greg’s server. Bobby uses the applet to send his mom a postcard of her with a beehive hairdo. Mom retaliates by scanning in that picture of Bobby when she made him be the dresser dummy for his sister’s prom dress: green taffeta, ribbons and all.
Java Unleashed
Page 462
Now Greg has just acquired a very interesting, and potentially profitable image. Good software engineering on Peter’s part could have prevented this unfortunate incident. Remember if the software is free, you are getting what you paid for in terms of software engineering. If you think this example is contrived, think again. It is exactly these kinds of vulnerabilities that were found in the early versions of the HotJava web browser system libraries1. One of the biggest effects on good software engineering is going to be the near random combination of all the applets as people borrow prewritten code fragments and add them to their applications. In the past, people have focused on making their programs work, not on making them secure. There is no reason to believe that will change because of Java. One can hope that solid standard libraries evolve, but it will take time.
Risk Analysis We have talked about some of the potential vulnerabilities, but what does it mean to you? Are you at risk when using Java? To figure out your risk you must answer several questions:
Do I have anything to protect from disclosure or unwanted modification? Is my system so critical that a denial of services attack must be avoided? Is anyone out there trying to get me or my information? What is the cost of a compromise? What does it cost not to use Java? If you do not have anything to protect on your system, running Java browsers are not a problem because you have nothing to lose. If your system is running software for remote pacemakers, don’t run Java! All other cases fall somewhere between these two extremes and require you to quantify some values. First, is anyone out to get you? The answer to this is always yes. The propagation of the myriad of computer viruses clearly indicates that there are always going to be people who want to do mischief. As a rule these people have been annoying and random, so they are not really a high-grade threat. If, however, you are a corporation or large organization, you have information to protect. Competitors are not always going to play by the rules. Now you must figure how much it is going to cost you if your data is compromised and weigh it against the cost of not running Java. Most people running personal computers out of their homes will probably decide that Java is worth the risk because it saves them time downloading and running software and saves them money (as long as you remove potentially embarrassing data such as things that might involve green taffeta or powder blue leisure suits). Note, if the system is a home business and a personal system, the home business is at risk. Organizations are faced with much tougher choices.
Security for Organizations Java is a wonderful tool, but as we have pointed out there are lots of security considerations. These considerations are much more important for organizations. As organizations become more interconnected, their need to be connected grows. The trend in the past for many organizations has been to introduce the technology without analyzing its effect on the organization. As a result, the ways that information flows through an organization grows. In terms of the bucket model, it’s like just having one big bucket. Going back to less connected systems with fewer functions is almost impossible for these organizations. Nowhere is this more apparent than in Java. Java brings easy-to-use interfaces that run on a wide variety of platforms. What organization isn’t going to look to Java to solve some of its problems? The problem with using Java in an organization is that organizations have something to protect. They could lose face, assets, data integrity, or worse. On a personal level the risks are much smaller.
Java Unleashed
Page 463
Organizations have to protect themselves not only from outsiders but also from honest mistakes by their own people. As you will see in the next section, many of the final security decisions reside with the end user. In a large organization you can be guaranteed that some user is going to make a bad decision sometime, no matter how much training he or she has, and no matter what clever warning posters the company puts up. Organizations can have several alternatives for reducing the risk in how they use Java.
Isolating the Organization’s Java Capability The first alternative is to isolate the corporate network from the Internet (see Figure 40.9). The firewall must stop all requests for Java applets. If someone needs an applet from the Internet, they can request an administrator to go get it. The administrator evaluates the applet, tests it, and then installs it on the internal net. In this approach, the corporate network is treated as one big information bucket. This reduces the risk of getting a malicious applet for the following reasons:
The number of applets is greatly reduced because only needed applets are brought in The administrator only gets applets from a trusted source The administrator reviews the applet The threat is not eliminated, however, because the malicious code inside an applet could be cleverly hidden. The malicious Java applet may still be able to e-mail home. This solution severely limits Java and only reduces the risk, but it may be suitable for some organizations. FIGURE 40.9. One solution for improving Java security for organizations is to not allow users to download applets from the Internet. All applets are downloaded and checked by a Java administrator.
Network Separation Another approach is completely separating the corporate network from the Internet. Internet workstations would be on a separate net (see Figure 40.10). When Java is needed, employees go to the Internet workstations that have full Java access. This approach is inconvenient, but very secure because there is no sensitive data on the Java net. FIGURE 40.10. Another approach to improve security is to keep Java off the internal net entirely. Java is still available, but it is inconvenient.
Advanced Firewall Protection A hot topic on the firewalls mailing list has been about whether in the future Java applets could be checked at the firewall. The check would be to ensure that the applet had been digitally signed by someone the organization trusts. If the applet was not signed, or if the applet was tampered with, the applet is rejected (see Figure 40.11). The advantage of this approach is that the approach is much easier to administer. *
*NOTE For those interested in joining the firewall mailing list write a message to [email protected] that contains the line subscribe firewalls user@host. FIGURE 40.11. In the future, firewalls could be required to ensure that all applets have been digitally signed before being allowed into the internal network. Users may still sneak applets past the firewall, but the Java verify could be modified to require valid digital signatures as well. Then, even local applets must be approved before they can be executed. Hopefully this functionality will be available soon.
So, Is Java Secure? Java has no inherent design flaws that make it insecure. However, there are several places where security could be improved. Also, a great deal of trust must be placed in the Java browser to ensure that the applet is properly contained. Even one small implementation error could compromise the entire system. As a result, there is always the potential that the next applet you bring inside is a Trojan horse that exploits a newly-discovered vulnerability in the Java browser.
Java Unleashed
Page 464
Every precaution has been taken to make Java as safe as possible, but in the end everyone must weigh the risks and decide for themselves. In the next section we discuss the mechanics of Java security and provide some important tips for increasing the security of using Java at your site.
Security Protection in Java What follows is a generic description of the security measures available to all implementations of a Java Interpreter (that is, any software capable of executing Java applets). The section discusses which components of the Java interpreter are performing the security checks and what those checks are. Discussions of what security measures are available for your favorite Java Interpreter are given later in the chapter. *
*CAUTION These security measures are available to Java interpreters, but availability does not guarantee use (much like the availability of mouthwash does not guarantee its use). Even if the security measures are used, there is no guarantee that they were used correctly. Readers are sternly cautioned to choose only reliable Java Interpreters that can be trusted to execute Java applets correctly. The security measures applied to a class are determined by the origin of the class. The built-in classes that come with the Java interpreter have fewer checks applied to them because they are assumed to be correct and non-malicious. All other classes go through a much more stringent set of checks. Figure 40.12 shows the security checks each class must go through depending on the origin of the class. FIGURE 40.12. An overview of security checks. The built-in classes are subjected to a small subset of the available security checks (path 1). Classes loaded from the netwrok (path 2) and classes from the local system (path 3) are subjected to more security checks. Figure 40.12 also introduces the concept of a security perimeter. For our purposes, we’ll consider the security perimeter to be the line that separates those things you can control from those things you can’t. First we discuss the path followed by built-in classes that make up the system.
Security Checks for Built-In Classes For built-in classes, the assumption is that the code is part of the system and therefore must be trusted. Because the code is trusted, many of the checks that could be applied at load- or runtime are not applied, as shown in Figure 40.12. Note that because these classes really are part of the Java interpreter, many of them must be loaded before any checks could be applied anyway. The built-in classes are those classes stored on your local file system that are located in any of the directories in your CLASSPATH. Your CLASSPATH is an environment variable that contains a colon separated list of directories. Your exact CLASSPATH is userand system-dependent, but here is an example: CLASSPATH=”/usr/local/java/classes/:/usr/share/java/classes One of those directories contains the built-in class file. Again, the exact name of this file is system-dependent, but some of the names used for this file include moz2_0.zip or lib/classes.zip. *
*CAUTION Only install vendor-supplied classes as built-in Java classes. Never, ever, store classes of unknown origins in any directory on your CLASSPATH. Putting an unknown class in this directory turns off many of the security checks that are protecting you from malicious code. The built-in classes are only checked by the following components:
Java compiler File system loader Security Manager (optional)
Java Unleashed
Page 465
Java Compiler The Java compiler is a big step toward making sure that the any Java class does not contain any security violations. As shown in Figure 40.13, the Java compiler turns Java source code file into a byte code file. The Java compiler ensures that the safety rules of the Java language are obeyed. FIGURE 40.13. The Java compiler turns Java source code into byte code if the code meets the Java Language safety requirements. Some of the numerous safety mechanisms in the Java compiler are as follows:
Pointers does not have pointers directly to memory. Instead pointers are a special type that must be de-referenced by the Java system to access the data. Java pointers can be thought of as keys that let you access the data associated with the key—without the key you cannot access the data. The Java compiler checks to make sure pointers are not manipulated in any way. Java provides no pointer arithmetic mechanisms and does not allow typecasts into the Java pointer type. This prevents a malicious program from making a key. Memory and garbage collection programmers do not allocate and manage memory. All memory management is handled for them. The Java Interpreter decides where to bind the class fields and class methods to memory, not the Java compiler. The Java Interpreter provides no mechanism to discover the results of this binding. collection of unused memory is automatic. This means that programmers do not have to explicitly free up memory they are no longer using. This provides protection against some types of memory object reuse. Array bounds checking classes are not allowed to access beyond the end of an array. Arrays remain the same size from creation to destruction, and the runtime prevents indexing overruns and underruns. Strict type-checking checking is enforced to ensure that the compile time type and the runtime type of variables are compatible. Casts are checked to ensure validity. No casts are allowed to the internal Java pointer type. (See the previous Pointers bullet for more information.) Enforced access modifiers access to an object must go through its public interface. This ensures that an object’s private methods or private data stays private. Implementation Independent Java language specification does not allow “implementation dependent” results. Everything occurs in a specified order that does not vary between Java Interpreters. Thus code behaves the same on all platforms. File System Loader Built-in classes are protected to a degree by the file system loader. As shown in Figure 40.14, the file system loader performs one security relevant function. It has the responsibility of placing the built-in classes in a namespace that is separate from the namespace used by other classes. This provides an extra level of protection from unauthorized manipulation by classes. FIGURE 40.14.
File system loader checks for built-in classes.
Java Unleashed
Page 466
Security Manager for Built-In Classes Built-in classes need not be subjected to any Security Manager checks. Any use of the security features provided by the Security Manager is completely voluntary. Built-in classes are usually supplied by the vendor and require access to system resources without interference from the Security Manager. One reason for this is because the vendor implements the SecurityManager class and puts it in the built-in class file that is loaded with the Java Interpreter. Thus, the Security Manager checks cannot be applied to loading the SecurityManager class, because there is no Security Manager available yet to apply the checks! Those classes that you write and compile and store in a directory on your CLASSPATH are not forced to call the Security Manager. Thus, you are turning off an important set of security checks for your classes, something we don’t recommended. For now, it’s sufficient to say that built-in classes are allowed to do the following:
read files write files load libraries on the client run new processes on the client delete files (by running processes that in turn delete the file) cause the Java Virtual Machine to exit connect to port(s) on the client connect to port(s) on 3rd party host create popup windows without the warning border Security Checks for Non-Built-In Classes All the classes that are not built-in go through a series of security checks as seen in Figure 40.15. All the classes loaded from the network are non-built-in classes. Most of the classes loaded from the local system are non-built-in classes as well. FIGURE 40.15. Security checks for non-built-in classes. Network classes do not go through a Java compiler that is under your control. All Java classes are compiled by an approved, trusted Java compiler, or are they? Because you control the Java compiler, you hopefully are using a good compiler that you can trust to safely compile Java byte code. When you get a Java applet from the net you have no control over the compilation process. The individual producing the applet may have used a bad compiler, or the byte code may have been tampered with after it was produced. Because the Java source and Java compiler are outside of your security perimeter, you can rely on none of the security related mechanisms built into the language or the compiler. As Figure 40.15 shows, classes under your control follow path three through the compiler you control. However, most of the classes you load follow path two from the Internet (where you do not control the compiler). From a security point of view, it is the bytecode that matters, not the Java source. A benign example is when a buggy Java compiler is used and generates “illegal” pointer arithmetic bytecodes. A malicious example is when an attacker specifically modifies either the compiler or the bytecodes to produce a class that no legitimate Java compiler could ever generate. These are the practical implications from having the Java source and Java compiler outside of your security perimeter: You can’t prevent either the buggy compiler nor the malicious compiler from producing bytecodes that could cause your computer system to perform unauthorized actions. Fortunately, the Java interpreter does elaborate checks before loading and executing a class. Each Java class is checked by the following components:
The Java Verifier The Class loader
Java Unleashed
Page 467
The Security Manager We discuss each component in turn.
Verifier The Java Verifier is used to check the bytecode to make sure that the safety features of the Java language are followed. As Figure 40.16 shows, the Java Verifier takes byte code, checks it, and only passes the code through if the checks are passed. FIGURE 40.16. Java Verifier analyzes the byte codes to ensure that the safety features of the Java language are followed. The purpose of the Verifier is to allow the Java Interpreter to retain three important properties. These three properties follow:
I. II. III.
Applets do not forge pointers, and always use pointers correctly Applets must use pointers (that is, no alternative access mechanisms exist) All access restrictions are properly followed
These three properties combine to enable us to make strong security-relevant statements about the system. For example, consider an Applet credit of the type Credit.class that stores your credit card number in one of its private fields, called ccnumber. The securityrelevant assertion we make is this: “No other applet can access credit.ccnumber.” One way to show this assertion is true is to examine the ways another applet, attack of the type Attack.class, could attempt to access credit.ccnumber. Some sample attacks are given in Table 40.1. Table 40.1. These potential attack techniques are stopped by the Java Verifier.*
*
*Property that Prevents the Attack
Potential Attack Method attack simply accesses credit.ccnumber
attack creates a class FakeCredit.class which also has a field ccnumber, but makes ccnumber in FakeCredit.class public. Then, attack tries something like this: stolencc =((FakeCredit)credit).ccnumber; attack.class creates a memory pointer that points directly to the storage that credit.ccnumber uses.
#3—All access restrictions are properly followed. Because Credit.class declared ccnumber to be type private, access is denied. #1—Applets cannot forge pointers and must always use pointers correctly. The attempt to recast the pointer credit to a pointer of type FakeCredit is not allowed. #2—Applets must use pointers. Because no alternative mechanisms exist to access an object’s fields, the creation of a “memory pointer” will not succeed in accessing credit.ccnumber.
The first implication is that when your credit card number is stored in a private location, it is protected from all kinds of attacks: unauthorized disclosure, unauthorized modification, and so on. The second implication is that the Java Interpreter itself is protected as well. The Java system itself (including and especially those parts of the Java system which perform security related functions, e.g. the Security Manager) are protected from malicious modification because of these three properties. Other aspects of the Java Interpreter help ensure these three properties remain true. The principle protection mechanism concerns Java’s memory-management system. Of primary importance is that bytecodes do not use pointers to a memory location. Instead, the bytecodes use “capabilities” or “handles” to denote access requests. These “capabilities” are resolved to actual memory locations only by the Java Interpreter. Property #2 is a direct consequence of the fact that the Java Interpreter provides instructions of the form “Load this pointer (Capability, for example) into this register.” It does not provide instructions of the form “Load the contents of this memory location into this register.” Another memory-management protection mechanism results from the fact that the Java Interpreter decides where in physical memory to place the class (or pieces of the class.) Thus, the declaration of a class need not have any bearing on the physical location of the class in memory.
Java Unleashed
Page 468
In terms of the credit card example above, our attack.class faces two distinct problems if it is going to “forge a pointer” to credit.ccnumber. First, using the only starting point available to it (the capability we’ve been referring to as credit), it must determine the memory location of credit.ccnumber. Remember, though, the Java Interpreter has incredible flexibility in determining memory locations. Gone are the days where a simple “memory location of credit plus eight equals the memory location of credit.ccnumber.” Second, if attack.class does somehow accomplish this feat, and learns that the memory location of credit.ccnumber is “0x2BAD,” it still hasn’t won. The Java Virtual Machine provides no mechanism to perform the function “Load the contents of memory location 0x2BAD.” To succeed, attack.class must construct a request in the form of a Capability such that the same memory location is accessed, but where the access control mechanisms are bypassed. (The first row in Table 40.1 shows what happens when you take the obvious approach and simply refer to credit.ccnumber in a normal manner; the access control mechanism prevents it.) It is becoming difficult to imagine how our attack.class might succeed. It still may be possible for attack.class to access or modify credit.ccnumber; the memory management system just makes it very unlikely that a “forged memory pointer” method is going to succeed. Details on the Verifier can be found in “Low Level Security in Java.”2 A summary of that paper is presented here. The Verifier performs four separate passes when examining a Java class to be loaded. The first pass is mainly a syntactic check. It ensures that the class “magic number” is present in the first part of the class file. It also ensures that the class file is neither too short nor too long. The second pass consists of all the verification that can be accomplished without looking at the class method bytecodes. This pass ensures that every class has a superclass and that the constant pool is constructed and referenced properly. The third pass consists of checking the bytecodes of each method in the class. A “Data-flow” analysis is performed on each method to ensure various invariants hold true regarding the stack and registers. At the end of this check, it is known that no stack overflows or underflows can occur. Also, each method call is checked to ensure that the correct number and type of arguments are used. The fourth pass consists of those tests that have been delayed from the third pass due to efficiency reasons. If possible, pass three avoids actually loading the class file. The checks in the fourth pass occur the first time a class is referenced by the Java Interpreter. Further checks occur the first time a field or method within the class is called. These checks ensure that the field or method exists, that all the types match, and that the current execution context has access to the field or method.
Class Loader After incoming code has been checked by the Verifier, the protections in Java class loader are invoked. The primary function of the class loader is to create and maintain separate namespaces. As shown in Figure 40.17, the class loader begins with a verified Java class, and performs namespace separation on it in preparation for execution. FIGURE 40.17. Class loader security checks for built-in classes. Each network source is given its own namespace and built-in classes are given their own namespace to share. Additional protections associated with this level are provided by using a safe method to search the namespaces for a class reference. When any class references another class, the file system source namespace is searched first (that is, the built-in classes are searched first). If no match is found, the namespace of the referencing class is searched. If still no match is found, an error is returned. By fixing the order of namespace searches in this manner, it makes it impossible for network source classes to override any filesystem source class. It also makes it impossible for a file system source class to access a network source class by accident. *
*CAUTION The surprising implication of namespaces separated by network source creates the strange situation where you should distrust your immediate neighbors more than anyone else! This is because your applet is put into the same namespace as all the other applets loaded from the same server that loaded your applet. This feature supports the applet writer who wants to split the applet into one or more classes: each class is in the same namespace as the others, and thus have access to each other. But this same feature introduces a security vulnerability. If you fail to take proper precautions in your code (for example, you make Credit.ccnumber public instead of private), your applet is vulnerable to attack from other applets. For example, if Attack.class is loaded from the same server as Credit.class, the simple access of credit.ccnumber succeeds. This vulnerability is of special concern to those Java class providers who are using some sort of shared host to serve applets. For example, all aol.com applets, no matter who writes them, are placed in the same namespace. Programmers beware!
Java Unleashed
Page 469
Security Manager Security Checks for Classes The Security Manager is used to provide a flexible access control mechanism for all classes. Any time a class needs to access a system resource, such as a file, the Security Manager is invoked. For each request the storage manager returns if the access is allowed or denied. As shown in Figure 40.18, the Security Manager acts a guardian for the system resources. FIGURE 40.18. Security Manager guards system resources from Java classes. Java classes can only access system resources if the Security Manager approves. Any time a non-built-in class accesses a system resource it must first ask permission from the Security Manager. Classes are prevented from bypassing the Security Manager because none of the system resources are available to ordinary classes. If a class wants to access a resource it must be defined as a method. Only the built-in classes can access system resources directly. However, each built-in class protects its public methods (those that ordinary classes can use) by having those public methods call the Security Manager. For example, suppose you have a new system resource (say, access to the CD-ROM) that you want to make available to ordinary classes. To do this, you create a new class, create a public method like readCDrom( ), and store the new class in the directory for built-ins. Now any class can call readCDrom( ) to read the CD-ROM. If you want to control access to readCDrom( ),you add a call to the Security Manager in the readCDrom( ) method. Now classes can access the CD-ROM if the Security Manager says it is okay. All built-in classes which provide access to the system resources are expected to use the Security Manager. The Security Manager provides methods to be called by the built-in classes to check for authorization before performing certain actions. Tables 40.2 and 40.3 provide a list of the protected and public methods, respectively, as well as a short explanation of each the method’s purpose 3 . Table 40.2 lists the fields and methods of interest to programmers wishing to extend or modify the behavior of the Security Manager. For the most part, only programmers creating a Java-capable browser need to be concerned with these fields and methods. Table 40.2. Protected variables and methods (for use by SecurityManager programmers).*
*
*Purpose
Field or Method boolean inCheck ” SecurityManager( ) Class[] getClassContext( ) ClassLoader currentClassLoader( ) int classDepth(String name) boolean inClass(String name) boolean inClassLoader( )
Local variable to store state of a “security check in progress. Constructs a new SecurityManager object(if one doesn’t exist already). Gets the execution context of this Class. The current ClassLoader in the execution context. Returns the position of the stack frame containing the first occurrence of the named class. Returns true if the specified String is in this Class. Returns a boolean indicating whether or not the current ClassLoader is equal to null.
Table 40.3 lists the methods of interest to all Java users. These SecurityManager methods determine the set of security-relevant checks that the Java Interpreter can perform. Java users can use this table to gain an understanding for which system resources are protected. Table 40.3. Public variables and methods (for use by SecurityManager programmers and by Java system programmers.*
*
*Purpose
Field or method boolean getInCheck( ) Object getSecurityContext( )
Returns whether there is a security check in progress. Returns an implementation-dependent Object that encapsulates enough information about the caller’s current execution context; this context is used to perform some of the security checks later.
Java Unleashed checkCreateClassLoader( ) checkAccess(Thread g) checkAccess(ThreadGroup g) checkExit(int status) checkExec(String cmd) checkLink(String lib) checkRead(FileDescriptor fd) checkRead(String file) checkRead(String file, Object context)
checkWrite(FileDescriptor fd) checkWrite(String file) checkDelete(String file) checkConnect(String host, int port) checkConnect(String host, int Âport, Object context)
- checkListen(int port) checkAccept(String host, int port) checkPropertiesAccess( ) checkPropertyAccess(String key) checkPropertyAccess Â(String key, String def) boolean checkTopLevelWindow Â(Object window)
checkPackageAccess(String pkg) checkPackageDefinition(String pkg) checkSetFactory( )
Page 470 Checks to see if the ClassLoader has been created. It is used to prevent the installation of additional class loaders. Checks to see if the caller can modify the specified thread. The specified thread is allowed to modify the current thread group. Checks to see if the specified thread group is allowed to modify the thread group. Checks to see if the caller can exit the Virtual Machine (with an exit( ) call). Checks to see if the caller can create a new process (that is, run a program). Checks to see if the caller can cause the specified dynamic library to be loaded and linked (and thus used as native code). Checks to see if the caller can read the file descriptor. Checks to see if the caller can read the file with the specified system-dependent filename. Checks to see if the caller’s current execution context and the indicated execution context can both read the file with the specified system-dependent filename. Checks to see if the caller can write the file descriptor. Checks to see if the caller can write the file with the specified system-dependent filename. Checks to see if the caller can delete the file with the specified system-dependent file name. Checks to see if the caller can create a socket connected to the specified port on the specified host. Checks to see if the caller’s current execution context and the indicated execution context can both create a socket connected to the specified port on the specified host. Checks to see if the caller can create a server socket to listen to the specified local port. Checks to see if the caller can accept a connection request to the specified port on the specified host. Checks to see if the caller has access to the system properties. Checks to see if the caller has access to the system property named by key. Checks to see if the caller has access to the system property named by key and def. Checks to see if the caller can create top level windows. A return of false means that the window creation is allowed, but the window will indicate some sort of visual warning. A return of true means the creation is allowed with no special restrictions. (To disallow the creation entirely, this method throws a SecurityException.) Checks to see if the caller can access a package. Checks to see if the caller can define classes in a package—that is, if the caller can add a new class to the package. Check to see if the caller can set a networking-related object factory.
The SecurityManager.class provided in the distribution is not intended to be used directly. Instead, each Java Interpreter is to create its own subclass of the SecurityManager.class in order to implement the desired security policy and security policy controls. Indeed, if
Java Unleashed
Page 471
some forgetful Java Interpreter tries to use the default Security Manager, it will quickly see that the default answer to every “May I do this?” question is “No.” By designing the Security Manager subsystem this way, Java Interpreters are provided great flexibility in controlling system resources. Security Policies as simple as “No” and as complex as “Bring up a dialog box to get the user’s permission unless permission has been previously granted, but still always if the request involves deleting a file, but never ask if the execution stack indicates that the request is coming from a built-in class” can both be implemented using the same basic Security Manager mechanism. The standard pattern for adding Security Manager access checks to system resources is to create an enclosing “guard” function which combines a query to the Security Manager with the actual call to the system resource. public boolean isFile() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(path); } return isFile0(); } In this code fragment, the public method isFile() first queries the Security Manager (through the call to the security.checkRead(path)). If the Security Manager won’t allow the “read” permission on the file specified by path, the call to security.checkRead(path) will throw a SecurityException. Because this exception is not caught by the isFile() function, the exception will propagate to the caller of isFile() immediately. Thus, when an exception is thrown, the low-level function isFile0() will not be called. If the Security Manager will allow the “read” permission on the file specified by path, the call to security.checkRead(path) will return without error, and the next statement, containing the call to isFile0() will be executed. The previous example shows how the set of public functions in the SecurityManager class can be used in creative manners. Because the SecurityManager class has no direct support for the check checkIsAFile(), the check checkRead() is used instead. For the Security Manager to be effective, it must have the cooperation of all the built-in classes that control a system resource. If just one built-in class is implemented that does not follow this “guard” pattern, the consequences could be disastrous. For example, an unguarded write() could allow an applet to overwrite the Java Interpreter’s built-in classes with classes that are all unguarded. When that happens, the security of the system evaporates. Additionally, the Security Manager relies on the proper functioning of the other security components. These previous levels provide protection against an applet that creates its own version of the Security Manager and protection against unauthorized modification of the built-in Security Manager. Also, the extent of this cooperation determines the “upper limit” on the kinds of functionality that can be controlled. For example, the built-in classes supporting network functionality (e.g. HTTP, FTP, and so on), currently allow security control to take one of these four forms:
I. II. III.
disallow all network accesses allow network accesses only to the network source from which the class was loaded disallow network accesses to network sources inside the firewall if the code was loaded from a network source outside the firewall IV. allow all network accesses
Java Unleashed
Page 472
Security in Specific Java Interpreters Now that we’ve covered the generic mechanisms available to all Java Interpreters, we can now look at some of the available Java Interpreters. Right now, the generic security concepts in Java are being scrutinized heavily. But it is not the case that each particular Java Interpreter is being subjected to the same level of scrutiny. The danger here lies in assuming that because the concepts are secure, the implementation is secure as well. This need not be the case. Two things that every implementation of a Java Interpreter depends on are this:
I.
that the Java Virtual Machine is implemented properly. Without this, there is no guarantee that the built-in classes are safe from unauthorized modifications. II. that the built-in libraries are implemented properly (including the Security Manager). Without this, there is no guarantee that the built-in classes as stored on disk (among other things) will remain free of corruption. In summary:
I. II.
Everyone needs to know Java’s weaknesses in theory Everyone needs to know their Java Interpreter’s weakness in implementation.
Number 2 is every bit as important as Number 1 if your goal is practical security. These upcoming sections provide some details on the following Java Interpreters:
Sun’s Appletviewer Sun’s HotJava Netscape Navigator 2.0 AppletViewer The AppletViewer application is Sun’s utility application capable of rendering Java classes. It is included as part of the Java Development Kit, 1.0 version. The AppletViewer enables you to run applets without using a completely functioning World Wide Web browser. It is mainly intended as an applet developer’s tool—it has the ability to understand only the <APPLET> tag. Figure 40.19 shows the AppletViewer’s security control dialog.
FIGURE 40.19.
AppletViewer security control dialog. The AppletViewer application allows control over the following security-related items:
The HTTP proxy server and port The Firewall proxy server and port Network access. The available settings are host Class access: The available settings are The AppletViewer has additional security behavior. This behavior is controlled by access control lists. One list controls the set of files that can be read, and another controls the set of files that can be written. By default, both of these lists are empty.
Java Unleashed
Page 473
Files (and directories) can be added to either list by creating a file called $HOME/.hotjava/properties or C:\.hotjava\properties. The directives acl.read and acl.write enable the user to specify sets of files that are readable and writeable, respectively. For example, adding this line acl.read=/home/tmp/:/home/pub/somefile to your properties file would let the AppletViewer get a list of files in the directory /home/tmp/ and any of its subdirectories, and would let the AppletViewer read the contents of the file /home/pub/somefile. Adding this line acl.write=/home/tmp/:/home/pub/pubdata would let the AppletViewer create new files in the directory /home/tmp/ and would let the AppletViewer write the contents of the file /home/pub/pubdata. *
*CAUTION Once you give applets the ability to create a file or to write a file, there is no way to restrict the size of file the applet writes. It is free to create a file large enough to consume all available disk space, if it so desires. HotJava HotJava, version 1.0 alpha3, is Sun’s Java Web browser. Its inclusion in this list, however, is mostly for historic reasons. As of this writing, a new version of HotJava is in development; the version discussed here is not compatible with the new Java API. It is reasonable to assume that this dialog will change in the new HotJava version that is compatible with the new Java API. It is also reasonable to assume that the properties file as used by the AppletViewer application will also be accessed by HotJava. Figure 40.20 shows HotJava’s security control dialog.
FIGURE 40.20.
HotJava security control dialog. The HotJava application allows control over the following security-related items:
Enter desired security mode—Grants applets access to information. The available settings are: access—Applets are not able to load information from the network Host—Only allow an applet to load information from its host applets can load information outside the firewall can load information from anywhere Apply security mode to applet loading—Grants applets access to loading other applets. This item interacts with the desired security mode. When this item is selected, the behaviors are as follows: access—No applets are loaded. HotJava will not interpret any applets Host—Only applets specified with a file: URL can be loaded applets that are inside your firewall can be loaded applets can be loaded Enter the kind of domain you’re using—Allows the user to choose between Network Information Services (NIS) Naming Service (DNS)
Java Unleashed
Page 474
Configure firewall...—This brings up another dialog that enables the user to specify a set of firewall hosts or domains. The HotJava version 1.0 alpha3 release does not permit applets to write files or modify files in any manner.
Netscape Navigator Netscape Navigator, beginning with version 2.0, is a Java-capable Web browser. Figure 40.21 shows Navigator’s security control dialog.
FIGURE 40.21.
Netscape Navigator security control dialog. As the dialog shows, the security preferences in Netscape Navigator concerning Java are straightforward. Java is either enabled (by default) or is disabled. But, even when Java is enabled, it operates in a very restricted environment. Currently, there is no way for a Netscape Navigator user to remove any of these restrictions:
Applets cannot read or write files on the local hard drive at all. (In particular, it cannot read or write the properties file mentioned earlier.) Applets are restricted to reading only nine system properties. These properties allow the Java Applet to access some information about the system that is executing the Java Applet. This information includes the Java Interpreter vendor name and Interpreter version number, the character used to separate components of a filename (e.g. “/”), the character used to separate lines, and so on. Applets are permitted to open a network connection to its originating host, but no others. Applets specified by a file: URL that do not reside in the CLASSPATH are loaded by the applet Class Loader. * *CAUTION Applets that happen to reside in your CLASSPATH, however, are not loaded by the applet Class Loader. Instead, they are loaded by the file system loader. Remember, for security reasons all Java Interpreters check the set of built-in classes before checking elsewhere. This rule applies to class loading as well. By loading a class from the CLASSPATH before loading from another source, all applets are prevented from installing their own private set of what should be built-in classes. For example, if the SecurityManager class had not been loaded when a mischievous applet attempted to load http://www.badguy.com/SecurityManager.class, the Java Interpreter will actually load SecurityManager.class from the local system, not from the external system.
Summary We have collected the cautions and warnings given throughout the chapter into short practical guides to make your Java experience a safer one:
I. II. III.
Be aware that the availability of security measures does not guarantee the use of security measures. Never, ever, place an unknown .class file in any directory that is in your CLASSPATH. Never load a .class from a file: URL (unless you know that your Java Interpreter does not treat file: URLs as built-ins). IV. Be alert that classes being loaded from the same network source can access each other. V. Be sure you trust the vendor that implements your Java Interpreter.
Java Unleashed VI.
Page 475
Be aware that giving write access to a file means you cannot control the size of the file.
Bringing in programs of unknown origin and running them on your system is a risky business. Java has been designed to take as much risk out of running these unknown programs as possible. Taking the precautions we have outlined in this chapter can further reduce the risks. However, some risks remain. Everyone needs to decide for themselves if the benefits of Java outweigh the risks. *
*FURTHER JAVA SECURITY INFORMATION ON THE WEB Joseph A. Bank, “Java Security”, Dec. 8, 1995 URL: http://swissnet.ai.mit.edu/~jbank/javapaper/javapaper.html James Gosling and Henry McGilton, “The Java Language Environment: A White Paper”, October 1995 URL: ftp://ftp.javasoft.com/docs/whitepaper.ps.tar.Z Sun Microsystems, “Frequently Asked Questions—Applet Security”, Jan. 9, 1996 version 1.0 Beta 2 URL: http://java.sun.com/sfaq/ Sun Microsystems, “HotJava: The Security Story”, May 19, 1995 URL: http://java.sun.com/1.0alpha3/doc/security/security.html Sun Microsystems, “The Java Language Specification”, DRAFT—Version 1.0 Beta, October 30, 1995 URL: http://java.sun.com/JDK-beta2/psfiles/javaspec.ps
Java Unleashed
Page 476
Chapter 41 Java script One of the most intriguing and exciting aspects of the World Wide Web is its capability to offer interactive content to many people. Thousands upon thousands of pages are linked together across the globe, each accessible with a single mouse click. The Web is the largest collection of information available to a single person since the beginning of time. As incredible as these notions may be, the users of the Web demand more. As technology on the Web improves, users want more interaction, more sophistication, more visually appealing content, and above all, these users want to be able to create this content themselves. Java has turned the promise of interactivity into reality. For the first time, programmers can create small software programs, or applets, that can be distributed and executed easily on the World Wide Web. Netscape Communications (http://www.netscape.com) has single-handedly helped to thrust this new technology into the mainstream by incorporating Java into its popular Navigator software. For the first time, Web sites can finally interact with their users. Sophisticated applications like paint programs, spreadsheets, games, and complex math engines can now run in the browser window, among HTML pages, without the need of specialized hardware or software, other than a Java-enabled browser such as Netscape’s Navigator. Two distinct solutions to interactive content creation have formed on the Web: the simple-to- use Hypertext Markup Language (HTML), and the sophisticated and powerful Java programming language. With these two tools, users can create the visually compelling content and have it merge seamlessly with the interactive applications that Java offers. Nevertheless, what seems to be missing is a system for bringing these two technologies closer together.
Welcome to JavaScript Netscape Communications saw the need for a bridge between these two technologies. They began working on a new scripting language that could have a place between HTML and Java, yet be powerful enough to link the two technologies together. When Netscape’s new scripting language was introduced, it was known as LiveScript. It was immediately incorporated into their Navigator product in an effort to quickly have it adopted by the Internet community. Soon thereafter, seeing the potential of a joint effort, Netscape and Sun teamed to help LiveScript become more mainstream and to establish it as the standard in the Internet community for Web-based scripting. Because LiveScript syntax was so similar to Java’s syntax, Sun and Netscape decided to rename their new product to make it more recognizable. It would now be called JavaScript. JavaScript was created as an easy-to-use, open, cross-platform scripting language that could link together objects and resources from both HTML and Java. While Java applets are primarily developed by programmers, JavaScript was intended to be used by HTML page authors to dynamically control the interaction and behavior of their pages. JavaScript is unique in that it has been designed to be complementary to and integrated with both HTML and Java. One of the most important benefits JavaScript offers is its capability to reduce network traffic by keeping simple tasks local. In other words, instead of the server performing tasks and returning the results to the browser, the browser can handle some of the tasks locally, thus giving the user quicker response times. JavaScript has been endorsed by over 25 industry-leading companies, including America Online, Inc., Apple Computer, Inc., Architext Software, Attachmate Corporation, AT&T, Borland International, Intuit, Inc., and Silicon Graphics, Inc. These companies plan to introduce products adopting the JavaScript language which will help to establish it in the Web community. JavaScript has also been submitted to the appropriate standards bodies for industry review and commenting. These four chapters on JavaScript provide an introduction into JavaScript and do not cover the entire scope of JavaScript. They are here as a starting point into your exploration of this new and unique scripting language. Because JavaScript is still an evolving language, some of its features and commands may change in the future. Every effort has been made to make the information contained here as timely as possible.
Learning JavaScript JavaScript is based on the powerful Java language in its syntax and usage, yet is interpreted, not compiled. What this means is that the JavaScript application code is downloaded as text into the browser along with the HTML text. The code is executed within the browser, enabling you to develop simple applications that can interact with and assist your users. With JavaScript, you can respond to events from the user such as mouse clicks, mouse movement over a link, and form input. You can build dynamic pages that change according to your user’s requests, or even play sounds or run applets when a user enters or
Java Unleashed
Page 477
leaves your page. This type of capability at the client level allows for tremendous interactivity with the users of your Web pages. The JavaScript language resembles Java, but is simpler and easier to learn. A JavaScript application may be as short as one line or take several pages. The complexity depends on the extent in which your script interacts with the page it is in. One of the first uses of JavaScript for most authors is in form validation. Form validation is the ability for an HTML form to check the input from a user before it is submitted, which greatly improves performance on your server as well as decreases user frustration. Listing 41.1 shows a simple example of form validation.
Listing 41.1. A simple form validation example.
The short HTML document in Listing 41.1 is a testament to the power and simplicity of JavaScript. The document displays a form that cannot be submitted until something is entered into the name field. If the field is left blank, a window will open stating You left the name field blank. Once information has been entered, the form may be submitted normally. Learning JavaScript isn’t difficult, but requires considerably more effort than HTML. Despite its simplicity, JavaScript is based on a programming language, making it much more sophisticated than HTML. It requires a time investment and a lot of experimentation. Fortunately, JavaScript applications are easy to develop and easy to test; this ultimately leads to a speedier grasp of the JavaScript language. As an added benefit, by learning JavaScript, you will have begun your journey into Java programming. JavaScript is based on Java, so many of the commands and constructs are similar, if not the same. The next section discusses the similarities and differences in these two languages.
JavaScript and Java JavaScript was designed to help the nonprogrammer in creating interactive applications for the Web and to facilitate the integration of Java and HTML. Despite these two seemingly different roles, JavaScript and Java are based on the same basic principles. They are both programming languages with similar commands and syntax, they are both object-oriented, and they are both open- and crossplatform. Following is a comparison of the two languages.
Comparing and Contrasting JavaScript is very similar to Java in its syntax, but many of the similarities stop there. JavaScript is an interpreted language, meaning that JavaScript code runs directly within the browser and requires no compilation, whereas Java requires compilation prior to execution. In addition, JavaScript supports a smaller set of data types compared to Java, and JavaScript methods do not require special declarations like Java methods. In contrast to Java, JavaScript contains many built-in objects that require minimal effort for their creation. JavaScript has no classes or class inheritance like Java, but rather relies on its built-in set of objects to be extended in order to suit the needs of the programmer. Because JavaScript is interpreted and not compiled, all object references are checked at runtime, whereas Java requires that all references exist at compile time. Also, a variable’s datatype need not be declared in JavaScript like is it required in Java. Another major difference between Java and JavaScript is in JavaScript’s tight integration with HTML. Java applets may be called from within an HTML document by use of the <APPLET> tag, but the actual compiled code resides in a separate file. JavaScript enables you to embed the source code directly into the HTML code, resulting in a single file for both HTML code and JavaScript source code.
Java Unleashed
Page 478
Security Considerations Like Java, JavaScript has been designed from the ground up to be a secure language. Neither Java nor JavaScript allows the use of pointers, which is commonly the cause of security violations. Because JavaScript is an interpreted language, there are no compile-time memory allocation concerns, which are another potential cause of security violations. In addition, in order to minimize the effectiveness of malicious programs, disk writes are not allowed in either Java or JavaScript. In the Java language, the application code is compiled into byte-code on the server. This byte-code file, generally with a .class extension, is the actual program that is received and run by the Java-enabled browser. This .class file, because it is compiled, does not contain the original source code of the application, thus making your source code unavailable to the user. In contrast, because JavaScript code is embedded in the HTML as plain text, the user may view the source to the HTML file, and in turn, view the source code to your JavaScript application. Currently, it is not possible to protect the source code other than by placing a copyright notice in the source of your JavaScript application.
Integrating JavaScript and Java One of the much-touted features of JavaScript is its capability to directly interact with Java applications. In the Java code, useful properties can be exposed to allow a JavaScript application to get or set these properties and alter the state or performance of the Java application. This same paradigm also applies to Netscape Navigator plug-ins, meaning a JavaScript could query and alter the performance of a plug-in, as well. In the first released version of JavaScript, which is included in Netscape Navigator 2.0, this tightly coupled interaction with Java is not yet implemented. The next major release of JavaScript, slated to appear in Netscape Navigator 2.1, will enable you to call Java methods from JavaScript directly and get and set public Java fields. This integration will allow for more dynamic content and interactivity within pages that contain both Java applets and JavaScript code. Many future applications on the World Wide Web will feature complex and sophisticated interfaces and interactivity. For example, suppose you wanted to create an HTML page that contained the logo of your company on top, a visually integrated three-dimensional image of an automobile in the center, descriptive text about the automobile below, and form controls for rotating the automobile in any direction you desired. This HTML page sounds exciting, but you would face very complex programming issues if you were trying to develop such an application entirely in Java. By allowing Java to perform the tasks it is best at, perhaps rotating and rendering the 3D car image, JavaScript can handle the interaction between the form controls and the Java application, sending the Java applet messages about what the user has chosen as rotation angles. Finally, HTML can be used to establish the framework, displaying the title logo, and building the actual form elements. By integrating all these technologies together, HTML, JavaScript, and Java, the development and implementation of highly interactive Web pages can become easier and faster to complete.
Halfway to Java with JavaScript One of the most promising aspects of learning JavaScript is that it will enable you to begin understanding the basic structures of object-oriented programming. Object-oriented programming is rapidly becoming the standard in programming languages of the future. Today, C++ has set the standard for object-oriented programming, and is used widely across the globe. It is no wonder that both JavaScript and Java resemble the syntax of C and C++. Many of the constructs of those languages have been passed on to JavaScript and Java, making it easier for programmers to transition to these new languages. If you have never programmed before, JavaScript will enable you to begin the process of understanding how these languages work. Because of the similarities between JavaScript and Java, once you have learned and grasped the basic concepts of JavaScript, you will already be well on your way to mastering Java. With the combination of these two languages at your disposal, compelling and interactive content is yours for the making. Spending the time reading these chapters carefully, completing all of the examples, and then, of course, experimenting with your own applications will soon make you a master at these powerful tools.
JavaScript Requirements JavaScript, unlike Java, requires few resources to use and program. One of the exciting aspects of JavaScript is that nearly anyone can use it and develop their own applications right away. Most users already have all of the tools they need on their computers right now. Because JavaScript is an interpreted language and not compiled, the usual requirements associated with compiled languages,
Java Unleashed
Page 479
compilers, linkers, debuggers, and so on, are not needed. As a result, JavaScript imposes a very minimal set of requirements in order to begin developing with it immediately.
Required Software and Hardware Because JavaScript was developed by Netscape, Netscape’s Navigator program is the first program to support JavaScript directly. Every version of the Netscape Navigator across all platforms supports JavaScript. These platforms includes Macintosh, Windows, Solaris, SunOS, HP-UX, IRIX, AIX, Digital UNIX, Linux, NetBSD, and FreeBSD. In turn, the hardware requirements for running JavaScript are the same as the requirements for running the Netscape Navigator. In other words, if you are currently using the Netscape Navigator for Web browsing, you already have all of the hardware and software required to run JavaScript applications. Developing for JavaScript is as simple as running JavaScript applications. Because JavaScript is interpreted, and therefore completely plain-text-based, any editor can be used to create JavaScript-enabled Web pages. Whichever editor you use now, whether it be NotePad for Windows, vi or emacs on UNIX, SimpleText on the Mac, or any of the host of HTML editors, they all have the capability to produce JavaScript code. Because JavaScript does not need to be compiled, as soon as you save your JavaScript code, you can immediately test it, right in your browser. With over 25 companies already dedicated to providing tools and applications based on JavaScript, including Netscape, there will soon be a large collection of tools available to more specifically focus on the development process of JavaScript applications. Netscape’s Navigator Gold product contains a full What You See Is What You Get (WYSIWYG) HTML editor built into the browser and allows you to enter JavaScript code directly. By being able to instantly switch back and forth between JavaScript source and the final HTML page, Navigator Gold promises to make it even easier for anyone to create compelling, JavaScript-based Web pages.
Authoring with JavaScript Creating a JavaScript program is a relatively simple process. Using any text editor, you may begin typing in JavaScript statements to expand the role of your current HTML pages, create entirely new pages using only JavaScript, or mix both HTML and JavaScript. This section will cover some of the issues and syntax in creating a JavaScript application, as well as information on how to embed your JavaScript code into your HTML documents.
Creating Your Script In order for your browser to recognize that there is JavaScript code in your file, two special tags have been introduced. <SCRIPT>... Between these two tags, JavaScript code may be inserted and the browser will recognize the JavaScript code while loading your page. The <SCRIPT> tag also has attributes that can be specified. <SCRIPT LANGUAGE=”JavaScript”>... The LANGUAGE attribute specifies the language to use for the script. Currently, only “LiveScript” and “JavaScript” are used. The “LiveScript” name is a legacy attribute from an older version of JavaScript. <SCRIPT SRC=”http://myjavascript.js”>... The SRC attribute is optional and, if given, specifies an URL that loads the text of a script. A script from a SRC is evaluated before in-page scripts. Also, any SRC URL should use the .js suffix. <SCRIPT LANGUAGE=”language” SRC=url>... You may also specify both a LANGUAGE and a SRC attribute together. The JavaScript code is evaluated after the entire page loads, and if you are using any JavaScript functions, they will be stored by the browser, but not executed without a specific call to that function.
Java Unleashed
Page 480
In order to create a script, simply use your favorite text or HTML editor and begin typing in JavaScript code. Listing 41.2 illustrates a simple JavaScript program.
Listing 41.2. The first JavaScript example. <SCRIPT> document.write(“JavaScript is Fun!”) This is normal HTML.
When the script is run, it produces what is shown in Figure 41.1. The JavaScript is Fun! message is produced by the JavaScript code, while the This is normal HTML. is HTML text. Notice how the JavaScript statement document.write() is inserted between the HTML tags <SCRIPT>.... This tells the browser that JavaScript code is located between the tags and not standard HTML. FIGURE 40.1. Javascript application displaying a message in the Netscape Navigator along with HTML text.
Embedding the Script in Your HTML Now that you know how to create a simple JavaScript program, there are some issues to address concerning embedding your JavaScript into HTML documents. As we have already seen, the <SCRIPT>... tags are used to denote JavaScript code from normal HTML code. Browsers that do not recognize a particular tag generally ignore them, so the <SCRIPT> and tags would not show up on a non-JavaScript-enabled browser. Unfortunately, the code within the SCRIPT tags would not be ignored but would display as unwanted output on your web page. Netscape Navigator solves this problem by enabling you to use HTML comment tags to comment out the portion of JavaScript code that other browsers would treat as plain text. In other words, when a browser that cannot support JavaScript comes across the comment tags, it will ignore everything in between them, where Netscape Navigator will not. Listing 41.3 uses the same code from Listing 41.2, but now comments out the portion that non-JavaScript browsers could not understand.
Listing 41.3. A JavaScript example with commented-out code. <SCRIPT> tags. These are HTML tags denoting a comment within the document. Netscape Navigator knows to look within these tags for JavaScript code. You may have also noticed two slashes // before the End the hiding here statement. Because we were within the SCRIPT tags, Netscape Navigator would try to execute the line as a statement. The two forward slashes is a JavaScript statement for denoting a comment line. The combination of the HTML comment tags and JavaScript comment tags help to
Java Unleashed
Page 481
ensure compatibility of your documents across many different types of browsers that fully suppport HTML commenting. Chapters 41 and 42 contain more in-depth examples of creating JavaScript applications, including JavaScript applications with functions.
Running Your Script Running your JavaScript code is as easy as viewing an HTML page. Once your code is complete, simply load the page into your browser for instant results. This ability to instantly run and check your JavaScript code is another powerful feature of JavaScript. In the development and testing process of a JavaScript application, your computer does not need to be connected to a network. You can edit, test, and execute your JavaScript applications without ever being online. This can be a real advantage when hourly connect charges are a concern. Also, because you are testing your applications locally, load times will be extremely fast, and because JavaScript executes the code immediately, your entire development process will be extremely efficient and rewarding.
Future Enhancements to JavaScript JavaScript is still in its infancy and as of this writing, the final version of JavaScript has not yet been released. Some of the features of JavaScript may change in the future, and new features will surely be added. Nevertheless, the core technology of JavaScript will not change any time soon. When new versions of JavaScript arrive, minor changes may have to be made to older code, but most code should work unmodified. It is also important to note that JavaScript has already been submitted to appropriate standards committees. Future versions of JavaScript promises total integration with Java applets as well as integration with Netscape Navigator plug-ins. Other new software systems that use JavaScript extensively are also available. For example, Netscape’s LiveWire system uses serverbased JavaScript code to integrate together database engines and HTML, as well a host of other capabilities. One of the best ways to stay abreast of the latest changes in this fast-moving industry is to stay tuned to the various resources available to you on the Internet.
JavaScript Resources As JavaScript expands in popularity and its usage becomes more widespread, more and more resources will become available. Currently, there are many Web sites that contain example JavaScript applications as well as tutorials, documentation, and collections of other resources on JavaScript on the Web. Following are a few web sites of interest; these sites will also point you to other sites containing even more information on Java and JavaScript:
Netscape Communications, Inc. (http://www.netscape.com), the creators of JavaScript, have JavaScript demos online as well as full documentation on the JavaScript language. The latest version of the Netscape Navigator is always available on their site as well. Netscape also offers a secure developer’s forum for the exchange of ideas and questions about JavaScript, Java, and other issues (http://developer.netscape.com). Sun Microsystems, Inc. (http://www.sun.com), the creators of Java, hosts a site completely dedicated to Java and JavaScript (http://java.sun.com). Here you will find the latest happenings related to Java as well as the latest development tools and examples. Live Software (http://www.livesoftware.com), a company that provides software solutions for interactive content creation, hosts a JavaScript Resource Center (http://jrc.livesoftware.com) that contains links to many Java and JavaScript sites on the Web, as well as all of the source code mentioned in these four chapters on JavaScript and other example JavaScript applications. In addition, there is also a news group available from Live software for the discussion of JavaScript programming and other technical issues (news://news.livesoftware.com/livesoftware.javascript.developer). Gamelan (http://www.gamelan.com) has grown to become one of the Internet’s largest collection of Java applets. It now also includes JavaScript collections on the Internet. There are also books available on JavaScript that go into more detail than these chapters. They cover expanded issues of JavaScript, as well as providing more examples on how to best utilize JavaScript for your requirements. Teach Yourself JavaScript in 21 Days by Sams Publishing is an example of one of these books.
Java Unleashed
Page 482
Summary JavaScript is a powerful, interpreted scripting language that is built-in to the Netscape Navigator software. JavaScript is full-featured and allows for the creation of dynamic pages, interactive content, forms input checking, and much more. JavaScript’s syntax is based on Java’s syntax, but otherwise remains as a different language with a different role. JavaScript excels in its ability to add client-side processing of forms, create HTML content on-the-fly, and tightly integrate HTML with Java. JavaScript requires only a text editor for the creation of applications, and because Netscape’s Navigator has a built-in JavaScript interpreter, JavaScript applications may be run immediately after they have been typed in simply by loading them into the browser window. JavaScript enables you to embed your JavaScript code directly into an HTML document with the use of the <SCRIPT>… tags. By hiding the code from other browsers using HTML comment tags and JavaScript comment statements, you can try to ensure that your JavaScript page stays compatible with most browsers that do not support JavaScript. The next chapter delves into the JavaScript language itself, its syntax, and how you can use the language to create your own JavaScript-enabled Web pages.
Java Unleashed
Page 483
Chapter 42 The JavaScript language The JavaScript language is not difficult to learn, but it does require knowledge of many of the syntax conventions of Java. An understanding of these conventions is necessary to adequately use JavaScript. It may be helpful to gain a thorough understanding of the Java syntax and conventions by reading some of the earlier chapters in this book before proceeding.
JavaScript Fundamentals This section covers the basic components of the JavaScript language. These components include variables and values, data types, literals, properties, arrays, and comment statements. As the basic building blocks of JavaScript, an understanding of these fundamentals is the first step toward the mastery of the JavaScript language.
Variables and Values JavaScript variables are similar to variables in any other programming language; they hold values that you use in your application. JavaScript enables you to name your case-sensitive variables so you may reference them elsewhere in your code. You can name variables anything you wish. You may opt to use descriptive names such as number_of_times_clicked, or short, generic names such as foo. The only restrictions on variable names is that they must begin with a letter or underscore (_). JavaScript variables can contain the following different types of values:
numbers, for example, 14 or 1.25 logicals (Boolean), can be True or False strings, for example, Hello World JavaScript also recognizes the null keyword for specifying null values for variables. JavaScript has reduced the confusion in deciding which data type to use for different types of numbers. JavaScript uses a single data type for numbers, which means that a JavaScript number can contain the equivalent of integers, reals, and doubles without the need for specialized types. In addition, JavaScript handles Date objects with this same data type, further simplifying code development under JavaScript. The var statement is used to declare variables in JavaScript. Each variable is given a name and optionally an initial value. Outside of a function, the var statement is optional, but it is highly recommended that you always use var to avoid the possibility of overwriting local variables with global variables. Local variables are generally declared within a function with the intention that only the function will be able to use the variable. Global variables are declared outside of any function, making the variable available to any function. Therefore, to avoid conflict, the var statement will ensure your variables safety in these situations. Following is the syntax for the var statement: var varname [= value] [..., varname [= value] ] Multiple variables may be declared from one var statement. The following example demonstrates this: var num_cans = 10, price_per_can = 2.0;
Data Types JavaScript fully supports variables of different data types, but does so loosely to enable you, the programmer, to more quickly develop applications without the headache of strict type specification prior to execution. JavaScript applications can handle a great number of different types of data, but JavaScript manages to do this with only three distinct data types. In addition, JavaScript can decide for you what data type your variable should be during script execution. Converting between different types is very easy and straightforward. Data types are converted automatically when your JavaScript application runs. JavaScript follows one simple rule when converting types; it performs the conversion from left to right. In other
Java Unleashed
Page 484
words, the right-hand operand will be converted to the type of the left-hand operand. No matter how complex the conversion, JavaScript always follows this left-to-right approach. For example, if you had the following variables:
var1 = “10” var2 = 20 Then performed the following statements:
x = var1 + var2 y = var2 + var1 The first statement would convert var2 to a string, because the operand on the left, var1, is a string. The result would be that x would contain the string “1020”. In contrast, the second statement would convert var1 to a number, because the operand to its left, var2, is a number. The two numbers would then be added together to form the result of 30 in y. As you saw in the previous example, JavaScript can convert strings that contain numeric characters into numbers quite easily. If a string contains non-numeric values, such as “Paul”, and you try to set a number variable to contain that value, JavaScript will generate an error because it cannot convert “Paul” into a numeric value.
Literals Many times when creating new variables, you need to specify an initial value. These types of fixed values are called literals. Literals are not variables, but rather, constant expressions of values for data types. For example, some literal values could include 24 72.745 “programming is fun” When expressing literal values for integers, you may use three different bases. You may use decimal (base 10), hexadecimal (base 16), or octal (base 8) format. To specify a decimal literal, simply use a series of digits without any leading zeros. For example 42 (a decimal literal) To specify an octal literal, precede the number with a leading 0 (zero). Octal literals can only include digits 0-7. For example 010 (an octal literal) Finally, when specifying a hexadecimal literal, precede the number with a leading 0x (or 0X). Hexadecimal literals can include all digits (0 - 9) and the letters a-f and A-F. For example: 0xFF (a hexadecimal literal) A floating-point literal is specified by the following parts: a decimal integer, a decimal point, another decimal integer, an exponent, and a type suffix. The exponent is specified by using an e or E followed by an integer that can be signed by proceeding it with a - or a +. A floating-point literal should have at least one digit plus either a decimal point or e or E. For example 4.25 (floating point literals) .4e3 2E-8 -6.2e9 A Boolean literal is specified by one of two values: true or false. Finally, string literals are specified by using zero or more characters enclosed in double or single quotes. For example
Java Unleashed
Page 485
“banana” (string literals) ‘pear’ “goodbye \t forever” As in the last example, string literals can also contain special characters. Following is a list of the JavaScript special characters for string literals: \b (backspace) \f (form feed) \n (new line character) \r (carriage return) \t (tab character)
Properties JavaScript objects, discussed later in this chapter, generally have properties associated with them. Object properties can be either variables or functions. These properties can be accessed in two different ways. The first approach to accessing the properties of objects is as follows: objectName.propertyName Once you have an object, creating properties for that object is as simple as assigning a value to a property name. For example, suppose you had an object named person. To give the person object some properties, you would simply use the following statements: person.name = “John”; person.age = 24; person.height = 68; person.weight = 155; These statements would add the four properties name, age, height, and weight to the person object.
Arrays Because JavaScript uses the same data structure for the storage of properties and arrays, they generally are treated as the same thing. In other words, they are so closely related that you may access properties through arrays and vice versa. For example, you could access the properties mentioned in the prior section as follows: person[“name”] = “John”; person[“age”] = 24; person[“height”] = 68; person[“weight”] = 155; In addition, you may also use array indices to access your properties; for example, to access the “name” property in the person object, since it is the first property, you can use the following: person[0] = “John” As you can see, JavaScript is very lenient in the use and access of object properties. This simplicity in the JavaScript allows you to spend more time on the development of applications, rather than specifics of the language. Listing 42.1 is an example of a simple JavaScript function that assists in the creation of arrays.
Listing 42.1. A JavaScript function for creating arrays.
Java Unleashed
Page 486
function MakeArray(n) { this.length = n; for (var i = 1; i . FIGURE 44.4. Retrieving a cookie value from the Navigator.
Listing 44.2. Source code for Page 1 of the Cookies Demo. <TITLE>Cookie Demo JavaScript’s Cookies Demo
JavaScript will now read the cookie information from your browser and try to retrieve the name you entered on the previous page: <SCRIPT> document.write(“Welcome back “ + GetCookie(‘DemoName’) + “.”); Cookies can be used in a wide set of applications, for example, a catalog shopping cart, a timed easter egg hunt, and personalized Web pages.
Cookie Toolkit The source code in Listing 44.4 contains the latest cookie toolkit from hIdaho Design. The source code contains usage information as well as an example of how to use the toolkit. The latest version is available from http://www.hidaho.com/cookies/.
Listing 44.4. Complete source to cookie toolkit.
<script language=”JavaScript”>