0% found this document useful (0 votes)
901 views809 pages

Tricks of The Java Programming Gurus

good book

Uploaded by

vvvvicky
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
0% found this document useful (0 votes)
901 views809 pages

Tricks of The Java Programming Gurus

good book

Uploaded by

vvvvicky
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/ 809

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
q q q q q

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

Chapter 2 Using the Media Tracker


q q

Java Media Objects and the Internet Keeping Up with Media Objects

file:///C:/temp/java%20guru/index.htm (1 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q q q q

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

Chapter 3 Exploiting the Network


q q q q

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

Chapter 4 Using Java's Audio Classes


q q q q q

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

Chapter 5 Building Special-Purpose I/O Classes


q q q q

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

Chapter 6 Effective Use of Threads


q q q q

Using Threads Performance Inside Threads Summary

Chapter 7 Concurrency and Synchronization


file:///C:/temp/java%20guru/index.htm (2 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus

q q q q q q q

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

Chapter 8 All About GridBaglayout and Other Layout managers


q q q q q

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

Chapter 9 Extending AWT Components


q q q q q q

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

Chapter 10 Combining AWT Components


q q q q q q q

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:/temp/java%20guru/index.htm (3 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q q q q

Overview Class Construction Event Handling Summary

Chapter 11 Advanced Event Handling


q q q q q q q q q q q

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


q q q q q q q q q q

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


q q q

What Is Animation? Types of Animation Implementing Frame Animation

file:///C:/temp/java%20guru/index.htm (4 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q q q q

Eliminating Flicker Implementing Sprite Animation Testing the Sprite Classes Summary

Chapter 14 Writing 2D Games


q q q q q q q q q q q q q

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


q q q q q q q q q q q q

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:/temp/java%20guru/index.htm (5 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q

Summary

Chapter 16 Building STand-Alone Applications


q q q q q q

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


q q q q q q

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


q q q q q q q q q q

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:/temp/java%20guru/index.htm (6 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus

Chapter 19 Persistence
q q q q q

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


q q q q q

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

Chapter 21 Creating a Security Policy


q q q q q q q q q

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


q q q q

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

file:///C:/temp/java%20guru/index.htm (7 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q

Summary

Chapter 23 Pushing the Limits of Java Security


q q q q q q q q q

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


q q q q q q q

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


q q q q

Java Packages Documentation Generation Using javadoc Class Dissassembly Using javap Summary

Chapter 26 The Java Debugging API


q q q

Remote Debugging Java Debugger The Sun Java Debugger API

file:///C:/temp/java%20guru/index.htm (8 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q q q

Simple Types Some Examples Summary

Chapter 27 Alternatives to Java


q q q q q

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

Chapter 28 Moving C and C++ Code to Java


q q q q q q q q q q q q q q q q q

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


q q q

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

file:///C:/temp/java%20guru/index.htm (9 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q q q q

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

Chapter 30 When and Why to Use Native Methods


q q q q q

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


q q q q q

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


q q q q

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


q q q q

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

file:///C:/temp/java%20guru/index.htm (10 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q q

Language Protection Mechanisms Summary

Chapter 34 Client/Server Programming


q q q q q q q q

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


q q q q q q

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


q q q

The Java Ideals The Java Community Setting Your Sights

Appendix A API Quick Reference


q q q

java.applet java.awt java.awt.image

file:///C:/temp/java%20guru/index.htm (11 of 19) [2/3/2003 8:17:15 AM]

CONTENTS -- Tricks of the Java Programming Gurus


q q q q q

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:/temp/java%20guru/index.htm (12 of 19) [2/3/2003 8:17:15 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:/temp/java%20guru/index.htm (13 of 19) [2/3/2003 8:17:15 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:/temp/java%20guru/index.htm (14 of 19) [2/3/2003 8:17:15 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:/temp/java%20guru/index.htm (15 of 19) [2/3/2003 8:17:15 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:/temp/java%20guru/index.htm (16 of 19) [2/3/2003 8:17:15 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:
q

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:/temp/java%20guru/index.htm (17 of 19) [2/3/2003 8:17:15 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:
q q q

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:
q q q q q

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:/temp/java%20guru/index.htm (18 of 19) [2/3/2003 8:17:15 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:/temp/java%20guru/index.htm (19 of 19) [2/3/2003 8:17:15 AM]

Chapter 1 -- Communication Between Applets

Chapter 1
Communication Between Applets

CONTENTS
q q q q q

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:/temp/java%20guru/ch1.htm (1 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (2 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (3 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (4 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (5 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (6 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (7 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (8 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (9 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (10 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (11 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (12 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (13 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (14 of 15) [2/3/2003 8:18:03 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:/temp/java%20guru/ch1.htm (15 of 15) [2/3/2003 8:18:03 AM]

Chapter 2 -- Using the Media Tracker

Chapter 2
Using the Media Tracker

CONTENTS
q q q q q q

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:/temp/java%20guru/ch2.htm (1 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (2 of 11) [2/3/2003 8:18:27 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:
q q q q

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:
q q q q q q q q q q q q q q q

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:/temp/java%20guru/ch2.htm (3 of 11) [2/3/2003 8:18:27 AM]

Chapter 2 -- Using the Media Tracker


q q

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:/temp/java%20guru/ch2.htm (4 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (5 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (6 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (7 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (8 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (9 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (10 of 11) [2/3/2003 8:18:27 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:/temp/java%20guru/ch2.htm (11 of 11) [2/3/2003 8:18:27 AM]

Chapter 3 -- Exploiting the Network

Chapter 3
Exploiting the Network

CONTENTS
q q q q

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

Because Java applets are usually prohibited from accessing files or other resources on the system where they are running, it's sometimes difficult to find really useful tasks for applets to do. However, one useful thing that applets can do is access the network. While interacting with a user, applets can retrieve new data from the network or request information from the user and return that information to the server. There may still be some restrictions; for example, Netscape Navigator currently permits an applet to communicate only with the host from which it was loaded. Nevertheless, with the proper support on the server, an applet can get anything it needs from the network. Additionally, the built-in networking support in the Java library makes exploiting the network easy. This chapter explains several ways to access the network from applets.

Retrieving Data Using URLs


One of the nicest aspects of the core Java library is that it provides built-in classes for using the Internet and the World Wide Web. The URL class and its associated classes are particularly useful, because they provide a simplified, high-level interface to common network operations, especially document retrieval. A Universal Resource Locator (URL) can be used to fetch a document of some sort-text, HTML, image, video, audio, or some other type of document, such as a Java class file. The Java library makes it extremely easy to fetch a document from the network using a URL.

Retrieving Typed Data Objects


file:///C:/temp/java%20guru/ch3.htm (1 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

Here's a short code fragment that shows how to fetch one document using a URL: URL home = new URL("http://www.utdallas.edu/~glv/"); Object page = home.getContent(); That's actually all there is to it (under some circumstances), although there is usually a little more error handling to do. There are certainly other options that you can use if you want, but it's nice to know that the basics of parsing the URL, finding the remote host, opening the connection, initializing the protocol, requesting the appropriate document, and reading it into a local buffer are all taken care of by the Java library. There are actually several ways to create a new URL object, using different types of information:
q q

URL(String spec) is the simple way, in which the single parameter is a URL string. URL(URL context, String spec) creates a new URL relative to an existing URL specified by the context parameter. The constructor is smart enough to recognize when the spec parameter is an absolute URL; it ignores the context parameter in that case. URL(String protocol, String host, String file) enables you to specify each of the three main parts of a URL independently. URL(String protocol, String host, int port, String file) is just like the previous constructor, except that it also enables you to explicitly specify a port number on the host. Usually, the port number is implied by the protocol name.

You will probably use the first method frequently, the second less often, and the last two methods rarely. Why go to the trouble of parsing a URL into its constituent parts when the URL class will do it for you? When you create a URL object, it doesn't open a network connection automatically. That doesn't happen until the object needs to open the connection to satisfy some other request. For example, in the previous code fragment, the URL object connected to host www.utdallas.edu when the getContent call was made. You might be wondering how to use the document once you fetch it, because the page variable in the example was declared as an Object. The actual type of object that is returned is determined by the data format of the document. If the URL points to an image in GIF format, for example, the object returned will be an Image object. Usually, when you retrieve a URL, you will have some idea of what kind of object you will get. (If you're interested in the details of how the mechanism works, it's explained in Chapter 17, "Network-Extensible Applications with Factory Objects.")

Accessing the Raw URL Stream


If you don't want to get the entire contents of the document all at once, or if you want to operate on the
file:///C:/temp/java%20guru/ch3.htm (2 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

raw byte stream, there is another method. Instead of calling getContent, you can arrange to read the data yourself. The openStream method returns an instance of InputStream from which you can read the document a byte at a time if that suits your needs. By the time the openStream method returns, the protocol has been initialized, and the desired document has been requested; the first byte you read from the input stream will be the first byte of the document. Actually, a lot of the work for handling URLs is done behind the scenes by a URLConnection object. In fact, when you ask for the input stream, the URL object simply asks its URLConnection object for the input stream and returns it to you. If you need to, you can get a direct handle to the URLConnection object associated with a particular URL object by calling the openConnection method. Why would you want to have direct access to the connection object? You might want to learn some additional information about the document-not just the document contents. The URLConnection object has several methods that return such information. Here are a few that are commonly useful: getContentEncoding getContentLength getContentType getExpiration getLastModified The data encoding used for transport The length of the document in bytes The MIME media type of the document The document expiration time The last-modified date of the document

Some protocols can't provide all those values, and the ones that can may not be able to provide them all for every document (for instance, not all documents have an expiration time). Therefore, you should be prepared to take appropriate default action if a particular value is not available.

Posting Data to a URL


There's another reason you may want to manipulate a URLConnection object directly: You may want to post data to a URL, rather than just fetching a document. Web browsers do this with data from online forms, and your applets might use the same mechanism to return data to the server after giving the user a chance to supply information. As of this writing, posting is only supported to HTTP URLs. This interface will likely be enhanced in the future-the HTTP protocol supports two ways of sending data to a URL ("post" and "put"), while FTP, for example, supports only one. Currently, the Java library sidesteps the issue, supporting just one method ("post"). Eventually, some mechanism will be needed to enable applets to exert more control over how URLConnection objects are used for output.

file:///C:/temp/java%20guru/ch3.htm (3 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

To prepare a URL for output, you first create the URL object just as you would if you were retrieving a document. Then, after gaining access to the URLConnection object, you indicate that you intend to use the connection for output using the setDoOutput method: URL gather = new URL("http://www.foo.com/cgibin/gather.cgi"); URLConnection c = gather.openConnection(); c.setDoOutput(true); Once you finish the preparation, you can get the output stream for the connection, write your data to it, and you're done: DataOutputStream out = new DataOutputStream(c.getOutputStream()); out.writeBytes("name=Bloggs%2C+Joe+David&favoritecolor=blue"); out.close(); You might be wondering why the data in the example looks so ugly. That's a good question, and the answer has to do with the limitation mentioned previously: Using URL objects for output is only supported for the HTTP protocol. To be more accurate, version 1.0 of the Java library really only supports output-mode URL objects for posting forms data using HTTP. For mostly historical reasons, HTTP forms data is returned to the server in an encoded format, where spaces are changed to plus signs (+), line delimiters to ampersands (&), and various other "special" characters are changed to three-letter escape sequences. The original data for the previous example, before encoding, was the following: name=Bloggs, Joe David favoritecolor=blue If you know enough about HTTP that you are curious about the details of what actually gets sent to the HTTP server, here's a transcript of what might be sent to www.foo.com if the example code listed previously were compiled into an application and executed: POST /cgi-bin/gather.cgi HTTP/1.0 User-Agent: Java1.0 Referer: http://www.foo.com/cgi-bin/gather.cgi Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Content-type: application/x-www-form-urlencoded Content-length: 43 name=Bloggs%2C+Joe+David&favoritecolor=blue
file:///C:/temp/java%20guru/ch3.htm (4 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

Java takes care of building and sending all those protocol headers for you, including the Contentlength header, which it calculates automatically. The reason you currently can send only forms data is that the Java library assumes that's all you will want to send. When you use an HTTP URL object for output, the Java library always labels the data you send as encoded form data. Once you send the forms data, how do you read the resulting output from the server? The URLConnection class is designed so that you can use an instance for both output and input. It defaults to input-only, and if you turn on output mode without explicitly setting input mode as well, input mode is turned off. If you do both explicitly, however, you can both read and write using a URLConnection: c.setDoOutput(true); c.setDoInput(true); The only unfortunate thing is that, although URLConnection was designed to make such things possible, version 1.0 of the Java library doesn't support them properly. As of this writing, a bug in the library prevents you from using a single HTTP URLConnection for both input and output.

Communication Using Sockets


Occasionally, you might find that network communication using URLs is too inflexible for your needs. URLs are great for document retrieval, but for more complicated tasks it's often easier to just open a connection directly to some server and do the protocol handling yourself. Applets can use the low-level networking facilities of the Java library directly, bypassing the URL-based mechanisms. The Java networking support is based on the socket model. Like the URL class, Java's Socket classes are easy to use. When you create a socket, you supply a host for connection (either a String containing the hostname or an InetAddress) and an integer port number on that host. Simply creating the object causes the connection to be made: Socket comm = new Socket("www.javasoft.com", 80); Either the socket object will successfully create the connection, or it will throw an exception. Once that's done, the Socket class really has only three interesting methods: close getInputStream getOutputStream Closes the connection Gets the input stream for the socket Gets the output stream for the socket

file:///C:/temp/java%20guru/ch3.htm (5 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

Using the stream objects, you can read from the socket or write to it, communicating with the process on the other end of the network connection. As with all InputStream and OutputStream objects, you can use any of the filter streams defined in the java.io package or write your own to help with the I/O you need to do. (See Chapter 5, "Building Special-Purpose I/O Classes," for more details about stream classes.) Once the socket is connected, you can write to it and read from it using the same operations you use on files or other I/O streams. When you are ready to end the communication, you can close the connection. There's really only one other small detail to know about sockets: Each of the constructors can also contain a third parameter in addition to the host and port. The extra parameter is a boolean value that indicates whether you want a stream or a datagram socket. If the third parameter is true, the socket is a stream socket (the default). If it is false, a datagram socket is created. Stream sockets involve a little more overhead than datagram sockets, but with a substantial benefit: Stream sockets are reliable. The connection might be cut off during use, but as long as it's active, bytes are guaranteed to be delivered in the same order they are sent. Datagram sockets, on the other hand, are unreliable: Packets can be dropped, and the recipient will never know they were sent. There are some circumstances where datagram sockets are appropriate, but most Java programmers use stream sockets almost exclusively.

A Socket Redirection Server


What can an applet do if its networking access is restricted? If it can't access the network at all, it might not be very useful. But the more common situation-where an applet is permitted to connect back only to the machine from which it was fetched-although a little inconvenient, isn't a serious barrier if the proper support exists on the source machine. If you are writing applets that need to connect to other machines while they're running and the applets can't get by with connecting only to the source machine, you may want to run a relay server. The relay server process runs on your Web server, accepts socket connections on behalf of your applets, finds out where they really want to connect, and then forwards the connections on to the real destination. This has disadvantages; for instance, it can increase the load on your server machine (and the network nearby) if your applets are used frequently. Nevertheless, it is an effective way of enabling your applets to get access to data when they are not permitted to fetch it directly. The following three code listings show how to build such a server and the applet interface to it. To save space, this implementation is quite simple and crude, and could be improved in many ways, but it does illustrate the concepts. The listings' weaknesses are pointed out along the way, and perhaps you'll be able to improve them to suit your needs. The BouncedSocket Class Listing 3.1 shows the BouncedSocket class, which is a rough replacement for the Socket class.
file:///C:/temp/java%20guru/ch3.htm (6 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

Socket is declared final in the Java library, so this replacement can't be a subclass of the real thing; that's unfortunate, because BouncedSocket would be more useful as a subclass of Socket. BouncedSocket works the same way as Socket, however, so it can be used in the same circumstances.

Listing 3.1. BouncedSocket.java. /* * BouncedSocket.java Vanderburg */ package COM.MCP.Samsnet.tjg; import java.io.*; import java.net.*; /** * A replacement for the Socket class which redirects through a * SocketBounceServer, to connect to hosts which would otherwise * not be allowed. * * @version 1.0, 03 Mar 1996 * @author Glenn Vanderburg */ public class BouncedSocket { // The Socket class is final, so unfortunately I can't extend it. That // means this class can't be used as a drop-in replacement. Oh, well. // The place we *really* want to connect to ... private InetAddress realaddr; private String realhost; private int realport; // The real Socket object which we use to communicate
file:///C:/temp/java%20guru/ch3.htm (7 of 16) [2/3/2003 8:18:32 AM]

1.0 96/03/04 Glenn

Chapter 3 -- Exploiting the Network

private Socket realsock; public final int DEFAULTSERVERPORT = 12223; /** * Creates a new BouncedSocket * @param host The real host that we ultimately want to talk to * @param port The port number on host * @param bouncehost The host where the SocketBounceServer is running * @param bounceport The SocketBounceServer port */ BouncedSocket(String host, int port, String bouncehost, int bounceport) throws IOException, UnknownHostException { realsock = new Socket(bouncehost, bounceport); DataOutputStream out = new DataOutputStream(realsock.getOutputStream()); DataInputStream in = new DataInputStream(realsock.getInputStream()); out.writeBytes("Host: " + host + "\nPort: " + port + "\n"); out.flush(); String ack = in.readLine(); if (ack.equals("UnknownHost\n")) { throw new UnknownHostException(host); } else if (ack.startsWith("IOException: ")) { throw new IOException(ack.substring(13)); } else if (ack.startsWith("Connected: ")) { realaddr = InetAddress.getByName(host); } else { throw new IOException(ack); } }

file:///C:/temp/java%20guru/ch3.htm (8 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

/** * Gets the address to which the socket is connected. */ public InetAddress getInetAddress() { return realaddr; } /** * Gets the remote port to which the socket is connected. */ public int getPort() { return realport; } /** * Gets the local port to which the socket is connected. */ public int getLocalPort() { return realsock.getLocalPort(); } /** * Gets an InputStream for this socket. */ public InputStream getInputStream() throws IOException { return realsock.getInputStream(); } /** * Gets an OutputStream for this socket. */ public OutputStream getOutputStream() throws IOException { return realsock.getOutputStream(); } /** * Closes the socket. */ public synchronized void close() throws IOException { realsock.close(); }

file:///C:/temp/java%20guru/ch3.htm (9 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

/** * Converts the Socket to a String. */ public String toString() { return "BouncedSocket[addr=" + realaddr + ",port=" + realport + ",localport=" + realsock.getLocalPort() + " via SocketBounceServer, addr=" + realsock.getInetAddress() + ",port=" + realsock.getPort() + "]"; } }

A BouncedSocket uses a real socket to communicate with the server, but it needs extra variables to store information about the real goal of the communication. Like the real Socket class, BouncedSocket initializes the connection when the object is created. It connects to the server and uses a very simple protocol to tell the server where to connect. The server returns an indicator of the success or failure of its own connection attempt. If something went wrong, BouncedSocket throws an exception; otherwise, the constructor returns, and the connection is established. All the real work of BouncedSocket is done either in the constructor or by the real Socket object. The rest of the methods simply supply information about the connection or forward socket operations to the real socket. The SocketBounceServer Class Listing 3.2 shows the server side of the operation: the SocketBounceServer class. This is the simplest part, largely because it uses a helper class, SocketBouncer, to do most of the work.

Listing 3.2. SocketBounceServer.java. /* * SocketBounceServer.java Vanderburg */ package COM.MCP.Samsnet.tjg;

1.0 96/03/04 Glenn

file:///C:/temp/java%20guru/ch3.htm (10 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

import java.io.IOException; import java.net.*; /** * A server which forwards socket operations to a host which may * not be accessible to another host. * * @version 1.0, 03 Mar 1996 * @author Glenn Vanderburg */ public class SocketBounceServer { static int portnum = 122223; public static void main (String args[]) { if (args.length == 1) { portnum = Integer.valueOf(args[0]).intValue(); } try { ServerSocket listener = new ServerSocket(portnum); while (true) { Socket connection = listener.accept(); Thread t = new Thread(new SocketBouncer(connection)); t.start(); } } catch (IOException e) { System.err.println("IO Error creating listening socket on port " + portnum); return; } } }

file:///C:/temp/java%20guru/ch3.htm (11 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

SocketBounceServer is a stand-alone application rather than an applet. It creates a ServerSocket so that it can camp on a port and wait for clients to connect. Each time a connection is accepted, the server simply creates a new SocketBouncer instance to handle that particular connection, starts the SocketBouncer running in its own thread, and goes back to wait for another connection. The SocketBouncer Class The SocketBouncer class is the interesting part of the server side. In reality, a single SocketBouncer instance handles the communication in only one direction, and it takes two of them to handle one client. The first one is responsible for the rest of the connection setup. It must find out from the client which machine to connect to, make the new connection, and then create the other SocketBouncer object (also in a separate thread, to avoid deadlocks). Only then can it begin forwarding data from the client. Listing 3.3 shows the code for SocketBouncer.

Listing 3.3. SocketBouncer.java. /* * SocketBouncer.java Vanderburg */ package COM.MCP.Samsnet.tjg; import java.io.*; import java.net.*; /** * Handles bouncing for one BouncedSocket client, in one direction only. * * @version 1.0, 03 Mar 1996 * @author Glenn Vanderburg */ public class SocketBouncer implements Runnable { private Socket readsock; private Socket writesock;

1.0 96/03/04 Glenn

file:///C:/temp/java%20guru/ch3.htm (12 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

public SocketBouncer (Socket readsock) { this.readsock = readsock; } public SocketBouncer (Socket readsock, Socket writesock) { this.readsock = readsock; this.writesock = writesock; } public void run () { if (writesock == null) { String host; int port; DataInputStream in; DataOutputStream out; try { in = new DataInputStream(readsock.getInputStream()); out = new DataOutputStream(readsock.getOutputStream()); String line = in.readLine(); if (line.startsWith("Host: ")) { host = line.substring(6); } else { out.writeBytes("IOException: expecting hostname\n"); out.flush(); readsock.close(); return; } line = in.readLine(); if (line.startsWith("Port: ")) { port = Integer.valueOf(line.substring(6)).intValue(); } else { out.writeBytes("IOException: expecting
file:///C:/temp/java%20guru/ch3.htm (13 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

port number\n"); out.flush(); readsock.close(); return; } try { writesock = new Socket(host, port); } catch (UnknownHostException e) { out.writeBytes("UnknownHost\n"); throw e; } out.writeBytes("Connected: " + writesock.getInetAddress() + "\n"); } catch (IOException e) { return; } finally { try { readsock.close(); if (writesock != null) { writesock.close(); } } catch (Throwable t) { } } Thread t = new Thread(new SocketBouncer(writesock, readsock)); t.start(); } try { InputStream in = readsock.getInputStream(); OutputStream out = writesock.getOutputStream(); byte b[] = new byte[32768]; int l;

file:///C:/temp/java%20guru/ch3.htm (14 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

while ((l = in.read(b)) > 0) { out.write(b, 0, l); } out.close(); } catch (IOException e) { } finally { try { readsock.close(); } catch (Throwable t) { } } } }

SocketBouncer handles the server end of the simple protocol that was introduced in BouncedSocket. That protocol is just one of the weak points of this example implementation. It is clumsy, and the error handling is poor. Furthermore, once the connection is completely established, there is no way for the server to communicate information about error conditions to the BouncedSocket object on the client side so that it can throw an appropriate exception there. A more thorough, robust socket proxy protocol would be a better choice. Another weakness is that the example doesn't check for sockets that have been idle for a long period of time. If a network connection is broken, one or more of the threads in the server might wait for a very long time. In spite of these weaknesses, the example implementation illustrates the basic concepts of a connectionforwarding server-possibly the solution to your applet's communication needs.

Summary
Applets can use the Java library's networking classes to get help from remote servers, allowing them to perform useful tasks. Applets can use URLs to fetch resources from standard network servers (such as HTTP servers or FTP servers), and the resources can be typed media objects or simply streams of character data. In some situations, a URL can be used to send information back from the applet to the server. For more general client/server interactions, the applet can use a socket to perform complicated interactions with specialized servers that perform a part of the applet's function.
file:///C:/temp/java%20guru/ch3.htm (15 of 16) [2/3/2003 8:18:32 AM]

Chapter 3 -- Exploiting the Network

Current applet security restrictions allow an applet to make network connections only to the machine from which the applet was loaded, but with the help of a server on that machine, an applet can effectively connect to any machine. This chapter contains an example of such a socket relay server.

file:///C:/temp/java%20guru/ch3.htm (16 of 16) [2/3/2003 8:18:32 AM]

Chapter 4 -- Using Java's Audio Classes

Chapter 4
Using Java's Audio Classes

CONTENTS
q q q q q

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

Audio is an area of multimedia that has stirred up a lot of excitement on the Web. It still isn't clear how far audio will permeate the Web world, but there is little doubt that it will play a vital role in the future of Java. As a Web communication tool, audio is extremely engaging and can often grab your attention even when visual cues fall short. Unfortunately, the audio support in Java 1.0 is still in its infancy. Java 1.0 is currently limited to playing audio clips in the Sun AU file format. In this chapter, you learn the basics about digital audio, along with how the current version of Java provides audio support. You then put together a pretty neat sample applet and finish with a glimpse at where future Java audio extensions might be headed.

Digital Audio Fundamentals


When a microphone converts sound waves to voltage signals, the resulting signal is an analog (or continuous) signal. Because computers are digital machines, it is necessary to convert this analog signal to a digital signal for a computer to process. Analog to digital (A/D) converters handle the task of converting analog signals to digital signals, which is also referred to as sampling. The process of converting an analog signal to a digital signal doesn't always yield exact results. How similarly a digital wave matches its analog counterpart is determined by the frequency at which it is sampled, as well as the amount of information stored at each sample. To sample a sound, you store the amplitude of the sound wave at regular intervals. Figure 4.1 shows how an analog sound wave is converted to a digital wave by sampling the sound at regular intervals. Notice in
file:///C:/temp/java%20guru/ch4.htm (1 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

Figure 4.1 that the digital representation of the analog sound wave is not a very good one. By taking samples at more frequent intervals, the digital signal will come closer to approximating the analog signal. Figure 4.1 : An analog sound wave and its digital representation. When sampling sounds, the rate (frequency) at which the sound is sampled is very important, as well as how much data is stored for each sample. The unit of measurement for frequency is Hertz (Hz), which specifies how many samples are taken per second. In Java 1.0, the only supported sound frequency is 8000 Hz, which means that there are 8000 samples per second. Although it sounds like a lot, this frequency actually results in a fairly low-quality sound. To understand why, consider the fact that the frequency for CD quality audio is 44000 Hz. Note The limitations on sound quality imposed by Java are really a reflection of the underlying AU sound format, which is discussed in a moment. When Java widens its support for other sound formats, these limitations will likely disappear.

The amount of data stored per sample determines the number of discrete amplitudes that a digital signal can represent. Obviously, the wider range of amplitudes represented by the digital signal, the closer the original wave is approximated. In Java 1.0, the sample data width is limited to 8 bits. A wave sampled at 8 bits has 256 discrete amplitude levels (2 ^ 8).

Java Audio Support


The current audio support in Java comes in the form of a single class, AudioClip, which is part of the applet package. The AudioClip class models a digital audio sound clip in the AU file format, which provides support for 8000 Hz mono 8-bit Law encoded audio clips. This is a fairly low-quality sound format and severely limits Java in providing professional audio capabilities. However, in the current context of the Web, just being able to play AU audio clips in Java is plenty for many applets. Note Law is the name of a sound data encoding mechanism that provides a 2:1 compression ratio. Law has very strong crossplatform support, which is probably one of the reasons Sun chose the AU sound format as Java's first supported format.

file:///C:/temp/java%20guru/ch4.htm (2 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

The AudioClip class is an abstract class, so you can't directly create instances of it. You create AudioClip objects by calling the getAudioClip method of the Applet class. You'll learn more about that in a moment, but first take a look at the methods in the AudioClip class:
q q q

public abstract void play() public abstract void loop() public abstract void stop()

As you can see, these methods are very high-level and quite simplistic. It doesn't get much easier than just calling play to play an audio clip. You may be a little intrigued by the loop method, which plays an audio clip repeatedly in a loop until you explicitly call stop to stop it. The loop method is useful in cases where you have a clip that needs to be repeated, such as a helicopter rotor sound or a music clip. Tip When using looped sounds, it's important to make sure that the sound begins and ends in a such a way that it isn't noticeable when the looping occurs. For example, using the helicopter rotor sound as an example, the sound itself might just be one "chop" of the rotor blades. To get the desired effect of a continuous rotor sound, you need to loop the sound repeatedly. However, if the end of the sound doesn't blend well with the beginning, there will be a noticeable pop when the looping takes place. Considering the fact that the looping is probably occurring very rapidly, the end result is that it probably doesn't sound like a helicopter. The only real solution to this problem is carefully hand editing the sound and using a trial-anderror approach. One particularly neat usage of looped audio is playing music. Because Java currently provides no support for music sound formats such as MIDI, you often must play music as looped sounds. The looping aspect comes about because you usually will want to avoid creating long sound files-they take up so much space and therefore take too long to load. Rather, you can create smaller music sounds that can be looped to give the effect of a longer piece of music. Again, you must be very careful to make the loop transition unnoticeable, or the effect will be ruined.

I'd love to give you more juicy details about the AudioClip class, but there just isn't any more to it. By understanding the three methods implemented by the AudioClip class, you are practically already a Java audio guru.

file:///C:/temp/java%20guru/ch4.htm (3 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

The missing link, however, is how to create AudioClip objects. Recall that you use the Applet class's getAudioClip method to get an AudioClip object. Actually, there are two versions of getAudioClip, which are defined as the following:
q q

public AudioClip getAudioClip(URL url) public AudioClip getAudioClip(URL url, String name)

The only difference between these two methods is whether or not the URL parameter contains the name of the audio clip. In the first version, it is assumed that the URL contains the complete name; the second version requires a separate name parameter. You typically will use the second version, because you can easily retrieve the URL of the applet or the HTML document in which the applet is embedded. You do this by using either the getCodeBase or getDocumentBase methods of Applet, like this: AudioClip clip1 = getAudioClip(getCodeBase(), "sound1.au"); AudioClip clip2 = getAudioClip(getDocumentBase(), "sound2.au"); You do not need to use an AudioClip object to play sounds; you are required to create an AudioClip object only if you want to play looped sounds. For normal sounds, you also have the option of using one of the play methods in the Applet class:
q q

public void play(URL url) public void play(URL url, String name)

These play methods take the same parameters as the getAudioClip methods. In fact, the play methods in Applet simply call getAudioClip to get an AudioClip object, followed by a call to the audio clip's play method. This is evident in Listing 4.1, which shows the Java 1.0 source code for the Applet play methods.

Listing 4.1. The Java 1.0 Applet play() methods. public void play(URL url) { AudioClip clip = getAudioClip(url); if (clip != null) { clip.play(); } } public void play(URL url, String name) { AudioClip clip = getAudioClip(url, name);
file:///C:/temp/java%20guru/ch4.htm (4 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

if (clip != null) { clip.play(); } }

Playing Audio In Java


Even though it looks simple, and you could probably turn out your own Java audio applet at this point, let's look at how Java audio can be used to create an interesting applet. Figure 4.2 shows a screen shot of the OnTheFarm applet, which uses the Java AudioClip class to generate some entertaining, if not rustic, results. Figure 4.2 : The OnTheFarm sample applet. The screen shot of OnTheFarm doesn't quite convey the real purpose of the applet, so at this point you should run it for yourself off the CD-ROM to get the real effect. In case you want to cut to the chase and skip the farm experience, OnTheFarm plays a looped music sound clip along with randomly playing various farm animal sounds. Listing 4.2 contains the complete source code for OnTheFarm.

Listing 4.2. The OnTheFarm sample applet. // OnTheFarm Class // OnTheFarm.java // Imports import java.applet.*; import java.awt.*; import java.util.Random; public class OnTheFarm extends Applet implements Runnable { AudioClip clip[] = new AudioClip[8]; Thread thread; Random rand = new Random(System.currentTimeMillis()); public void init() { // Load the sounds clip[0] = getAudioClip(getDocumentBase(), "Res/Hillbilly.au");
file:///C:/temp/java%20guru/ch4.htm (5 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

clip[1] = getAudioClip(getDocumentBase(), "Res/Cow.au"); clip[2] = getAudioClip(getDocumentBase(), "Res/Duck.au"); clip[3] = getAudioClip(getDocumentBase(), "Res/Goat.au"); clip[4] = getAudioClip(getDocumentBase(), "Res/Hen.au"); clip[5] = getAudioClip(getDocumentBase(), "Res/Horse.au"); clip[6] = getAudioClip(getDocumentBase(), "Res/Pig.au"); clip[7] = getAudioClip(getDocumentBase(), "Res/Rooster.au"); } public void start() { if (thread == null) { thread = new Thread(this); thread.start(); } } public void stop() { if (thread != null) { thread.stop(); thread = null; } } public void run() { while (Thread.currentThread() == thread) { // Loop the music sound clip[0].loop(); while (true) { // Wait three seconds try Thread.sleep(3000); catch (InterruptedException e) break; // Play an animal sound clip[(rand.nextInt() % 3) + 4].play(); } } }
file:///C:/temp/java%20guru/ch4.htm (6 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

public void paint(Graphics g) { Font font = new Font("TimesRoman", Font.PLAIN, 20); FontMetrics fm = g.getFontMetrics(font); String str = new String("On the farm..."); g.setFont(font); g.drawString(str, (size().width - fm.stringWidth(str)) / 2, ((size().height - fm.getHeight()) / 2) + fm.getAscent()); } }

OnTheFarm has three member variables: an array of AudioClip objects, a Thread object, and a Random object. The AudioClip objects are used to hold each different sound. The Thread object is used to manage the main applet thread, which handles looping the music sound and playing the random animal sounds. Finally, the Random object is used to generate random numbers that determine which animal sounds are played. Note A thread is necessary here because you want the music sound to be looped continuously. The only way to guarantee that the looping is getting enough attention is to give it its own thread. This is a standard technique for looping sounds in Java.

The init method handles loading the audio clips by way of the getAudioClip method. The start and stop methods are standard thread management methods. All the action takes place in the run method, where the music sound is first looped. The random animal sounds are then played inside an infinite while loop. Notice that the thread is put to sleep for three seconds between sounds, which keeps the sounds from overlapping too much and makes them easier to hear. The infinite while loop is automatically terminated when the thread is destroyed, which occurs when the applet terminates. Note

file:///C:/temp/java%20guru/ch4.htm (7 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

There's something taking place in the OnTheFarm sample applet that you may be taking for granted: sound mixing. The sound support in Java 1.0 has built-in sound mixing, which enables you to play multiple sounds at once. Java handles all the details of overlaying and playing multiple sounds, which is no small feat. Although you have learned about the downside of the current audio support in regard to its simplicity and limitations, built-in sound mixing is a big benefit.

The only other method in OnTheFarm is paint, which simply draws the message On the farm... centered in the applet window. That's all there is to creating a virtual audio farm in Java.

The Future of Java Audio


There is no arguing the limitations of audio in the current release of Java. It is very clear that the Java architects focused on more critical aspects of the language and class libraries in this first release, which is just as well. As nice as it would be to have fancy audio support right now, I would certainly rather opt for stronger security and portability at this stage, which is apparently the same logic used by Sun. Knowing the current limitations of Java audio, what might the future hold? Sun has promised more complete audio features in a future release of Java that will include support for MPEG and CD quality audio. There will also no doubt be additional sound formats supported, such as Windows WAV files. As far as future Java audio classes go, it is likely that Sun will introduce a stream-based set of audio classes. In this scenario, an audio clip would correspond to a stream of audio data. To play an audio stream, you would simply write the stream of data to an audio channel. An audio device, which corresponds to your audio hardware, would contain multiple audio channels. Sun already uses this same approach in the low-level classes that support the AudioClip class. These low-level classes are currently undocumented and unsupported, but it is likely that they will form the basis of future Java audio classes. If you want to check these classes out for yourself, they can be found in the Classes\Sun\audio directory under your main Java directory. You may have to expand the compressed Classes file to install these classes. You can then use the javap tool to look at the data and methods for these classes. For more information on how to use the javap tool to examine Java classes, check out Chapter 25, "Class Organization and Documentation Tools."

Summary
In this chapter, you learned all about audio in Java, including the fundamentals of digital audio and the
file:///C:/temp/java%20guru/ch4.htm (8 of 9) [2/3/2003 8:18:36 AM]

Chapter 4 -- Using Java's Audio Classes

means by which Java enables you to play it. You learned about the AudioClip class and how to use it to play audio files in the AU format. You also learned that although it is limited in its current state, audio in Java is nevertheless available and quite usable. Even more importantly, you saw how adding audio to Java applets is simple, consisting of only a couple of method calls. This chapter concluded with a brief discussion of what the future might hold for Java audio. It isn't clear yet how Sun will supplement Java audio in the future, but it has shown a definite intent. A more powerful Java audio is on the horizon. Until then, you should try to make the most of AU sounds and the AudioClip class. You saw in the OnTheFarm sample applet how you can still have fun with Java audio as it is-so go have some!

file:///C:/temp/java%20guru/ch4.htm (9 of 9) [2/3/2003 8:18:36 AM]

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

Chapter 5
Building Special-Purpose I/O Classes

CONTENTS
q q q q

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:/temp/java%20guru/ch5.htm (1 of 23) [2/3/2003 8:18:40 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:/temp/java%20guru/ch5.htm (2 of 23) [2/3/2003 8:18:40 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:/temp/java%20guru/ch5.htm (3 of 23) [2/3/2003 8:18:40 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:/temp/java%20guru/ch5.htm (4 of 23) [2/3/2003 8:18:40 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:/temp/java%20guru/ch5.htm (5 of 23) [2/3/2003 8:18:40 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:/temp/java%20guru/ch5.htm (6 of 23) [2/3/2003 8:18:40 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:/temp/java%20guru/ch5.htm (7 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (8 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (9 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (10 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (11 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (12 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (13 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (14 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (15 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (16 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (17 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (18 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (19 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (20 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (21 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (22 of 23) [2/3/2003 8:18:41 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:/temp/java%20guru/ch5.htm (23 of 23) [2/3/2003 8:18:41 AM]

Chapter 6 -- Effective Use of Threads

Chapter 6
Effective Use of Threads

CONTENTS
q q q q

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:/temp/java%20guru/ch6.htm (1 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (2 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (3 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (4 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (5 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (6 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (7 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (8 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (9 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (10 of 30) [2/3/2003 8:18:46 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():
q

q q q

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

file:///C:/temp/java%20guru/ch6.htm (11 of 30) [2/3/2003 8:18:46 AM]

Chapter 6 -- Effective Use of Threads


q q q

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:/temp/java%20guru/ch6.htm (12 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (13 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (14 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (15 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (16 of 30) [2/3/2003 8:18:46 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:
q q q q q q

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:/temp/java%20guru/ch6.htm (17 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (18 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (19 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (20 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (21 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (22 of 30) [2/3/2003 8:18:46 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.
q q q q q

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:/temp/java%20guru/ch6.htm (23 of 30) [2/3/2003 8:18:46 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:
q q q q

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:/temp/java%20guru/ch6.htm (24 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (25 of 30) [2/3/2003 8:18:46 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:
q

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

file:///C:/temp/java%20guru/ch6.htm (26 of 30) [2/3/2003 8:18:46 AM]

Chapter 6 -- Effective Use of Threads

q q q q

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:/temp/java%20guru/ch6.htm (27 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (28 of 30) [2/3/2003 8:18:46 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:/temp/java%20guru/ch6.htm (29 of 30) [2/3/2003 8:18:46 AM]

Chapter 6 -- Effective Use of Threads

file:///C:/temp/java%20guru/ch6.htm (30 of 30) [2/3/2003 8:18:46 AM]

Chapter 7 -- Concurrency and Synchronization

Chapter 7
Concurrency and Synchronization

CONTENTS
q q q q q q q

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:
q q q q

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:/temp/java%20guru/ch7.htm (1 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (2 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (3 of 31) [2/3/2003 8:18:53 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:
q q q q

Critical sections Semaphores Mutexes Database record locking

file:///C:/temp/java%20guru/ch7.htm (4 of 31) [2/3/2003 8:18:53 AM]

Chapter 7 -- Concurrency and Synchronization


q

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:/temp/java%20guru/ch7.htm (5 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (6 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (7 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (8 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (9 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (10 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (11 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (12 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (13 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (14 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (15 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (16 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (17 of 31) [2/3/2003 8:18:53 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:
q

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:/temp/java%20guru/ch7.htm (18 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (19 of 31) [2/3/2003 8:18:53 AM]

Chapter 7 -- Concurrency and Synchronization

following:
q q

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:
q

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:/temp/java%20guru/ch7.htm (20 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (21 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (22 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (23 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (24 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (25 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (26 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (27 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (28 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (29 of 31) [2/3/2003 8:18:53 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:/temp/java%20guru/ch7.htm (30 of 31) [2/3/2003 8:18:53 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:
q q

q q q

q q q

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:/temp/java%20guru/ch7.htm (31 of 31) [2/3/2003 8:18:53 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

Chapter 8
All About GridBaglayout and Other Layout managers

CONTENTS
q q q q q

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

The AWT library includes classes for automated layout management of windows. The layout manager classes are a set of classes that lay out widgets on forms and windows. Moreover, these classes recompute the layout when the user resizes a window. You might be asking yourself why an automated layout manager is needed. Why not just use a layout editor to position widgets on the window? The answer is that the layout manager not only helps you create an initial layout. If you are developing a Java program where the user must be allowed to resize windows, the layout manager classes can minimize your work, because you don't have to write your own code for recomputing the layout after resize events. For static, nonresizeable windows, you might be better off using a layout editor (such as a layout editor provided by a Java development environment). The most powerful layout manager is the GridBagLayout class. This manager provides advanced functions for specifying widget resizing and repositioning as the window size changes. Before you explore the GridBagLayout class and other layout manager classes, let's first examine the concept of automated layout.

Automated Layout and the AWT Layout Manager


Although you can use static layouts defined with, for instance, layout editors for windows of fixed size, there are still reasons for taking the layout manager approach. Because Java is designed to be platformindependent, it can be difficult to find a layout that is acceptable on every platform. Components look

file:///C:/temp/java%20guru/ch8.htm (1 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

somewhat different on each platform because the AWT uses native widgets in the window system of the platform. Layout managers can assist you in creating well-designed user interfaces on every platform by recomputing the layout dynamically. Selecting an appropriate layout manager is sometimes difficult. The AWT includes a set of standard layout managers, which you can configure to a certain extent. The best strategy often is to use the simplest layout manager that is sufficient for your layout task. To take advantage of a layout manager, you should instantiate a layout manager class, such as FlowLayout, GridBagLayout, and so on. The next step is to associate it with the container on which it should operate. The method setLayout(LayoutManager) sets the layout manager for a container. For some layout managers, you can specify the layout strategy by providing parameters to the constructor of the layout manager or by setting parameters in the layout manager. Once you have set the layout manager for a container, the latter will invoke the layout manager just before the components are drawn. Resizing windows and adding components to the container will cause the layout manager to recompute the layout. If one of the basic layout classes in AWT is sufficient, you may want to use it instead of GridBagLayout. If you need a more sophisticated layout than what is provided by a single layout manager, it is possible to use a combination of several layout managers, where components of an overall layout are themselves containers with their own layout managers.

Basic Layout Classes


In addition to GridBagLayout, the AWT includes four basic layout managers: FlowLayout, BorderLayout, GridLayout, and CardLayout. These layout managers are simpler to use, but less powerful than GridBagLayout.

FlowLayout
The FlowLayout class is a straightforward layout manager that lays out components linewise from left to right. When a line of components is filled, FlowLayout creates a new line and continues laying out components on the next line. The layout strategy is similar to the way a word processor wraps text on a page. Use the FlowLayout class in situations where it is important to line up components horizontally and where you want the layout manager to wrap the lines for you. Figure 8.1 shows a sample layout produced by FlowLayout. Figure 8.1 : Sample FlowLayout. The constructors for FlowLayout allow several layout options. You can control the alignment of components by specifying an alignment code (which is defined as a constant in the FlowLayout class). The possible alignments are FlowLayout.CENTER, FlowLayout.LEFT, and
file:///C:/temp/java%20guru/ch8.htm (2 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

FlowLayout.RIGHT. You also can specify the horizontal and vertical gaps between components. Use the constructor FlowLayout() to create a FlowLayout with a centered alignment. The constructor FlowLayout(int align) creates a FlowLayout with the specified alignment, and the constructor FlowLayout(int align, int hgap, int vgap) creates a FlowLayout with the specified alignment, horizontal gap, and vertical gap, respectively.

BorderLayout
The BorderLayout class enables you to specify where on the border of a container each component should be placed. By naming the component members North, South, West, East, and Center, you can control the location of the components. Specify the component names with the add() method when you add components to the container. The BorderLayout class lays out the North, South, West, and East components using their preferred sizes. BorderLayout resizes the Center component to fill the remaining center space. Use BorderLayout when you need to group components on the borders of a container, such as positioning scrollbars on the bottom and right side of a container. Figure 8.2 shows a sample layout generated by BorderLayout. Figure 8.2 : Sample Borderlayout. There are two constructors for BorderLayout: BorderLayout() and BorderLayout(int hgap, int vgap). The first constructor creates a basic BorderLayout, and the second creates a BorderLayout with horizontal and vertical gaps.

GridLayout
The GridLayout class lays out components as a matrix according to a grid. GridLayout places a component on each position in the grid. The order in which you add components to the container is important, because GridLayout fills the grid from left to right and from top to bottom. Use the GridLayout when you need a matrix-like layout, such as a matrix of TextFields. Figure 8.3 shows a sample GridLayout. Figure 8.3 : Sample GridLayout. GridLayout has the constructors GridLayout(int rows, int cols) and GridLayout(int rows, int cols, int hgap, int vgap). The first creates a GridLayout with the specified number of rows and columns. The second enables you also to specify the horizontal and vertical gaps. Specifying the number of columns is important, because GridLayout uses this information when placing components on the grid.

CardLayout
file:///C:/temp/java%20guru/ch8.htm (3 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

The CardLayout class enables you to define a set of alternative cards that are displayed in the container. Each card is typically a container that can include several components. Unlike other layout mangers, CardLayout does not lay out components geometrically. It shows and hides the appropriate components but does not change the location of them. Use the CardLayout class when you need to display alternative sets of components on a panel, such as when you are implementing slide-show applets and preference panels with several alternative forms. Because CardLayout shows and hides containers, you must inform the layout manager when you want to change the current card. For this purpose, CardLayout provides several methods for controlling the cards programmatically. Table 8.1 shows the control methods that CardLayout supports. You can create a panel of buttons that control the panel by calling these methods. Table 8.1. CardLayout methods. Layout Manager Method first(Container) last(Container) next(Container) previous(Container) show(Container, String) Description Show the first card Show the last card Show the next card Show the previous card Show a named card Parameters The parent container The parent container The parent container The parent container The parent container and the name of the card

A Layout Manager Example


Let's consider an example of how we can use basic layout managers. By combining several layout managers, we can achieve quite complex layouts. Figure 8.4 shows a sample window layout, which is produced by a combination of four layout managers. Figure 8.4 : A sample layout created by a combination of layout managers. Here is the code that creates this window: import import import public java.awt.*; java.util.*; java.applet.Applet; class ComboEx extends Applet {

public void init() { /* Use BorderLayout as the overall layout */ setLayout(new BorderLayout());

file:///C:/temp/java%20guru/ch8.htm (4 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

/* Add the table of name, e-mail, and URL */ Panel t = new Panel(); t.setLayout(new GridLayout(4,3)); // Add column headers... t.add(new Label("Name")); t.add(new Label("E-mail")); t.add(new Label("URL")); // Add nine text fields... for (int i = 1; i <= 9; i++) t.add(new TextField()); add("Center",t); /* Add the ranking numbers to the left */ Panel r = new Panel(); r.setLayout(new GridLayout(4,1)); r.add(new Label("No.")); r.add(new Label("1")); r.add(new Label("2")); r.add(new Label("3")); add("West",r); /* Add control buttons at the bottom */ Panel control = new Panel(); control.setLayout(new FlowLayout()); control.add(new Button(" OK ")); control.add(new Button("Cancel")); control.add(new Button("Revert")); add("South", control); } public static void main(String args[]) { Frame f = new Frame("Layout Combination Example"); ComboEx ce = new ComboEx(); ce.init(); f.add("Center", ce) f.pack(); f.resize(f.preferredSize()); f.show(); } } Note that BorderLayout controls the overall layout of the window. The left position of this layout is a GridLayout with the "No." label and the numbers 1-3. The center position is a second GridLayout with the column labels and nine text-entry fields. Finally, the south position is a

file:///C:/temp/java%20guru/ch8.htm (5 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

FlowLayout with the control buttons for the "OK", "Cancel", and "Revert" operations.

The GridBagLayout Class


The GridBagLayout class is a powerful layout manager that lays out components based on a grid. You can think of GridBagLayout as an advanced version of GridLayout. The major difference between GridLayout and GridBagLayout is that GrigBagLayout supports components of different sizes, and you can specify layout options for each component. Use the GridBagLayout when you need tabular layouts (or layouts that can be thought of as matrices) and when it is important to specify the resizing behavior of each component.

Basic Concepts
GridBagLayout supports a rectangular grid of cells. Each component of a GridBagLayout can occupy one or more cells. Because GridBagLayout enables you to specify layout properties for each component, you must associate components managed by GridBagLayout with instances of the class GridBagConstraints. These instances specify how GridBagLayout should lay out components in the matrix. Let's examine how you can set up a GridBagLayout. The constructor GridBagLayout() creates a GridBagLayout instance. You can then use the method setConstraints(Component, GridBagConstraints) to associate components with constraints. In addition to the setConstraints(Component, GridBagConstraints) method, GridBagLayout provides a set of methods to manage constraints. Table 8.2 shows the constraint management methods. Table 8.2. Constraint management methods for GridBagLayout. Layout Manager Method setConstraints(Component, GridBagConstraints) Description Associate constraints with a component Get the constraints for a component (a copy of the GridBagConstraints instance is returned) Parameters The component andthe constraints The component to get constraints from

getConstraints(Component)

Get the constraints for a component (the actual lookupConstraints(Component) GridBagConstraints instance is returned)

The component to get constraints from

Typically, you set up instances of the GridBagConstraints class before associating them with the components using the setConstraints(Component, GridBagConstraints) method.

file:///C:/temp/java%20guru/ch8.htm (6 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

GridBagConstraints
The GridBagConstraints class enables you to specify constraints for each component of the container. The GridBagConstraints class provides several options for specifying the behavior of member components. You specify the constraints by setting instance variables of the GridBagConstraints object. GridBagConstraints has three major variable categories that execute the following:
q q q

Control the position and size of a component on the grid Specify the size and location of the component in its display area Add padding to components and display areas

The variables gridx and gridy control the component position on the grid (the cell in which the component is placed). The variables gridwidth and gridheight determine the component size in terms of grid cells. The variables fill and anchor control the position of a component within its display area. The variables ipadx, ipady, and insets specify the padding. Finally, the variables weightx and weighty control the distribution of space among cells. Here is a detailed description of each variable: gridx, gridy Use these variables to specify explicitly where on the grid the layout manager should place the component. The upper-left cell is the origin, which has the location gridx = 0, gridy = 0. The default value is GridBagConstraints.RELATIVE, which specifies that the component should be placed at the next location relative to the last component added to the container. (In this case, the next location is just to the right, or just below, the previous component.) gridwidth, gridheight Use gridwidth and gridheight to specify the size of the components display area. You specify this size in number of grid cells. For example, the values gridwidth = 2 and gridheight = 1 mean that the component display area is two cells wide and one cell high in the grid. The default value of gridwidth and gridheight is 1. To specify that a component is the last one in its row or column, you can set gridwidth and gridheight to GridBagConstraints.REMAINDER. You can use the value GridBagConstraints.RELATIVE to indicate that the component is next to the last one in the row or column. fill Use fill to specify how GridBagLayout should resize components when the display area is larger than the component. Set fill to GridBagConstraints.HORIZONTAL to make the component

file:///C:/temp/java%20guru/ch8.htm (7 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

sufficiently wide to fill its display area (without changing the component height). Set fill to GridBagConstraints.VERTICAL to make the component sufficiently tall to fill its display area (without changing the component width). Set fill to GridBagConstraints.BOTH to make the component fill the display area completely. Thus, the value GridBagConstraints.BOTH is a combination of GridBagConstraints.HORIZONTAL and GridBagConstraints.VERTICAL. The default value of fill is GridBagConstraints.NONE, which specifies no fill for the component. anchor Use anchor to specify where a component should be placed if it is smaller than the display area. GridBagLayout attaches the component to the specified location. The following are the possible values: GridBagConstraints.CENTER (default) GridBagConstraints.NORTH GridBagConstraints.NORTHEAST GridBagConstraints.EAST GridBagConstraints.SOUTHEAST GridBagConstraints.SOUTH GridBagConstraints.SOUTHWEST GridBagConstraints.WEST GridBagConstraints.NORTHWEST ipadx, ipady Use ipadx and ipady to enlarge the minimum size of components. GridBagLayout adds ipadx pixels to the left and right of the minimum size of the component. Similarly, GridBagLayout adds ipady pixels to the bottom and top of the minimum size of the component. Thus, GridBagLayout increases the minimum width and height by ipadx*2 and ipady*2 pixels, respectively. Insets Use Insets to specify the minimum border between the component and its display area. The value must be an instance of the class Insets. You can use the constructor Insets(int, int, int, int) to create an Insets instance with top, left, bottom, and right insets. GridBagLayout then inserts the specified space between the edges of the component and its display area. weightx, weighty Use weightx and weighty to specify how GridBagLayout should distribute space. You can use numeric values for weightx and weighty to distribute space among columns (weightx) and rows
file:///C:/temp/java%20guru/ch8.htm (8 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

(weighty). These weights determine how much extra space a row (or column) will get when the container expands. By setting the weightx and weighty values, you control how rows and columns scale. Rows (columns) with larger weights will grow faster than rows (columns) with smaller weights. Typically, weightx and weighty have values between 0.0 and 1.0. The default weight is zero (0.0), which means no growth. When all weights are zero, GridBagLayout places the components together at the center of the container. Thus, GridBagLayout puts space between the grid and the edges of the container. Note that the actual weight for each row (column) is a combination of the weights of each of the components in the row (column). You may find the task of setting up these variables difficult. If you start modifying the values without a clear idea of how they affect the layout, you may find it difficult to get the layout and resizing behavior you want. The key to successful layout creation is to plan ahead and to design the layout before specifying it. Make a mock-up on paper or draw it using a drawing program. Once you are satisfied with the mock-up design, you can proceed with creating a grid on top of the layout. Use this grid as the basis for assigning components to cells and adding components to correct cell positions. Determine how you want each component to behave inside its display area. Do you want the component to fill the area horizontally, vertically, or both? Do you want the component to attach to a certain side or corner of the area? Do you want to enlarge the size of components or to add space between components and the edges of their display areas? When you have answered these questions, you can determine the correct values for the GridBagConstraints variables. After you implement the initial version of your layout specification, you can then redesign your layout incrementally by modifying the variable values.

A GridBagLayout Example
As you learned in the previous section, taking advantage of GridBagLayout and GridBagConstraints is a matter of setting appropriate values for variables. By studying layout examples, you can learn more about how to set the layout variables correctly. Because comprehensive examples of the use of GridBagLayout often get confusing, let's look at a minimal layout for a window with two buttons. Once you understand how the variables in GridBagConstraints work, you can easily use this knowledge to create containers with many components. You will first examine the source code and the resulting window and then learn how you can modify different GridBagConstraints variables to get alternative layouts and resizing behavior. Here is the code for the MinimalGridBag class: import java.awt.*; import java.util.*; import java.applet.Applet; public class MinimalGridBag extends Applet { protected void makebutton(String name,
file:///C:/temp/java%20guru/ch8.htm (9 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

GridBagLayout gridbag, GridBagConstraints c) { Button button = new Button(name); gridbag.setConstraints(button, c); add(button); } public void init() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setLayout(gridbag); c.weightx = 1.0; c.weighty = 1.0; makebutton("Button 1", gridbag, c); c.fill = GridBagConstraints.BOTH; makebutton("Button 2", gridbag, c); } public static void main(String args[]) { Frame f = new Frame("Minimal GridBag Layout Example"); MinimalGridBag mgb = new MinimalGridBag(); mgb.init(); f.add("Center", mgb); f.pack(); f.resize(f.preferredSize()); f.show(); } } Figure 8.5 shows the resulting window from the MinimalGridBag example. Initially, GridBagLayout sizes the container (window) to accommodate buttons 1 and 2. The buttons line up horizontally in a 2-by-1 grid. Figure 8.5 : The layout generated by the MinimalGridBag example (before resizing by the user). When the user enlarges the window, GridBagLayout regenerates the layout based on the GridBagConstraints specification. Figure 8.6 shows the enlarged window. The size of button 1 remains the same because the fill is GridBagConstraints.NONE (the default value). However, GridBagLayout expands button 2 to fill its display area, because fill is set to GridBagConstraints.BOTH. Note that weightx and weighty are set to 1.0 in this example. It is necessary to set them to a nonzero value to enable the grid cells to grow.

file:///C:/temp/java%20guru/ch8.htm (10 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

Figure 8.6 : The layout after the user has enlarged the window. As this example illustrates, it is easy to set up a minimal layout that uses GridBagLayout. The best way to learn more about how the GridBagConstraints variables work is to modify this example yourself and play with different settings. Because you may not currently have access to a computer running Java or have the time to perform these experiments, this chapter presents some of the possible modifications and their result. Let's consider what happens to the layout if you change some of the variable values (by modifying the code, for example). In the remainder of this example, you make some controlled experiments with the variable values where you start with the previous code and vary the value of only one or two variables simultaneously. Figure 8.7 shows what happens if you change fill to GridBagConstraints.HORIZONTAL for button 2 and rerun the example. Button 2 now expands horizontally. (The original version used the value GridBagConstraints.BOTH, which makes the button 2 fill both horizontally and vertically.) Likewise, you can set fill to GridBagConstraints.VERTICAL for button 2. Figure 8.8 shows what happens. You can use different settings for fill to make components, such as lists of items and text fields, expand to accommodate more information as the user enlarges the window. Figure 8.7 : Enlarged window with fill set to GridBagConstraints. HORIZONTAL for button 2. Figure 8.8 : Enlarged window with fill set to GridBagConstraints. VERTICAL for button 2. The anchor variable controls where in the display area a component should be placed. Because the default value for anchor is GridBagConstraints.CENTER, GridBagLayout centers 1 and 2 in the previous examples. Figure 8.9 shows what happens if you set anchor to GridBagConstraints.WEST for button 1 and enlarge the window. (Note that you start from the original example where fill is GridBagConstraints.BOTH.) If you want, you can try other values for anchor to place button 1 on other sides and in one of the corners. Figure 8.9 : Enlarged window with anchor set to GridBagConstraints.WEST for Button 1. Understanding how weightx and weighty work can sometimes be difficult, especially if you start with a complex layout. However, it is much easier if you consider a small example. Basically, the variables weightx and weighty control how cells in the grid scale when the container is resized. By using different values for weightx for the buttons in the example, you can control how the display areas scale when you resize the window. Until now, you set weightx to 1.0 for buttons 1 and 2 to ensure that the display areas will scale. (You also set weighty to 1.0 to ensure vertical scaling.) Figure 8.10 shows what happens if you set weightx to 0.8 for button 1 and 0.2 for button 2 and then enlarge the window. Note that, because button 1 has more weight than button 2, the area for button 1 scales more rapidly than the area for button
file:///C:/temp/java%20guru/ch8.htm (11 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

2. However, the scaling for cells in grids with multiple rows is more complex than this example shows, because the weight for a column is calculated from the weight of all the cells in the column. Figure 8.10 : Enlarged window with weightx set to 0.8 for button 1 and 0.2 for button 2. GridBagConstraints provides variables for adding to the size of components and to add padding space around components. Let's examine what happens if you change these variables. The variables ipadx and ipady specify how much GridBagLayout should add to the size of a component. Figure 8.11 shows the result of adding internal padding to buttons 1 and 2 by setting ipadx and ipady to 50. GridBagLayout expands the cell size of the grid to accommodate the buttons. Figure 8.12 shows the result of enlarging the window in Figure 8.11. Button 1 keeps its size, and button 2 fills its display area. Figure 8.11 : Layout with ipadx set to 50 pixels for buttons 1 and 2 (before enlargement of the window by the user). Figure 8.12 : Enlarged window with ipadx set to 50 pixels for buttons 1 and 2. In addition to adding to the size of widgets, you can instruct GridBagLayout to add external padding to the components. GridBagLayout will then maintain a minimum amount of space around the component. Figure 8.13 shows the result of setting insets to new Insets(20,20,20,20). GridBagLayout inserts 20 pixels of space between the component and its display area. In this case, GridBagLayout expands the cell size to accommodate the padded components. When the user enlarges the window, GridBagLayout maintains the padding space when adjusting the component sizes. Figure 8.14 shows the layout after enlargement of the window. Figure 8.13 : Layout with insets set to 20 pixels on a each side of buttons 1 and 2 (before enlargement of the window by the user). Figure 8.14 : Enlarged window with insets set to 20 pixels on each side of buttons 1 and 2. Once you learn how to use the variables of GridBagConstraints, creating larger layouts is straightforward. To succeed in specifying larger layouts, however, you must carefully plan and design the layout before you begin assigning values to variables of GridBagConstraints.

Creating Your Own Layout Manager


In certain situations, you may want to create your own layout manager. Fortunately, the AWT package enables programmers to implement new layout managers. For instance, if none of the available standard layout manager classes provide the functionality you need, you can develop new layout managers that perform the required task. Because the GridBagLayout class is complex and somewhat difficult to
file:///C:/temp/java%20guru/ch8.htm (12 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

use, you might have some ideas for simple, yet powerful, layout managers that work better for your layout job. You and others can then reuse these layout managers in several applets and applications. There are two basic strategies for creating a layout manager. The first strategy is to subclass a preexisting layout manager. Here, your subclass implements the required functionality by modifying the behavior of the basic layout manager class. The second strategy is to create a new layout manager from scratch. In this approach, you develop your layout manager by creating a class that implements the LayoutManager interface. The advantage of subclassing a standard layout manager class is that you can take advantage of the methods defined in the standard layout manager by inheriting them. However, the design of these standard classes is not very "open." It is difficult to reuse code in layout managers because a monolithic method, layoutContainer(Container), is responsible for performing the layout calculations. Basically, you have to rewrite this method for each layout manager you develop by subclassing standard layout managers. Given the difficulties of modifying the behavior of layout classes by subclassing them, you might as well develop a new layout manager class that implements the LayoutManager interface. Using this strategy, you can even create your own hierarchy of layout manager classes, which inherit properties among each other. The LayoutManager interface specifies methods for adding named components to the layout, removing components from the layout, calculating minimum and preferred layout sizes, and computing the layout. Table 8.3 describes the methods in the LayoutManager interface. Note that the LayoutManager is an interface with abstract methods; therefore, you must define them in your layout manager. Table 8.3. LayoutManager methods. LayoutManager Method addLayoutComponent (String, Component) layoutContainer (Container) minimumLayoutSize (Container) preferredLayoutSize (Container) removeLayoutComponent (Component) Description Adds a new component to the layout Lays out a container Calculates the minimum size Calculates the preferred size Parameters A string describing the component name and the component The container to lay out The container in question The container in question

Removes a component from the The component to remove layout

Here's an example of how you can create a layout manager that lays out components diagonally:
file:///C:/temp/java%20guru/ch8.htm (13 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

class DiagonalLayout extends Object implements LayoutManager { public void addLayoutComponent(String name, Component comp) { } public void removeLayoutComponent(Component comp) { } public Dimension preferredLayoutSize(Container parent) { int l = parent.countComponents(); Rectangle r = parent.getComponent(l-1).bounds(); return new Dimension(r.x + r.width, r.y + r.height); } public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } public void layoutContainer(Container parent) { int l = parent.countComponents(); for (int i = 0; i < l; i++) { Component c = parent.getComponent(i); c.move(50*i,50*i); } } } In this layout manager, the layoutContainer method iterates over the components and moves each component to the appropriate location (which is determined by the component index). The layout manager calculates the preferred size by getting the lower-right corner of the last component (which is the same as the size). Figure 8.15 shows the resulting layout for a container with seven buttons. Although this layout manager does not change the layout dynamically as the user resizes the window, you can easily modify it to do so. Figure 8.15 : Layout generated by DiagonalLayout. As you have seen, implementing a new layout manager is also a straightforward task. What is more difficult, however, is to design a layout manager that is both powerful and easy to use. The advantage of the AWT design is that once you have developed an appropriate layout manager, it is easy to reuse it for many situations.
file:///C:/temp/java%20guru/ch8.htm (14 of 15) [2/3/2003 8:18:59 AM]

Chapter 8 -- All About GridBaglayout and Other Layout managers

Summary
Layout managers automate the layout task by calculating window layouts dynamically. The AWT provides predefined managers that you can configure to get the window resizing behavior you want. For simple layout tasks, the FlowLayout, BorderLayout, GridLayout, and CardLayout managers work best. For advanced layout tasks, the powerful and general GridBagLayout manager is better than the basic layout managers. If these layout managers are insufficient for your task, the AWT enables you to define new layout managers.

file:///C:/temp/java%20guru/ch8.htm (15 of 15) [2/3/2003 8:18:59 AM]

Chapter 9 -- Extending AWT Components

Chapter 9
Extending AWT Components

CONTENTS
q q q q q q

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

The Java Abstract Window Toolkit (AWT) consists of classes that encapsulate basic GUI controls. Java is a multi-platform solution so the AWT provides a lowest common denominator interface. Any interface you develop should appear about the same on any platform. Often the AWT is called Another Window Toolkit or affectionately, Awful Window Toolkit. Now don't be misled; the AWT provides many useful controls and your applications or applets may not require anything more. Of course, you are reading this chapter because you want to learn how to extend the functionality of the AWT controls. To do this, you learn a technique called subclassing. Subclassing is just a fancy object-oriented term for changing the way a class works. The actual method is to create a new class from the old one and add new features along the way. Other ways exist to extend the AWT (see Chapter 10, "Combining AWT Components") but this chapter focuses on extending by subclassing. In this chapter, you will learn how to extend TextField to create a self-validating TextField. The new class will be a TextField that keeps track of user input and only allows entry of valid data. You could use such a control to enable the user to enter color choices for some graphic object. This control would force the user to enter only valid colors and reject any other entries. The text also looks at extending the Button class to create a multi-state toggle button. This toggle button will display a different label each time it is pressed. You could use it in place of separate on and
file:///C:/temp/java%20guru/ch9.htm (1 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

off buttons.

Components-an Overview
In discussions of Java, people often use the word component to mean two different things. Sometimes, people use the generic meaning and refer to any GUI object as a component. However in Java, Component has a very specific meaning. Component is a class derived from Object. The major GUI widgets derive from Component as illustrated in Figure 9.1. Figure 9.1 : The Java AWT class hierarchy. The Component class is the base GUI class. This class provides functions to handle events and set or query attributes.

What Is a Peer?
If you have looked at the Java API documentation, you have probably seen a class called ComponentPeer. Derived from it are peer classes associated with each of the component classes. The purpose of the peer classes is to bridge the gap between the AWT classes and the underlying platform-specific UI widgets. By using a peer, the AWT provides a uniform programming interface across all platforms. The peer classes are rarely used directly in Java programming, except when porting the AWT to other platforms.

Why Are Image Buttons Hard?


If you were to compile a list of language features that Java users would like to see in a 1.5 or 2.0 release, image buttons would appear near the top. An image button is a button that has an image on its face instead of text. Most modern GUIs include image buttons, so why doesn't the AWT? The reason the AWT doesn't have image buttons has to do with the nature of Java itself. Because the Java AWT is a multi-platform GUI finding, a universal solution becomes difficult. The problem is that the implementation of an AWT button gets tied up between the classes Button and ButtonPeer. You can change the behavior of the Button class, but not its associated peer. One possible solution would be to create an image button by extending some class other than Button. You could derive such a class from Canvas. You would need to create multiple images to represent the up and down states of the button and switch them and repaint when the user clicked in the Canvas. The problem is that such a button would look exactly the same on every platform rather than looking like a native implementation of an image button.
file:///C:/temp/java%20guru/ch9.htm (2 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

New Components from Old


When you design a user interface, you use the widgets provided in the Toolkit as the basic building blocks. Sometimes the design calls for a control that is just slightly different from the AWT version. Rather than try to develop new controls, you modify the existing controls in the AWT. To accomplish this, you use a method called subclassing. In object oriented terminology, this technique is often called class derivation or inheritance.

A Self-Validating TextField
In this example, you create a self-validating version of a TextField. You will derive a class from TextField called SelfValidatingTextField. The class will have a list of acceptable entries and users will only be allowed to enter values from this list. This control allows you to limit the possible inputs from the user and to anticipate the user's input. When the user enters a character, you try to determine which string they are typing and fill in the blank for them.

Overview
You create the SelfValidatingTextField class by subclassing the TextField class. Start with a list of valid strings. When the user enters a character, you catch the key down Event. At this point, the bulk of the work begins. You must look at the text already in the control and determine whether the new keystroke is valid. If it so, add it to the control and find the best match for the entered text. When you add the string to the control, you select the portion that the user did not type. So if the user types s and the matching string is spray, the last three letters (ray) are selected. An Example with Valid Strings Now create a SelfValidatingTextField in an Applet with the following valid strings; Janice, Jedidiah, Jonathan, Joy, Joshua, Jennifer, Jason. If the user types a character other than J nothing happens because all the valid strings begin with J. When the user types a J, the control displays the J and the remainder of the first matching string in alphabetical order-in this case Janice. Figures 9.2 to 9.6 illustrate a typical user session. Figure 9.2 : Type a J. Janice is displayed and the anice is selected.

file:///C:/temp/java%20guru/ch9.htm (3 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

Figure 9.3 : Type an a. Janice is still displayed, but now the nice is selected. Figure 9.4 : Type a d. Nothing happens since none of the valid strings begin with Jad. Figure 9.5 : Now type an s. The control displays Jason with the on selected. Figure 9.6 : Pressing the delete key causes the selected portion of the text to be deleted. As Figures 9.2 through 9.6 illustrate, you have created a new control that retains much of the functionality of the original TextField while providing significant enhancements. The new control still takes input from the user, but it now anticipates the user's input. This function means that less typing is necessary to enter the desired string. You have retained the functionality of delete, backspace, and other special keystrokes. These keys operate in the control just like they do in the AWT version. The SelfValidatingTextField can be used as a drop in replacement for the TextField control.

What the Class Needs to Know


The AWT TextField needs to know very little. To use one, you simply create it and add it to your layout. When you want to get the data that the user has entered, simply call the getText() method. Because the SelfValidatingTextField enhances the functionality of TextField, it needs more information. The control must know what strings to accept and how to interpret keystrokes. Our enhanced TextField should also be able to anticipate what the user is entering and display the best match string. Text-matching algorithms must deal with the issue of case-sensitivity. In other words, does the string "Jennifer" match "jennifer"? Your control enables you to be either case-sensitive or caseinsensitive, which makes the control more versatile, but requires some extra processing. You let the class store the information it needs by adding the following instance variables: String[] strings ; boolean caseSensitive ; int pos ; The strings variable is used to store all the acceptable string values. Eventually, you will sort this array so your matches display in alphabetical order. The caseSensitive variable is a flag that indicates whether the string matching you do will be casefile:///C:/temp/java%20guru/ch9.htm (4 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

sensitive. You need to set this variable whenever you create an instance of the SelfValidatingTextField class. During the data validation, you use the variable to determine whether to accept a given keystroke. The pos variable is used by the class to keep track of the position of the last character entered by the user. This information becomes important when you display the best match string for a given input. You will need to update pos whenever you get input from the user. Use this constructor to pass the information needed to the class: public SelfValidatingTextField( String[] a, boolean cs, int chars ) { super( chars ); strings = a; caseSensitive = cs; pos = 0 ; sortStrings() ; } The constructor takes three parameters: the array of valid strings, the case-sensitivity flag, and an integer parameter chars. The chars parameter is used to call the overloaded TextField constructor. The specific constructor you call is TextField(int n), which creates a TextField big enough to hold n characters. The call to the parent class constructor looks like super( chars ); This statement invokes a super class constructor. Because the super class is TextField, the TextField(int n) constructor is called. The next three statements in the constructor initialize the class instance variables. You pass the values for strings and caseSensitive into the constructor. The function initializes pos to zero because at the time you create the control, no keystrokes have yet been entered. Sorting the Strings The last thing the constructor does is call sortStrings(). This function uses a bubble sort algorithm to sort the array of strings in ascending order. The implementation is
file:///C:/temp/java%20guru/ch9.htm (5 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

void sortStrings() { for (int i = 0; i < strings.length - 1; i++) { boolean swaps = false ; for (int j = strings.length - 2; j >= i; j--) { if (strings[j].compareTo(strings[j+1]) > 0) { String temp = strings[j]; strings[j] = strings[j+1]; strings[j+1] = temp; swaps = true; } } if ( swaps == false ) { break ; } } } This is the traditional bubble sort. It has been modified slightly to use the swaps variable to terminate the sort if any iteration fails to produce a single swap.

Capturing Keystrokes
One of the most important things this class needs to do is respond to individual keystrokes. In Java, a keystroke is an event. You use the event-handling mechanism of Java to capture keystrokes. In event-driven programming, you often have to decide which object in the system captures which events. In many Java applets, it is the container class that captures the events generated by its embedded controls. Your control is designed to be self-contained so that you capture the keystroke events in the control itself. To capture the keystrokes, you override the keyDown() function from the Component class (Remember: Component is the parent class of TextComponent, which is the parent class of TextField): public boolean keyDown( Event e, int key ) { if ( key > 31 && key < 127 ) { return validateText( key ); }
file:///C:/temp/java%20guru/ch9.htm (6 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

return false; } The function receives two parameters: an Event object and the value of the keystroke. The first parameter is an Event object. Events in Java are class objects (see Chapter 11, "Advanced Event Handling"); they contain both data and functions. In this case, you only need the value of the keystroke, not the specific combination of keys that produced it. In the overridden method, you handle some keystrokes yourself, while passing others on to the superclass method. In the implementation of the class constructor, you made an explicit call to the superclass constructor. Notice that you make no such call here. Component.keyDown() is a special function. Instead of calling the superclass function directly, you call it by specifying the function return value. If the return value is true, it means that the function handled the event internally and the superclass method does not need to be called. If the return value is false, the function has not fully handled the event and the superclass method will be called. When overriding Component.keyDown(), you should not call the superclass method explicitly. In the if statement, you compare the key to two values: 31 and 127. These values represent the minimum and maximum values for printable characters. If the character is printable, then you call the validateText() method and return its value. In this case, validateText() always returns true. For non-printing characters, the expression is false and the function returns false. This causes the superclass version of keyDown() to be called. Thus, all non-printing characters are simply passed on to the superclass.

Validating Text
Most of the work in your control gets done in the validateText() method. This method must handle all of these different tasks:
q q q q q q

Update the pos variable. Get the current text. Handle case sensitivity. Check for string matches. Display the new string. Select the control supplied portion.

Given all that it does, validateText() is a small function. It uses the Java String class to do as much work as possible.

file:///C:/temp/java%20guru/ch9.htm (7 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

The validateText() method starts by updating the pos variable: boolean validateText( int key ) { pos = Math.min( pos, getText().length() ) ; Start by setting pos to the index of the last character the user entered. If characters have been deleted, you update pos to reflect the current contents of the control. Next, you get the text from the control: String editField = getText().substring( 0, pos ) + (char)key; You need to instantiate a local String object. The editField variable holds all of the characters the user has entered until this point. That is, the first pos characters and the value the user just entered. This is accomplished by calling the getText() method that is inherited from the superclass. Now that you have gotten the text, you must determine whether the control is case-sensitive: if ( !caseSensitive ) { editField = editField.toLowerCase(); } You handle case insensitivity by calling the toLowerCase() method from the string class. By converting both the text from the control and the array of valid strings to lowercase, you can now make a case-insensitive comparison. Note The String.toLowerCase() method returns a lowercase version of the String. It does not actually modify the String. Therefore, it is necessary to assign the result to another string if you want the change to persist.

For case-sensitive comparisons, you will work with the unconverted strings. The issue of case sensitivity has been settled for the string. You must now check to see whether the characters entered thus far match any of the valid strings. The for loop below performs the appropriate comparisons:

file:///C:/temp/java%20guru/ch9.htm (8 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

for ( int i = 0; i < strings.length; i++ ) { try { if ( caseSensitive ) { if( strings[i].lastIndexOf(editField,0) &nbs p; != -1 ) { setText( strings[i] ) ; select( ++pos, strings[i].length() ) ; return true; } } else { if( strings[i].toLowerCase().lastIndexOf( editField,0) != -1 ) { setText( editField + strings[i].substring( editField.length() ) ) ; select( ++pos, strings[i].length() ) ; return true; } } } catch ( Exception e ) { // ignore any exceptions here } } The for loop iterates through the array of valid strings. During each iteration, you need to check for case sensitivity. You will call toLowerCase() if necessary. To make the actual comparison, you call the String.lastIndexOf() method. Notice that you pass two parameters to lastIndexOf(); editField and 0. The function searches the String for the subscript you pass in. The String is searched backwards starting at the index-in this case 0. Because you are searching backwards from 0, you are in effect searching from the beginning. If a match exists in the array of valid String, you need to update the control. In a case-sensitive instance of the control, you simply put the matching valid String in the control. If the control is not case-sensitive, you replace characters that the user has entered so far and append the remainder of the matching String. Next, you select or highlight the portion of the string that you supplied from the out array of valid Strings so that the next character typed by the user replaces the selected portion of the String. Finally, you return true. In every case, this control returns true. This value is returned by the calling function as well. In the calling function, keyDown(), this return value indicates that the

file:///C:/temp/java%20guru/ch9.htm (9 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

superclass implementation will not be invoked. Note The try/catch block is included because String.substring() throws a StringIndexOutOfBoundsException. This particular exception is non-critical here, so you catch it in an empty catch block.

Putting it Together
To use the SelfValidatingTextField class in your own applets or applications, you must pass it: An array of valid Strings, a boolean value for case sensitivity, and the number of characters you wish to display. The control is self-contained and takes care of all of its own validation. To get the text from the control, you call SelfValidatingTextField.getText() just as you would if you were using the AWT TextField. The entire SelfValidatingTextField class is shown in Listing 9.1.

Listing 9.1. The SelfValidatingTextField class. package COM.MCP.Samsnet.tjg ; import java.awt.*; public class SelfValidatingTextField extends TextField { String[] strings ; boolean caseSensitive ; int pos ; public SelfValidatingTextField( String[] a, boolean cs, int chars ) { super( chars ); strings = a;

file:///C:/temp/java%20guru/ch9.htm (10 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

caseSensitive = cs; pos = 0 ; sortStrings() ; } void sortStrings() { for ( int i = 0 ; i < strings.length - 1 ; i++ ) { boolean swaps = false ; for (int j = strings.length - 2; j >= i; j--) { if (strings[j].compareTo(strings[j+1]) > 0) { String temp = strings[j]; strings[j] = strings[j+1]; strings[j+1] = temp; swaps = true; } } if ( swaps == false ) { break ; } } } public boolean keyDown( Event e, int key ) { if ( key > 31 && key < 127) { return validateText( key ) ; } return false; } boolean validateText( int key ) { pos = Math.min( pos, getText().length() ) ; String editField = getText().substring( 0, pos ) &nbs p; + (char)key; if ( !caseSensitive ) { editField = editField.toLowerCase(); }
file:///C:/temp/java%20guru/ch9.htm (11 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

for ( int i = 0; i < strings.length; i++ ) { try { if ( caseSensitive ) { if(strings[i].lastIndexOf(editField,0)!=1) { setText( strings[i] ) ; select( ++pos, strings[i].length() ) ; return true; } } else { if( strings[i].toLowerCase().lastIndexOf( edit Field,0) != 1 ) { setText( editField + strings[i].substring( editField.length () ) ) ; select( ++pos, strings[i].length() ) ; return true; } } } catch ( Exception e ) { // ignore any exception here } } return true; } }

A Multi-State Toggle Button


For our second example, you create a multi-state ToggleButton. You derive a class from the
file:///C:/temp/java%20guru/ch9.htm (12 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

Button called ToggleButton. You also create an array of button values and pass them to your class. When a user presses the button, the text on the face changes. You can use this ToggleButton to replace multiple Buttons. An application that might normally have on and off Buttons could now have a ToggleButton that switched between on and off. It could also replace show and hide buttons.

Overview
The ToggleButton is derived from Button. This new class enables a button to display a different String each time it is pressed. Actually it displays all the Strings in the array and then starts over. The class provides public methods to return the index or the string associated with each press.

A Self-Destructive Example
Imagine that you want a Java applet that causes your computer to self-destruct. Don't be too worried, because applet security won't let us really self-destruct (see Chapter 20, "A User's View of Security"). Now give your applet a self-destruct button and an enable/disable button. You also create a ToggleButton for enable/disable and a Button for self-destruct. When you start the applet, the self-destruct button is enabled and the ToggleButton displays "Disable." Figures 9.7 through 9.9 illustrate the self-destruct sequence. Figure 9.7 : The fully armed Self Destruct button. Figure 9.8 : Press Disable. The Self Destruct button is disabled. The ToggleButton now displays Enable. Figure 9.9 : Press Enable. The Self Destruct button is now armed. The ToggleButton now displays Disable. You are now ready to self-destruct!

State Information
When you create a Button, you pass it a String that will be displayed on its face. The ToggleButton class needs to know what Strings to display. The class must also know how to order the strings. The class must be able to respond to button presses and modify itself accordingly. You will also give the class a means of responding to queries about its previous state.
file:///C:/temp/java%20guru/ch9.htm (13 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

The previous state information is actually more important than it seems. For example, the container that owns your control may not respond immediately to button presses. It may need to query the control in response to some other event. If the ToggleButton is currently displaying "Off" it means that the current state is "On" because the ToggleButton now displays the next state of the control. Of course, in a two-state button (like an on/off button) it is a simple matter to determine what the previous state was, but your button is not limited to only two states. You may pass it an array of any size. The class defines two constants: static final int BUTTON_BORDER = 12 ; public static final int NO_Prev = -1 ; Note Java provides a mechanism for declaring constants. The Java implementation is superior to C or C++ manifest constants. Manifest constants (or #defines) are a way of getting the C or C++ preprocessor to substitute a value for a symbol. Java constants are class members. Use the static and final modifiers when declaring them. They must be initialized and have two definite advantages over manifest constants: They are strongly typed, and as class members, they have globally unique names, thus eliminating namespace collisions.

BUTTON_BORDER is the number of pixels between the button text and the edge of the button. The actual border on each side is BUTTON_BORDER/2. NO_Prev is a flag value. You use it when you first create an object to indicate that no previous value exists. NO_Prev is declared to be public because it may be returned from the getPrevN() method. The ToggleButton class uses the following instance variables to keep track of the current object state: String[] strings ; int n ; int prevN ; The strings variable is an array that stores the states that will be displayed on the button. The ordering of the strings in this array is the order in which they will be displayed. Because Java arrays are objects, you can determine the number of states directly from the array.
file:///C:/temp/java%20guru/ch9.htm (14 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

The two integer variables n and prevN keep track of the current and previous states. Strictly speaking, only one of these variables is necessary. You could calculate the previous state from the current state. Your class does this calculation every time the state changes and stores the results. The class constructor gets the initial values from the user: public ToggleButton( String[] a ) { super( a[0] ); strings = a; n = 0; prevN = NO_Prev; } The constructor takes only one parameter. You pass the array of states into the class through the constructor. The array encapsulates the information about its size so you do not need another parameter. First, call the superclass constructor: super( a[0] ); You pass the first state value to the superclass so that it gets displayed in the control when it starts up. Note Calls to a superclass constructor should only be made from a derived class constructor. They must be the first line of code in the derived class constructor. Other superclass methods may be called from any derived class method. The syntax is super.superclassMethod().

Next you initialize the class's instance variables. You assign the array parameter to the member array. You need to set n to 0 to indicate the index of the currently displayed string. Then set prevN to NO_Prev to indicate that no previous value exists.

Updating the Button


Every time the button is pressed you need to update its text. To do this, you need to capture the button
file:///C:/temp/java%20guru/ch9.htm (15 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

press Event. In the SelfValidatingTextField class, you overrode the Component.keyDown() method to capture keystrokes. Here, you override Component.action() to capture Events. The action() method takes two parameters: an Event and an Object. The event contains information about the specific UI action that has occurred. The second parameter is an arbitrary Object that varies depending on the type of control that initiates the action. In the case of a Button, it is the text on its face. The beginning of the overridden action() method appears in the following string of code: public boolean action(Event evt, Object what) { prevN = n ; if ( n < strings.length - 1 ) { n++ ; } else { n = 0 ; } First, the method sets the prevN variable to the current index value. The compound if that follows takes care of updating the index value. The index value gets increased until it reaches the number of Strings in the array. Once it has reached this maximum, the index is set to 0. The following text contains the rest of the action() method: FontMetrics fm = getFontMetrics( getFont() ); int width = fm.stringWidth( strings[ n ] ); int height = bounds().height; resize( width + BUTTON_BORDER, height ); setLabel( strings[ n ] ); return false ; } Here you resize the button to fit the text if necessary. Start by creating a FontMetrics object. The FontMetrics constructor takes a reference to a Font. This class is the Java mechanism for providing detailed information about a Font on the current platform. You then call the stringWidth() method to get the width of the new label in pixels using the current Font. The height of the button will not change unless you use a different Font. Now that you have the width
file:///C:/temp/java%20guru/ch9.htm (16 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

and height, you call resize() to adjust the button to fit the String. Finally, the function returns false. The function must return false so that the Container in which it is embedded can also capture the button presses. Returning false means that this object has not fully processed the Event so other objects may need to do further processing.

Accessing the Data


The ToggleButton class provides methods that enable a Container to ignore button press events when they occur. These methods are used to query the control about its state when last pressed. The two methods return either the index of the previously displayed label or the label itself. Here is the first method: public int getPrevN() { return prevN ; } The getPrevN() method may return NO_Prev, which would mean that the button has never been pressed. Here is the second method: public String getPrevString() { if ( prevN == NO_Prev ) { return "" ; } return strings[ prevN ] ; } The getPrevString() method indicates that the button has never been pressed by returning an empty >String.

Putting it Together
To put a ToggleButton object in your applet or application, you need to pass it an array of states(Strings). The order of the states is important because the states will be cycled in this order. The complete ToggleButton class is shown in Listing 9.2.

file:///C:/temp/java%20guru/ch9.htm (17 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

Listing 9.2. The ToggleButton class. package COM.MCP.Samsnet.tjg ; import java.awt.*; public class ToggleButton extends Button { static final int BUTTON_BORDER = 12 ; public static final int NO_Prev = -1 ; String[] strings ; int n ; int prevN ; public ToggleButton( String[] a ) { super( a[0] ); strings = a; n = 0; prevN = NO_Prev; } public boolean action(Event evt, Object what) { prevN = n ; if ( n < strings.length - 1 ) { n++ ; } else { n = 0 ; } FontMetrics fm = getFontMetrics( getFont() ); int width = fm.stringWidth( strings[ n ] ); int height = bounds().height; resize( width + BUTTON_BORDER, height ); setLabel( strings[ n ] ); return false ; } public int getPrevN() {
file:///C:/temp/java%20guru/ch9.htm (18 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

return prevN ; } public String getPrevString() { if ( prevN == NO_Prev ) { return "" ; } return strings[ prevN ] ; } }

Usage
When you embed a ToggleButton in a Container, you may catch button presses by overriding the Container's handleEvent() method. Normally, you would use the label displayed on the Button to identify it. In your class, you need to modify this slightly because your button displays many different labels. A possible implementation of handleEvent() is: public boolean handleEvent(Event evt) { int i; for( i = 0; i < validLabels.length; i++ ) { if( validLabels[i].equals(evt.arg) ) { handleButton(i); return true; } } return false; } In this method, you iterate through the array of button states that was passed to the ToggleButton constructor. If you find a match, you call the handleButton() method. You provide this method to respond to button presses for specific button states. HandleEvent() returns true if it actually handles the Event or false if it does not.

file:///C:/temp/java%20guru/ch9.htm (19 of 20) [2/3/2003 8:19:04 AM]

Chapter 9 -- Extending AWT Components

Summary
Sometimes your applets or applications require UI functionality beyond that provided by the AWT. You can use subclassing to extend the AWT and create new classes from the basic AWT classes. By subclassing you can take the best features of an existing control class and add new functionality. You can also modify existing functionality. When you subclass, you can create self-contained controls that respond to their own Events. Your subclassed controls can often be used as drop-in replacements for the associated AWT control. Other ways exist to customize the AWT. In Chapter 10, "Combining AWT Components," you look at another method of extending or enhancing AWT functionality-combining controls.

file:///C:/temp/java%20guru/ch9.htm (20 of 20) [2/3/2003 8:19:04 AM]

Chapter 10 -- Combining AWT Components

Chapter 10
Combining AWT Components

CONTENTS
q q q q q q q q q q q

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 Overview Class Construction Event Handling Summary

If you have ever served on a committee, you know how hard it is for a group of people to work together to reach a common goal. Without leadership, everyone seems to go their own way. Without wellcoordinated communication, duplication of effort can occur. Likewise, if you try to put together a Java applet with several AWT controls, it may seem like you have a big committee-lots of activity but no leadership and no communication. In this chapter, you learn how to combine AWT components. Unlike forming a committee, this text establishes leadership, communication, and division of labor. This cooperative effort produces a whole that is much greater than the sum of the parts. Once you have formed the composite controls, they will act like all the rest of the AWT controls. You can use these new controls anywhere that you use regular AWT controls. To demonstrate composite controls, you create a scrolling picture window control. This control takes an image and makes it scrollable. All of the interaction between the AWT components that make up the control gets handled internally. To use one, all you have to do is create one and add it to your layout.

file:///C:/temp/java%20guru/ch10.htm (1 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

Component, Container, Panel


Before you look at combining controls, you need to look at some key AWT classes. These three classesComponent, Container, and Panel-are illustrated in Figure 10.1. Figure 10.1 : The core AWT classes. First, you examine the Component class. Component is the base class from which all AWT components are derived. Java does not allow you to instantiate Component or to directly subclass Component. The Component class also implements the ImageObserver interface. ImageObserver provides a means of keeping track of image properties as they are loaded. Container is a class derived from Component. Containers are objects that may contain other AWT Components. When a Container paint method is called, all the embedded Components' paint methods are called as well. Container provides functions to manage embedded Components. Like Components, you do not actually instantiate Container objects. The last of the key classes is Panel. Panel is derived from Container and may be instantiated or subclassed. Panels are Java's multi-purpose Containers and do not provide any special functionality, except the capability to embed other GUI objects. Panels form the foundation of the composite controls you build.

E Pluribus Unum: Out of Many-One


Encapsulation is the technique used in object-oriented programming to combine data and methods into classes. This principle is the idea of grouping similar things. You also use encapsulation to combine classes into composite classes. In Chapter 9, "Extending AWT Components," you used subclassing to extend controls. In this chapter, you use containment to extend controls, which means that you instance variables in your classes that are themselves AWT classes. Note

file:///C:/temp/java%20guru/ch10.htm (2 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

In Object-Oriented Programming (OOP), the containment relationship is called hasa. (For example, class Foo hasa member of type class Bar.) The subclassing relationship is called isa. (For example, class SubFoo isa derived class of class Foo.)

Using Panels to Combine UI Elements


The base class for all of the composite controls is Panel. The Panel class allows you to embed other AWT components. This class is derived from Container so it can contain UI components. The Panel class contains functions for managing embedded components. Some functions can retrieve references to the embedded components. These functions allow the class to iteratively call methods in the embedded components. Other functions handle layout issues.

Panels Are Components Too


The primary advantage of using Panel as your composite component base class is that it is a Component itself. You can use your composite components like any other AWT components. You can take these new components and combine them to form composite components from other composite components and so on. The new components can be added to layouts; they can generate existing Events or create new ones. They are full-fledged UI components and may be used anywhere that the AWT components are used.

Layouts
The composite controls will be more versatile if you implement them with the appropriate layout manager. Because you would like the controls to be self-contained, they should be able to lay themselves out properly no matter what size they are. Chapter 8, "All About GridBagLayout and Other Layout Managers," discusses the use of layout managers, including the versatile GridBagLayout and designing your own layout.

Whose Event Is It Anyway?


When you build user interfaces, you will use components that generate and respond to events. One of the critical decisions to make is who should handle a given event. In the case of your composite controls, you have three choices: (1)the Component, (2)the Panel, or (3)the Applet.

file:///C:/temp/java%20guru/ch10.htm (3 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

Some components handle their own events. The SelfValidatingTextField from Chapter 9 is an example of such a component. Your composite components will also handle many of their own events. Some Events you will want the Panel to handle. Because the Panel has information about all of the embedded components, it has the option of handling the events they develop. The Applet class is derived from Panel, which means that the applets can contain embedded controls. Applets may also handle the events generated by these controls.

The Panel as a Component Manager


Your composite components will use the Panel class as a component manager. The Panel will handle any Events that are not handled in the components and will maintain information about its embedded components. You use the panel's LayoutManager to size the embedded controls. When an event occurs in a control, the panel will respond. An animation control might have a panel and some VCR style buttons. When a user presses the start button, the animation begins. Clicking on the stop button ends the animation. While the animation plays, you disable the play button and enable the stop button.

A Scrolling Picture Window Example


In this example, you create a scrolling picture window. You derive a class from Panel called ScrollingPictureWindow. The class will contain three member objects, a Canvas to hold the picture, and two scrollbars. This composite control will provide a self-contained way of displaying a picture. A user simply needs to pass an Image object to the control, and it does the rest. The control handles scrolling and updating the image. When the LayoutManager resizes the control, the scrollbars and image location automatically get adjusted.

Overview
Unlike some committees, each member of the ScrollingPictureWindow class has well-defined responsibilities. You will define specific roles for each component. You also design a means of handling communication between member components. All committees need leadership. This committee needs a leader as well. The chairman of your committee is the ScrollingPictureWindow object. This class is derived from Panel and contains the two
file:///C:/temp/java%20guru/ch10.htm (4 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

scrollbars and the Canvas object. The relationship of these classes is shown in the organizational chart of Figure 10.2. Figure 10.2 : The organization of your committee. The first member of the committee is the ImageCanvas object. ImageCanvas is a class derived from Canvas. This object actually displays the image. The ScrollingPictureWindow object will tell the ImageCanvas how to display the image. Note The Canvas class is an AWT component and serves as a generic UI component. It is meant to be subclassed to provide application (or applet) specific behavior. It is often used to display images. Canvas generates all of the key and mouse events so that it can be used to create powerful new UI classes.

The last two members of the committee are the scrollbars. These scrollbars do not handle any of their own events, but simply report them to the ScrollingPictureWindow object. The ScrollingPictureWindow class will also inform the scrollbars when they need to reposition themselves.

The ImageCanvas Class


The ImageCanvas class is derived from Canvas. You use this class to display your image. The class defined contains one instance variable: Image canvasImg ; The ImageCanvas constructor takes an Image object as a parameter. Because parameters of class type are passed by reference, this makes a local reference to the Image object in the class. public ImageCanvas( Image img ) { canvasImg = img ; } Note

file:///C:/temp/java%20guru/ch10.htm (5 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

Java uses pass-by-value for parameters of simple types. Pass-byvalue means that these variables are copied into the local space for a function. This is the normal passing paradigm of C++ and the only passing paradigm of C. Parameters of class type use pass-byreference. This is like the C++ implementation. Pass-by-reference creates a local reference to a parameter in the function space.

The only other method provided in the ImageCanvas class is paint(). The paint method will actually draw the image. Because the picture scrolls, the class will need to know where to draw it. The location of the image depends on the position of the scrollbars. In your scheme, the ScrollingPictureWindow object handles communication between the member objects. You need to query the ScrollingPictureWindow object to determine where to draw the image. public void paint(Graphics g) { g.drawImage( canvasImg, -1 * ((ScrollingPictureWindow)getParent()).imgX, -1 * ((ScrollingPictureWindow)getParent()).imgY, this ) ; } To get the information, use the getParent() method. The getParent() method is a member of the Component class. This method returns a reference to the Container object that holds the Component. When you call getParent(), you get a reference to the ScrollingPictureWindow object. Because this reference is the Container type, you need to cast it to a ScrollingPictureWindow reference. Now you can access the public instance variables in the ScrollingPictureWindow object. If you feel uncomfortable with directly accessing the members of the parent class, an alternative method would be to provide public methods or access functions. These functions would return the x and y values at which to draw the image. The imgX and imgY members contain the x and y coordinates of the point (in terms of the image) that will be displayed in the upper left corner. If you want the point (10,5) to be displayed in the upper left corner, you pass -10 and -5 to drawImage(). As the example in Figure 10.3 shows, the Canvas class clips the image to fit within its boundaries.

file:///C:/temp/java%20guru/ch10.htm (6 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

Figure 10.3 : Drawing and clipping the image. ImageCanvas provides the basic drawing for the ScrollingPictureWindow class. This class shows a typical usage of the Canvas class.

Instance Variables
The ScrollingPictureWindow class contains several instance variables. These variables include the embedded controls and state variables. The embedded controls will be stored as: ImageCanvas Scrollbar Scrollbar Image imageCanvas ; vertBar ; horzBar ; image ;

The last instance variable in this list is a reference to an Image object, which gets passed in by the owner of your class object. The remaining instance variables all contain state information. The first two contain the size in pixels of the entire image: int imgWidth ; int imgHeight ; The next two instance variables contain the current position of the image. These variables also reflect the current position of the scrollbars. Because the scrollbars and the image are tied together, both classes use these variables. The scrollbars will set their value, and the ImageCanvas uses the value to place the image. int imgX ; int imgY ; The last variable is used by the scrollbars. This value specifies the amount that the scrollbar moves when you request a pageup or pagedown. int page ;

Class Construction
The class constructor performs all of the initialization for your class. The constructor must
file:///C:/temp/java%20guru/ch10.htm (7 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

q q q q q

Initialize the state variables Determine the size of the image Instantiate the member controls Set up the GridBagLayout Set the constraints for each control

State Variables
Begin construction by setting the local Image reference to the Image argument: public ScrollingPictureWindow ( Image img ) { image = img ; The next step in the construction process is simple. You need to initialize imgX and imgY to 0. What this really does is set the initial position of the image and scrollbars. These two instance variables contain the x and y offsets at which to display the image: imgX = 0 ; imgY = 0 ; The ImageCanvas class will need these variables to determine how to place the image. The ImageCanvas paint() method accesses these instance variables directly and uses them in its call to drawImage().

Image Size
Your composite control needs to know how large its image is. Once you have this information, it will remain constant. Unfortunately, determining the image size is not as straightforward as you might think. You have this difficulty by design. Your class has been designed to take an Image object as a parameter, giving the users of the class a great deal of flexibility. They may load the image anyway they want. The image you receive may be one of many in an array. It may be in use by other objects in the applet. It may also have been just recently loaded by the calling applet. It is this last case that causes problems. The sample applet used to test the class is very simple. It loads an image, creates a ScrollingPictureWindow object, and adds it to the layout. The Applet code follows: package COM.MCP.Samsnet.tjg ;

file:///C:/temp/java%20guru/ch10.htm (8 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

import java.applet.*; import java.awt.*; import ScrollingPictureWindow ; public class Test extends Applet { ScrollingPictureWindow pictureWindow ; public void init() { Image img = getImage( getCodeBase(), "picture.gif" ) ; pictureWindow = new ScrollingPictureWindow( img ) ; setLayout( new BorderLayout() ); add( "Center", pictureWindow ) ; } }; The first line of the init() method calls getImage(). The getImage() method loads a specified image file. The problem is that getImage() returns immediately before the image actually loads. The image is not really loaded (i.e., its bits read into memory) until it is needed. Therefore, when you pass an image to the ScrollingPictureWindow constructor, it may not be fully loaded. Thus in your class constructor, it is possible that the reference you receive is to an image that is not yet fully loaded. To get the image size, you make a call to Image.getHeight(). If the image is not fully loaded, however, getHeight() returns -1. To get the size of the image, you will loop until getHeight() returns a value other than -1. Both while loops below have null bodies: while ((imgHeight = image.getHeight(this)) == -1 ) { // loop until image loaded } while ((imgWidth = image.getWidth(this)) == -1 ) { // loop until image loaded }

Member Controls
Next, you need to create the embedded member objects. The ImageCanvas takes the Image as a parameter. Each scrollbar constructor takes a constant that determines whether the scrollbar is vertical or horizontal.

file:///C:/temp/java%20guru/ch10.htm (9 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

imageCanvas = new ImageCanvas( image ) ; vertBar = new Scrollbar( Scrollbar.VERTICAL ) ; horzBar = new Scrollbar( Scrollbar.HORIZONTAL ) ;

GridBagLayout
You use a GridBagLayout to lay out the embedded control. GridBagLayout is the most versatile LayoutManager in the AWT, which provides precisely the control you need to arrange the components. While GridBagLayout is the most powerful LayoutManager, many Java programmers have been slow to accept it. Why is GridBagLayout so mysterious? Chapter 8 hopefully helped to de-mystify the topic. The Java phenomenon has developed so quickly that it seems almost comical to talk about the history of Java. Early Java books and even the beta versions of the online documentation from Sun omitted GridBagLayout. Many people who were doing Java in the early days still hesitate to use GridBagLayout. You may be wondering why this control does not use a BorderLayout. BorderLayout is simple to use and a good choice in many situations. Figure 10.4 shows the control using a BorderLayout. Figure 10.4 : A ScrollingPicture-Windows with a BorderLayout. What's wrong with this picture? Take a look at the lower-right-hand corner of Figure 10.4. Do you see how the horizontal scrollbar is wider than the image area? Look for Java applets on the Internet; many have scrollbars arranged just like these. Nothing is intrinsically wrong with this layout. Compare it, however, to the TextField applet in Figure 10.5. Figure 10.5 : A TextField with scrollbars. This applet simply creates and displays a multiline TextArea. The scrollbars are part of the TextArea. Look at how they are arranged. In the built-in AWT component the scrollbars do not overlap. This is the suggested look for the ScrollingPictureWindow control. The best way to achieve this layout is to use GridBagLayout. First, you create a GridBagLayout object. Then, you call setLayout() to make it the current layout manager. GridBagLayout gridbag = new GridBagLayout(); setLayout( gridbag ) ;

file:///C:/temp/java%20guru/ch10.htm (10 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

Constraints
The GridBagLayout class uses the GridBagConstraints class to specify how the controls get laid out. First, you create a GridBagConstraints object. You will then use the GridBagConstraints object to determine how to layout your individual components. GridBagConstraints c = new GridBagConstraints(); You add the ImageCanvas object to your panel first. Because the ScrollingPictureWindow control is supposed to act like the native AWT controls, it may be resizeable. Therefore, you need to specify that it can grow in both x and y directions. So you set the fill member to BOTH. c.fill = GridBagConstraints.BOTH ;

You want the image to fill all the available space with no padding, so set the weight parameters to 1.0. c.weightx c.weighty = 1.0; = 1.0;

You finish laying out the image by calling setConstraints() to associate the ImageCanvas object with the GridBagConstraints object. Then, you add the image to the layout. gridbag.setConstraints(imageCanvas, c); add( imageCanvas ) ; Next, you layout the scrollbars. Start with the vertical scrollbar. The vertical scrollbar should shrink or grow vertically when the control is resized, so you set the fill member to VERTICAL. c.fill = GridBagConstraints.VERTICAL ;

Look at your layout in terms of rows. You see that the first row contains two controls: the ImageCanvas and the vertical scrollbar. You indicate that the scrollbar is the last control in the row by setting the gridwidth member to REMAINDER. c.gridwidth = GridBagConstraints.REMAINDER ;

You complete the vertical scrollbar layout by associating it with the constraint object and then adding it to the layout. gridbag.setConstraints(vertBar, c); add( vertBar ) ;
file:///C:/temp/java%20guru/ch10.htm (11 of 21) [2/3/2003 8:19:10 AM]

Chapter 10 -- Combining AWT Components

Finally, you layout the horizontal scrollbar. Because this scrollbar should be horizontally resizeable, set the fill member to HORIZONTAL. c.fill = GridBagConstraints.HORIZONTAL ;

The reason for using a GridBagLayout is to prevent the horizontal scrollbar from filling the entire width of the control. You need to guarantee that the horizontal scrollbar remains the same width as the ImageCanvas object. Fortunately, the GridBagConstraint class provides a means of tying the width of one object to the width of another. You use the gridWidth member of the GridBagConstraint class to specify the width of the scrollbar in terms of grid cells. Set this member to 1 so that the horizontal scrollbar takes up the same width as the ImageCanvas object (they are both one cell wide). It is the ImageCanvas object that sets the cell size. c.gridwidth = 1 ;

The last thing you need to do is add the horizontal scrollbar. First associate it with the constraints object; then add it to the layout. gridbag.setConstraints(horzBar, c); add( horzBar ) ;

Sizing and Resizing


One of the most important features of the composite control is that it is resizeable. When you resize the control, you expect it to: resize its components, reposition the image, and adjust the scrollbars. You also need to update the class's status variables. Start by examining what happens when you resize the control. First, size the control so that the control is smaller than the image it displays. You should be able to use the scrollbar to see all of the image. Figure 10.6 shows the control. Figure 10.6 : The ScrollingPicture-Window. Next, you make the control wider until it is wider than the image. The control now shows the entire width of the image. The horizontal scrollbar is no longer necessary, so you disable it. By making the control taller than the image, you disable the vertical scrollbar. If you make the control both wider and taller than the image, you disable both scrollbars. Figure 10.7 displays the control enlarged so that both scrollbars are disabled.
file:///C:/temp/java%20guru/ch10.htm (12 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

Figure 10.7 : The ScrollingPictureWindow resized, with scrollbars disabled. Figure 10.8 : The ScrollingPictureWindow resized, with scrollbars enabled. When you shrink the control, you enable the scrollbars again. You will handle all of the resizing by overriding the Component.reshape() method. This function is called every time a control gets resized. The first thing that your function does is call the superclass (baseclass) reshape method. The superclass method does the real work of sizing. Because you are using a GridBagLayout, the LayoutManager resizes the individual components. public synchronized void reshape(int int int int x, y, width, height) {

super.reshape( x, y, width, height ) ; You let the superclass do the resizing, so now you must update the image and scrollbars. First, determine whether the width of the control is greater than the image width plus the width of the vertical scrollbar. If the control width is greater, then you disable the horizontal scrollbar. if ( width > imgWidth + vertBar.preferredSize().width ) { horzBar.disable() ; If the control width is not greater, then you enable the horizontal scrollbar. } else { horzBar.enable() ; Next, you determine how to reposition the horizontal scrollbar. Start by getting the size of the entire control and the width of the vertical scrollbar. lllll Rectangle bndRect = bounds() ; int barWidth = vertBar.preferredSize().width ; Note

file:///C:/temp/java%20guru/ch10.htm (13 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

When working with scrollbars, you have to set several values: (1)the thumb position, (2)the maximum and minimum values, (3)the size of the viewable page, and (4)the page increment.

Now you can calculate the maximum value for the scrollbar. You always set the minimum of the scrollbar to 0. The maximum value will be the image width minus the width of the ImageCanvas. You set the page size and page increment to one-tenth of the maximum size. int max = imgWidth - (bndRect.width - barWidth); page = max/10 ; Before setting the new values, you have to determine how to translate the old position to the new scale. Start by getting the old maximum value. If the old value is 0, you make the position 0. int oldMax = horzBar.getMaximum() ; if ( oldMax == 0) { imgX = 0 ; If the old maximum is not 0, you calculate the new position. First, express the old position as a fraction of the old maximum. Then, multiply the fraction by the new maximum. The resulting value gives you the new position. } else { imgX = (int)(((float)imgX/(float)oldMax) * (float)max) ; } The last thing you need to do is set the scrollbar parameters. horzBar.setValues( imgX, page, 0, max ) ; horzBar.setPageIncrement( page ) ; } You use the same algorithm for setting the vertical scrollbar.

Event Handling
file:///C:/temp/java%20guru/ch10.htm (14 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

Whenever the user interacts with your control, the system generates an Event (see Chapter 11, "Advanced Event Handling"). This program is especially concerned about scrollbar Events. All other types of Events get passed on and handled outside your program. You start by overriding the Component.handleEvent() method. In this method, you look for Events generated by the horizontal scrollbar. If the Event is one of the seven scrollbar Events, you reset the imgX variable and call repaint(). You return true if you can handle the Event. public boolean handleEvent(Event e) { if ( e.target == horzBar ) { switch( e.id ) { case Event.SCROLL_PAGE_UP: case Event.SCROLL_LINE_UP: case Event.SCROLL_ABSOLUTE: case Event.SCROLL_LINE_DOWN: case Event.SCROLL_PAGE_DOWN: imgX = horzBar.getValue() ; imageCanvas.repaint(); return true ; } The code for handling the vertical scrollbar is the same as for the horizontal scrollbar. If you do not handle the Event, call the superclass handleEvent method and return. return super.handleEvent(e) ; }

Putting It Together
You now have a composite control that can become a drop-in replacement for other AWT controls. It handles its own events, and it responds to external resizing. The complete ScrollingPictureWindow class is as follows: package COM.MCP.Samsnet.tjg ;
file:///C:/temp/java%20guru/ch10.htm (15 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

import java.awt.*; public class ScrollingPictureWindow extends Panel { ImageCanvas Scrollbar Scrollbar Image imageCanvas ; vertBar ; horzBar ; image ;

int imgWidth ; int imgHeight ; int imgX ; int imgY ; int page ; public ScrollingPictureWindow ( Image img ) { image = img ; imgX = 0 ; imgY = 0 ; while ((imgHeight = image.getHeight(this)) == -1 ) { // loop until image loaded } while ((imgWidth = image.getWidth(this)) == -1 ) { // loop until image loaded } imageCanvas = new ImageCanvas( image ) ; vertBar = new Scrollbar( Scrollbar.VERTICAL ) ; horzBar = new Scrollbar( Scrollbar.HORIZONTAL ) ; GridBagLayout gridbag = new GridBagLayout(); setLayout( gridbag ) ; GridBagConstraints c = new GridBagConstraints();

file:///C:/temp/java%20guru/ch10.htm (16 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

c.fill = GridBagConstraints.BOTH ; c.weightx = 1.0; c.weighty = 1.0; gridbag.setConstraints(imageCanvas, c); add( imageCanvas ) ; c.fill = GridBagConstraints.VERTICAL ; c.gridwidth = GridBagConstraints.REMAINDER ; gridbag.setConstraints(vertBar, c); add( vertBar ) ; c.fill = GridBagConstraints.HORIZONTAL ; c.gridwidth = 1 ; gridbag.setConstraints(horzBar, c); add( horzBar ) ; } public synchronized void reshape(int int int int x, y, width, height) {

super.reshape( x, y, width, height ) ; if ( width > imgWidth + vertBar.bounds().width ) { horzBar.disable() ; } else { horzBar.enable() ; Rectangle bndRect = bounds() ; int barWidth = vertBar.preferredSize().width ; int max = imgWidth - (bndRect.width - barWidth); page = max/10 ; int oldMax = horzBar.getMaximum() ; if ( oldMax == 0) { imgX = 0 ; } else {
file:///C:/temp/java%20guru/ch10.htm (17 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

imgX = (int)(((float)imgX/(float)oldMax) * (float)max) ; } horzBar.setValues( imgX, page, 0, max ) ; horzBar.setPageIncrement( page ) ; } if (height > imgHeight + horzBar.bounds().height) { vertBar.disable() ; } else { vertBar.enable() ; Rectangle bndRect = bounds() ; int barHeight = horzBar.preferredSize().height ; int max = imgHeight - (bndRect.height &nbs barHeight) ; page = max/10 ; int oldMax = vertBar.getMaximum() ; if ( oldMax == 0) { imgY = 0 ; } else { imgY = (int)(((float)imgY/(float)oldMax) * (float)max) ; } vertBar.setValues( imgY, page, 0, max ) ; vertBar.setPageIncrement( page ) ; } } public boolean handleEvent(Event e) {
file:///C:/temp/java%20guru/ch10.htm (18 of 21) [2/3/2003 8:19:11 AM]

p;

Chapter 10 -- Combining AWT Components

if ( e.target == horzBar ) { switch( e.id ) { case Event.SCROLL_PAGE_UP: case Event.SCROLL_LINE_UP: case Event.SCROLL_ABSOLUTE: case Event.SCROLL_LINE_DOWN: case Event.SCROLL_PAGE_DOWN: imgX = horzBar.getValue() ; imageCanvas.repaint(); return true ; } } else if ( e.target == vertBar ) { switch( e.id ) { case Event.SCROLL_PAGE_UP: case Event.SCROLL_LINE_UP: case Event.SCROLL_ABSOLUTE: case Event.SCROLL_LINE_DOWN: case Event.SCROLL_PAGE_DOWN: imgY = vertBar.getValue() ; imageCanvas.repaint(); return true ; } } return super.handleEvent(e) ; }

file:///C:/temp/java%20guru/ch10.htm (19 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

}; class ImageCanvas extends Canvas { Image canvasImg ; public ImageCanvas( Image img ) { canvasImg = img ; } public void paint(Graphics g) { g.drawImage( canvasImg, -1 * ((ScrollingPictureWindow)getParent()).imgX, -1 * ((ScrollingPictureWindow)getParent()).imgY, this ) ; } }

Summary
The ScrollingPictureWindow class you created in this chapter is a good example of a composite control. This class combines the techniques of subclassing and encapsulation. It also is a subclass of Panel and serves to encapsulate the Canvas and the two scrollbars. The goal in developing composite controls is to provide plug-in replacements for the existing AWT controls. Because the ScrollingPictureWindow is a subclass of Panel, it inherits all the properties of the Panel class. Therefore, you can use a ScrollingPictureWindow object anywhere that you would use a Panel. When you create a composite control, you need to provide a mechanism for encapsulating the embedded controls. In this chapter you used the AWT Panel class to contain the other controls. The embedded controls must also communicate with each other. The handleEvent() method handles scrollbar events and enables the Canvas class to determine how to draw the image. When you design an applet or application in Java, you have at your disposal the basic AWT controls. Now you can create composite controls like the ScrollingPictureWindow. Common tasks (like scrolling an image) are good candidates for combined controls. These new controls will become part of your personal Java toolbox, and you can use them in all of your future Java programming.

file:///C:/temp/java%20guru/ch10.htm (20 of 21) [2/3/2003 8:19:11 AM]

Chapter 10 -- Combining AWT Components

file:///C:/temp/java%20guru/ch10.htm (21 of 21) [2/3/2003 8:19:11 AM]

Chapter 11 -- Advanced Event Handling

Chapter 11
Advanced Event Handling

CONTENTS
q q q q q q q q q q q

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

This chapter looks at event handling in the AWT. The first section, "Basic Event Handling," is about how events are generated and processed by the AWT and by applications using this toolkit. When application code is called in response to an event, it may need to get information about the event, such as the coordinates of a mouse click. The information available to an application is discussed in "The Event Class." There are two sets of events that are particularly important for applications-key and mouse events. Further details on these are given in the next two sections. Regrettably, programs do need debugging. The AWT still has problems, which sometimes makes determining where errors are occurring difficult, so a program is included in this chapter that can be used to show the events that occur in an application and the event information associated with them. The topic of event generation is revisited in the section "Generating Events." Events are manufactured by the AWT in response to user actions, but applications may also create them and send them to objects. You have probably already come across a number of problems with the AWT event model. Some of
file:///C:/temp/java%20guru/ch11.htm (1 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

these require fixes by the library authors, and some can be fixed by defining new classes and filling in some gaps in the AWT methods. These techniques are dealt with in "Fixing Broken Event Handling." This section is followed by a longer example incorporating the techniques discussed earlier. There are deeper problems with AWT events that cannot simply be fixed by a few new classes, requiring instead a replacement of the event model. Two alternative models are examined in the section "Major Surgery to the Event Model."

Basic Event Handling


The AWT toolkit generates events in response to user actions. For example, selecting a button generates an ACTION_EVENT. Applications or applets that use the AWT toolkit have to respond in an eventdriven manner to these events; for example, a mechanism must exist for catching these events and processing them. GUI applications sit in an event loop to catch and dispatch events. The detailed mechanism of this varies between the different GUI toolkits. For example, Windows enables you to catch each event and branch on the event type. Then, application-specific code is invoked. Xlib (the basic library of the X Window System) acts in a similar way, which is a strictly procedural approach. Motif and other Xt-based toolkits allow application-specific code to be attached to callback functions, which is more object oriented in approach in that it attaches application code to the application objects. It does, however, have flaws. For example, it is hard to separate view from model-one often ends up with application code being mixed up with GUI code. AWT hides the event processing loop in its internals and posts events to objects. When an event of interest to the AWT toolkit occurs, the toolkit calls postEvent() for the object it occurred in. For subclasses of component, postEvent() calls the handleEvent() method in the object. If handleEvent() returns false, it calls handleEvent() in the parent object, etc., until either the method returns true or the top of the tree is reached. Note that these events are not Windows events, nor X events, nor Mac events, but AWT events. They get triggered by certain actions occurring in Windows, but not by all actions. For example, when a Motif PushButton is clicked (activated), an AWT event with field id set to ACTION_EVENT is sent to the AWT Button. When the Motif PushButton is merely pressed (armed), however, no AWT event gets generated. This setup often causes frustation to the programmer familiar with a particular windowing system: At present you just do not have the detailed control over some Java objects that you have over the native objects that they are built on. For example, Java does not presently support control over drag and drop in the full sense of being able to drag information from one application to another-a window can be dragged

file:///C:/temp/java%20guru/ch11.htm (2 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

from, due to native support, but when you try to drop, Java has no way of handling this action. (The MOUSE_DRAG event type is just mouse motion with one of the buttons pressed-it does not register a drop site.)

handleEvent() Method for Component


Most AWT classes inherit the handleEvent() method from component, which does a switch on event id and calls another method: public boolean handleEvent(Event evt) { switch (evt.id) { case Event.MOUSE_ENTER: return mouseEnter(evt, evt.x, evt.y); case Event.MOUSE_EXIT: return mouseExit(evt, evt.x, evt.y); // other case elements omitted default: return false; } } public boolean mouseEnter(Event evt, int x, int y) { return false; } Note This group does not include classes derived from MenuComponent. They are dealt with in the section "Fixing Broken Event Handling."

Not all events call methods; some fall through to the default case, which returns false. The methods called for AWT component objects all return false. You can override them in a user-defined subclass. For a concrete example of this, consider the button. When the button gets clicked, an AWT event the with id set to ACTION_EVENT is generated. handleEvent() calls the method public boolean action(Event evt, Object what) The second argument here is the value of the button's label as a String. To examine this value, what needs to be coerced to the String class.

file:///C:/temp/java%20guru/ch11.htm (3 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

If you use a button and want to attach application code that is executed on a button click, you can do so by overriding action() in a subclass class MyButton extends Button { public boolean action(Event evt, Object what) { // handle button ... System.out.println("I've been pushed"); return true; } } Note Note that this returns true to prevent the event from being passed to its parent. This return value is intimately tied up with which event model is used. This idea gets discussed further in the following two sections.

To give a realistic example of event handling, consider the problem of password entry. The password should be entered into a single-line TextField. This class actually has a special method to set the echo character to something other than the input character. When the newline character is pressed, the method action() is invoked. You can override this in a subclass of TextField to get useful behavior. The program appears in Listing 11.1.

Listing 11.1. Password.java. import java.awt.*; public class Password extends Frame { public static void main(String argv[]) new Password().show(); } Password() { add("Center", new PasswordText()); resize(100, 20); } }

file:///C:/temp/java%20guru/ch11.htm (4 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

class PasswordText extends TextField { PasswordText() { setEchoCharacter('*'); } public boolean action(Event evt, Object what) { // just print the "secret" password System.out.println(getText()); return true; } }

Event Models
The AWT is built on top of native toolkits. For example, in Windows 95 all Java GUI objects are implemented by Windows objects. The native toolkits have their own event handling models, with their own way of handling user actions. For example, when the user presses the left mouse button, Windows 95 generates a WM_LBUTTONDOWN event, while Motif generates a BUTTON_PRESS event. The details of how these are handled are buried in the native code of each implementation of the AWT. AWT supplies a layer of event handling code that will be called when an event occurs and is written in C as native code, different for each toolkit. This code is responsible for preparing a dynamic call to the Java interpreter, using the AWT object's peer object. For example, when an AWT button is clicked, a call is made from the native toolkit up to the Java interpreter to execute the method action() of the button's peer. The peer's method prepares an AWT event from the information passed to it and calls postEvent() for the object. The AWT event is then dealt with by Java code within the AWT toolkit for a while. For objects of the component type, handleEvent() is called on the object, its parent, and its grandparent (in the widget tree) until one of the methods returns true. If none of these return true, then the handleEvent() of the peer objects is called, from outer container to inner. These methods are native and also return a Boolean value. public boolean postEvent(Event e) { ComponentPeer peer = this.peer; if (handleEvent(e)) { return true; }

file:///C:/temp/java%20guru/ch11.htm (5 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

if (parent != null) { e.translate(x, y); if (parent.postEvent(e)) { return true; } } if (peer != null) { return peer.handleEvent(e); } return false; } For example, suppose a frame contains a panel, and within the panel is a button. If the button is selected, then postEvent() is called successively on the button, the panel, and the frame (assuming that each of these returns false from handleEvent()). Then the event is offered to handleEvent(). Within this structure, the Java AWT 1.0 has two ways of dealing with the native events-the "old" and "new" ways. This has two unfortunate effects: First, the Java code you write as an applications programmer may be different for the two models; second, Sun has promised to migrate events from the old model to the new model over time, which means that the code you write today may not work tomorrow.

Old Event Model for Component


In the old model, a native event affects the native object immediately. In response to a mouse click, for example, the focus may change to an application and bring it to the top; in response to selecting an item in a list, the highlighted element may change. When the AWT event is created, it belongs to the Java level only. The event that triggered the application code should be treated as a read-only object because changes to it have no effect. When application code executes for the event it may return true or false from handleEvent(). The common convention has been to return true to stop further event propagation. There is a lot of code in the public domain that still assumes this event model, and a lot of tutorials and books that tell you that this is the way to do it. Unfortunately, you have to use this old model for some cases even in JDK 1.0.1 because of bugs, some of which are discussed in a later section.

New Event Model for Component


The new model changed the underlying handling of native events so that Java application code can filter
file:///C:/temp/java%20guru/ch11.htm (6 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

events. In a TextComponent, what gets typed may not be what the application wants to see in the TextComponent. For example, the application may want:
q q

q q

To convert all characters typed to lowercase To change all characters to an asterisk for password entry (TextField has a method just for this use, but this is really too specialized) To discard unwanted characters To replace a character by a sequence as in macro expansion

These examples show why an application may want to change the event presented to a different one, to discard the event, or to change it into a sequence of events. The new model supports this by trapping the native event before it gets to the native GUI object. It uses this to create the AWT object that it sends on its route up through the object and its parents and back down through the peers. Once it arrives back in the peer object, it is converted back into a native event and then is given to the native object. Changes to the AWT event are rejected by changes to the native event, so that an application can filter and modify events. Caution The peer objects allow platform-specific code to execute. You should normally stay away from peer objects because they are a "hidden" part of AWT that exists to implement lots of native code methods. The new event model forces you to pay more attention to the peer objects than you should.

Right now, the AWT only actively uses the new event model for key events: A key event is trapped before it gets to the native GUI object, is sent through the application code, and then the (possibly modified) event is fed back into the native GUI object. The new model allows an application to change the input character into a different one: One of the component's handleEvent() methods need only change the key value while returning false. For example, to change every character input into lowercase for a TextArea, the keyUp() method should be overridden to public boolean keyUp(event evt, int key) { evt.key = Character.toLowerCase((char) key); return false; } Suppressing a character entirely is done by one of the handleEvent() methods returning true.
file:///C:/temp/java%20guru/ch11.htm (7 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

Expanding a single keystroke into a set of keystrokes is a bit messier, but can be done by generating a set of events and passing them to the parent's handleEvent(). An example is given later to do this. Caution You should remember the following points:
q

In AWT 1.0.2, filtering by the new event model is only done to key eventsbut the Sun documents suggest this will be extended to other event types over time. The AWT code to do this is broken in Windows 95 for JDK version 1.0.1 and earlier, so nothing changes if you do change the event's key. The AWT code to catch key events for the X/Motif JDK version 1.0.1 is also broken-you don't even get the events to try to change them. If the bugs get fixed, then events must be passed to the peer object, or they will never get to the native GUI object. Thus, handleEvent() must never return true, only false for key events (unless you want to discard the event).

The net result of this idea is that handleEvent() must return false for those event types which get intercepted before reaching the native GUI object. This happens by default. Even if extensive application code is invoked by receipt of the event, however, it must still return false. If handleEvent() returns true, the event will never get back to the GUI object. This is a distinct change from the old event model. On the other hand, for those event types which are not intercepted, the old event model applies, and the component that handles the event should return true. Regrettably, in JDK 1.0 you cannot assume a single approach for all event types. Adopting the old model will result in lost key events, whereas adopting the new model, returning false, leads to problems with other event types (an example appears in the section "Window Events"). This problem could have been solved more cleanly by allowing a string of keys to be stored in an event, using the old event model and then passing the (possibly modified) event into the native object. Unfortunately, this process would have to be done within the peer object, which would be messy for application developers; however, it is fairly easy for those in charge of the AWT.

The Event Class


While processing events, an application may need to make use of information such as the location of the
file:///C:/temp/java%20guru/ch11.htm (8 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

mouse in a button click. Much of this information is contained in the event itself, which is an instance of the Event class. This section discusses this class in more detail. The Event class is central to the AWT. Events are constructed by peer objects in a supposedly invisible manner. They are then fed into methods such as postEvent() and are used in handleEvent() and convenience methods. A detailed knowledge of the Event class is very necessary for use of the AWT.

Event Fields
A large set of final variables exist that just define constants; these variables are discussed in later sections. Apart from these items, the variables in each Event object are Object long int int int int int int Object Event target; when; id; x; y; key; modifiers; clickCount; arg; evt;

The target is the object the event occurred in, for example the button the mouse was pressed in. when is a timestamp for the event. The x and y fields are the coordinates of the event within the target and follow the usual practice of being measured from the top-left of the object. The key and modifiers sometimes convey extra information; they are discussed in more detail later. When objects share common characteristics, differing only in small ways, you may create separate classes for each, where each class is derived from a common parent. Alternatively, you may use non-OO tricks, distinguishing each item by different values of a field. Which method gets used depends on the designer of the class(es). For the Event type, a large number of different events exist, so having a separate class for each type would lead to a large number of derived classes, which cause confusion. The different variations on event types are instead handled by use of the id field within the single Event class. The values of this field are discussed later. Events are generated by many different objects-buttons, Lists, TextField, etc. When an Event is prepared, the arg field may be set to any suitable object by the AWT object implementing postEvent(). This field is often set to information that can be obtained from the event and is just for convenience. Because of Java safety rules, the various fields will always contain sensible values. Whether they actually have useful values depends on the type of the event.
file:///C:/temp/java%20guru/ch11.htm (9 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

Event Types
The constant values used to distinguish event types appear in Table 11.1. They appear roughly alphabetically but are grouped by function. For example, there are two focus-related events, four keyrelated events, etc. Table 11.1. Event types. ACTION_EVENT GOT_FOCUS KEY_ACTION KEY_RELEASE LIST_DESELECT LOAD_FILE MOUSE_DOWN MOUSE_EXIT SCROLL_ABSOLUTE SCROLL_PAGE_DOWN WINDOW_DEICONIFY WINDOW_ICONIFY LIST_SELECT SAVE_FILE MOUSE_DRAG MOUSE_MOVE SCROLL_LINE_DOWN SCROLL_PAGE_UP WINDOW_DESTROY WINDOW_MOVED WINDOW_EXPOSE MOUSE_ENTER MOUSE_UP SCROLL_LINE_UP LOST_FOCUS KEY_ACTION_RELEASE KEY_PRESS

Useful Event Fields


The id field is used by the toolkit and also by the Java programmer to distinguish between event types. Just as with native events, different event types have different pieces of useful information. For example, the ACTION_EVENT is generated after a button has been clicked. The toolkit designers have decided that a knowledge of the x, y coordinates of the mouse is not necessary here, but a knowledge of the button's label is. Because the different event types are all handled within the same class, some fields have useful information, but others do not. This is poor OO practice but is partly excusable. It avoids a large number of subclasses of an abstract event class, and due to the default values for Java data types, the useless fields will never have "dangerous" values in them. Table 11.2 lists the fields of the event class that are valid for the different types of event. The target field and id fields are always valid for each type. Some event types are never generated by the toolkit, so their valid fields are unknown.

file:///C:/temp/java%20guru/ch11.htm (10 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

Table 11.2. Valid event fields. Event ACTION_EVENT LIST_DESELECT LIST_SELECT GOT_FOCUS LOST_FOCUS LOAD_FILE SAVE_FILE MOUSE_DOWN MOUSE_DRAG MOUSE_ENTER MOUSE_EXIT MOUSE_MOVE MOUSE_UP SCROLL_ABSOLUTE SCROLL_LINE_DOWN SCROLL_LINE_UP SCROLL_PAGE_DOWN SCROLL_PAGE_UP KEY_ACTION KEY_ACTION_RELEASE KEY_PRESS KEY_RELEASE WINDOW_DEICONIFY WINDOW_DESTROY WINDOW_EXPOSE WINDOW_ICONIFY WINDOW_MOVED Valid Fields arg* arg arg none none never generated never generated when, x, y, modifiers, clickCount when, x, y, modifiers when, x, y when, x, y when, x, y, modifiers when, x, y, modifiers arg arg arg arg arg when, x, y, key, modifiers when, x, y, key, modifiers when, x, y, key, modifiers when, x, y, key, modifiers none none never generated none x, y

* For MenuItem and CheckboxMenuItem the fields when and modifiers are also valid.

file:///C:/temp/java%20guru/ch11.htm (11 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

This and later tables show some minor inconsistencies. For a GOT_FOCUS or LOST_FOCUS event, no additional fields of the event are set. The gotFocus() and lostFocus() methods, however, have an extra parameter object, which turns out to be just null-a pretty useless parameter, really!

arg value for ACTION_EVENT


The arg value in an event carries additional information supplied by the toolkit about the context in which the event occurred. This value does not contain any new information content because any valid information can either be obtained from other event fields or from the object the event occurred in (available in target). It is, however, convenient to use sometimes. Table 11.3 lists the value of arg for events of type ACTION_EVENT. Table 11.3. arg Values for ACTION_EVENT. Object Button Checkbox CheckboxMenuItem Choice List MenuItem TextField Value getLabel() Boolean(getState()) getLabel() getSelectedItem() getSelectedItem() getLabel() getText() Type String Boolean String String String String String

arg Value for SCROLLBAR_... Events


The events generated for the various Scrollbar actions all have a valid arg value of class Integer (note that this class is a wrapper class around the base type int). These values all contain the new slider location value.

arg Value for LIST_... Events


The events generated for LIST_SELECT and LIST_DESELECT have a valid arg value of type Integer, which is the index selected or deselected.

Tracking Mouse Clicks

file:///C:/temp/java%20guru/ch11.htm (12 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

For a simple illustration using this idea, the following example shows a box within a Canvas. When the user successfully clicks the mouse in the box, a "hit" count is updated, but when the box is missed, a "miss" count is updated instead. After each mouse click, the box gets moved to a new random location. This example uses the method mouseDown() in the Canvas object and uses the x, y values set for this method from the event information. The application, Chase.java, appears in Figure 11.1 with code in Listing 11.2. Figure 11.1 : Chase application.

Listing 11.2. Chase.java. import java.util.*; import java.lang.*; import java.awt.*; class Chase extends Frame { static public void main(String argv[]) { new Chase().show(); } Chase() { Report r = new Report(); add("North", r); ChaseArea ca = new ChaseArea(r); add("Center", ca); resize(300, 300); } } /** A status bar showing hits and misses counts */ class Report extends Panel { int HitCount = 0; int MissCount = 0; Label Hits, Misses; Report() { setLayout(new GridLayout(1, 2)); Hits = new Label("Hits: " + HitCount); Misses = new Label("Misses: " + MissCount); add(Hits);
file:///C:/temp/java%20guru/ch11.htm (13 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

add(Misses); } public void addHit() { Hits.setText("Hits: " + ++HitCount); } public void addMiss() { Misses.setText("Misses: " + ++MissCount); } } /** A Canvas with a box drawn in it that moves * randomly when the mouse is clicked in it */ class ChaseArea extends Canvas { final int box_size = 8; Rectangle box; Random rand; int box_x = 0, box_y = 0; Report report; ChaseArea(Report r) { report = r; rand = new Random(); box_x = 0; box_y = 0; } // draw a new rectangle public void paint(Graphics g) { g.drawRect(box_x, box_y, box_size, box_size); } // move the box to a random location public void moveBox() { box_x = (int) Math.floor(rand.nextFloat() * (size().width - box_size)); box_y = (int) Math.floor(rand.nextFloat() * (size().height - box_size)); repaint(); } // handle mouse down, moving box and updating report

file:///C:/temp/java%20guru/ch11.htm (14 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

line public boolean mouseDown(Event evt, int x, int y) { if (box_x <= x && x <= box_x + box_size && box_y <= y && y <= box_y + box_size) { report.addHit(); } else { report.addMiss(); } moveBox(); return true; } }

Key Events
Key and mouse events are probably the most important of the events that occur in the AWT. They are a little more complex than other events, and this is explored a little more in these two sections. There are four key events: KEY_PRESS, KEY_RELEASE, KEY_ACTION, and KEY_ACTION_RELEASE. The first two of these are generated by pressing and releasing of the "ordinary" keys such as alphabetics and punctuation. The second two are generated in response to pressing and releasing "special" keys such as the function keys, the Escape key, etc. The event's id field can be used to distinguish between these types.

Key Values
Java uses the 16-bit Unicode character set to allow for internationalized applications. This is an area where the native toolkit has great influence. Windows NT uses Unicode for all character processing. The Motif toolkit has support for international character sets using "wide" characters and XmStrings. Its implementation of the AWT toolkit does not make any use of this as yet, using only 8-bit characters. Windows 95 still uses the Windows 3.1 windowing functions, which do not understand Unicode at all. Thus, although Java admirably supports Unicode, the ordinary ASCII set is all that can be currently used portably. Isn't this a shame in a graphical environment? In addition to the "ordinary" characters, the Event class defines a number of constants for certain keys, which appears in Table 11.4. Table 11.4. Constant key values.

file:///C:/temp/java%20guru/ch11.htm (15 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

DOWN

END

HOME

LEFT

PGDN PGUP RIGHT

UP

f1 ... f12

These can be used in code as if (evt.key == Event.HOME) ...

modifiers
For some event types-the KEY... and MOUSE... types-the modifiers field is valid. modifiers is a bitmask of values, where zero means no mask. The possible masks are given in Table 11.5. Table 11.5. modifiers constants. ALT_MASK CTRL_MASK META_MASK SHIFT_MASK

For key events, they have the expected values-for example, the META key on a Sun is the "diamond" key.

Mouse Events
There are six mouse events, MOUSE_DOWN, MOUSE_DRAG, MOUSE_ENTER, MOUSE_EXIT, MOUSE_MOVE, and MOUSE_UP. MOUSE_DOWN and MOUSE_UP occur on button clicks for any other mouse buttons. MOUSE_MOVE and MOUSE_DRAG occur on moving the mouse-in MOUSE_DRAG one of the mouse buttons is held down during movement. MOUSE_ENTER and MOUSE_EXIT occur on moving the mouse pointer in and out of a window.

Modifiers
The following information is not officially documented, and some newsgroup messages suggest that this part of Java may change in the future. For mouse events, the modifiers play an unusual role in that they distinguish keys using a three-button model. With a modifier value of zero, the button selected is the left button; with a modifier value of ALT_MASK, the button selected is the middle button; with a modifier value of META_MASK, the button selected is the right button. When working with a physical mouse with fewer buttons than are logically required, one technique is to use modifier keys as described in the preceding text. Another technique is chording, where two buttons
file:///C:/temp/java%20guru/ch11.htm (16 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

are pressed simultaneously. This technique is physically cumbersome. Chording is not supported by AWT: Even though the modifier value is a bit-wise OR of values, the value of zero for the left button means it canot be tested! Many Macintosh users only have a single-button mouse, and many pc mice are still only two-button. Unless absolutely necessary, an application should try not to assume more than one button.

Displaying Events
In debugging an application and in just trying to find out what happens to events, you may want to be able to trace events as the application runs. This process can be done for the majority of events by simply overriding the top-level frame's handleEvent() to print event information and then call the overridden event handler. This step prints out all events, except those which are removed from the event chain by components lower in the window tree. The following program (Listing 11.3) shows events for a very trivial program of a label and button in a frame.

Listing 11.3. EventTest.java. import java.awt.*; public class EventTest extends Frame { public static void main(String argv[]) { new EventTest().show(); } EventTest() { // add your own windows in here add("North", new Label("Hello World")); add("South", new Button("Hello too")); resize(200, 200); } public boolean handleEvent(Event evt) { System.out.println(evt.toString()); return super.handleEvent(evt); }
file:///C:/temp/java%20guru/ch11.htm (17 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

The program prints constant integer values (such as id) in their literal, rather than symbolic, form. To interpret these values, you may need to have the Event.java source code handy.

Events with Methods


Events are eventually dealt with by a method handleEvent() of some component object. The default method does a switch on event id and often calls another method of component. While the handleEvent() method can be overridden in subclasses, doing so is not an ideal solution, and it is better to override the method called in the switch. For example, override the method action() rather than look for ACTION_EVENT in handleEvent(). Unfortunately, you cannot always use the preferred style of programming because not all event types call their own methods. Table 11.6 lists the methods called (or not called) by handleEvent() of component objects. In particular, note that none of the SCROLL_... nor WINDOW_... events are handled. Special treatment to redeem this deficiency (and a related one with Menus) appears in the section "Fixing Broken Event Handling." Table 11.6. Event types and associated methods. Event Type ACTION_EVENT LIST_DESELECT LIST_SELECT GOT_FOCUS LOST_FOCUS LOAD_FILE SAVE_FILE MOUSE_DOWN MOUSE_DRAG MOUSE_ENTER MOUSE_EXIT MOUSE_MOVE Method Called action(Event evt, Object arg) no method no method gotFocus(Event evt, Object arg) lostFocus(Event evt, Object arg) no method no method mouseDown(Event evt, int x, int y) mouseDrag(Event evt, int x, int y) mouseEnter(Event evt, int x, int y) mouseExit(Event evt, int x, int y) mouseMove(Event evt, int x, int y)

file:///C:/temp/java%20guru/ch11.htm (18 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

MOUSE_UP SCROLL_ABSOLUTE SCROLL_LINE_DOWN SCROLL_LINE_UP SCROLL_PAGE_DOWN SCROLL_PAGE_UP KEY_ACTION KEY_ACTION_RELEASE KEY_PRESS KEY_RELEASE WINDOW_DEICONIFY WINDOW_DESTROY WINDOW_EXPOSE WINDOW_ICONIFY WINDOW_MOVED

mouseUp(Event evt, int x, int y) no method no method no method no method no method keyDown(Event evt, int key) keyUp(Event evt, int key) keyDown(Event evt, int key) keyUp(Event evt, int key) no method no method no method no method no method

Generating Events
Because the AWT responds to events, it is useful to know where the events come. The primary source is from the toolkit itself, which generates events in response to user actions.

Toolkit Generated Events


One complicated area of AWT is knowing what events get generated by what components. For example, an ACTION_EVENT is generated when a button is clicked, but what other events are generated for buttons? While it would be nice to be able to provide a table of what events are generated for what objects, it is not possible-no such table exists. The following list provides some of the particularly worrying aspects of AWT in the JDK version 1.0.1:
q

The events generated for an object can change if other objects are added. For example, an application consisting of just one label in a frame will generate key events for the label under Motif. Add another object, such as a button, and the label will not do so anymore. Although apparently trivial, an application with only a label could be used for a digital clock display, so such inconsistent behavior can show up in real applications. A discrepancy in behavior often exists between Windows 95 and Motif. For example, in Windows 95, KEY_UP (and other KEY events) are generated for TextArea and TextField. For Motif, they

file:///C:/temp/java%20guru/ch11.htm (19 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

are not. This particular problem seems to have been fixed in JDK 1.0.2. There is no workaround for older versions. A number of objects generate events that would not appear to be of interest to them. For example, Panel generates mouse motion events. Why should it do this? It is just an object to contain other objects. It is not as though Panel should be used for graphics drawing, which would need mouse tracking-Canvas gets used for that.

These problems could all chalked up to implementation hiccups if an implementation-independent specification of what should happen existed. Unfortunately, no such document seems to exist at the moment.

Application Generated Events


In the native toolkits, you may be able to synthesize events and send them to objects. Can this be done in AWT, and is there any point to it? It can certainly be done; this process is exactly what happens in the peer objects. Methods such as action() in the button's peer object get invoked by the native toolkit when the button is clicked. The method creates an event and posts it public void action() { target.postEvent(new Event(target, Event.ACTION_EVENT, ((Button)target).getLabel())); } where the target is the button. Is it worthwhile for an application to do this? The answer depends, unfortunately, on the type of event and whether it is handled under the old or new event model. Now look at two cases, a button under the old model and a TextField under the new one. When a button is clicked, the native toolkit catches the event and deals with it. It changes the button's appearance, depressing and raising it. After all this is over, control is passed to AWT to create an ACTION_EVENT and post it to the button. The native event is handled by the old model: You can discard or change the AWT event without any effect on the GUI appearance-all that was already dealt with before the AWT event was created. You can create an ACTION_EVENT and post it to a button. The application-specific code will still run in response to the event, but it will have no effect on the native implementation of the button. Thus, the button won't depress and raise. But other things won't happen that are also important: Selecting a button in an application that currently does not have the focus will cause it to rise to the top of other applications
file:///C:/temp/java%20guru/ch11.htm (20 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

and set the focus to the button (under many window managers). This cannot be done in Java as yet. Similar things happen with List. You can create a LIST_SELECT event and send it to the List object. The application will process the new selection, but the native GUI object will still be showing the old selection. The net result of this for the old model is that the application may get out of synch with its native GUI implementation. This should be avoided. Easier ways probably exist, such as calling select() for the List object, which removes these problems. So much for the old event model. In the new model, the application acts as a filter on the event, and changes are permissible. From JDK 1.0.2 onwards, what happens with keystrokes in TextComponents is this: The keystroke is captured before it reaches the native object and passed up to AWT. Application code has the ability to modify or discard the event before it finally gets back to the native object via the TextComponent's peer. The (possibly modified) event is then allowed to affect the native object in the normal way. In the new event model, it does make sense to create and post events from within the application. The created events will not only affect the application but will also make their way into the native GUI object. For example, a single keystroke could be expanded into a sequence of strokes by overriding keyUp() in a subclass of the TextComponent: public boolean keyUp(Event evt, int key) { if (key == 9) { // expand tab to 8 spaces Event e = new Event(evt.target, evt.when, Event.KEY_RELEASE, evt.x, evt.y, ' ', evt.flags, null)); if (peer != null) for (int n = 0; n < 8; n++) { peer.handleEvent(e); // lose the tab event return true; } // handle other keys normally return super.handleEvent(evt); } The preceding code short-circuits what any of its window parents might want to do to the new sequence of events. It also assumes that a subclass of TextArea/TextField is doing this. To relax these restrictions gets messier: You have to avoid potentially recursing around your macro expansion by passing the new
file:///C:/temp/java%20guru/ch11.htm (21 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

events up to the parent's postEvent() and then deal with peer handling if the event comes back.

Fixing Broken Event Handling


In several places in this chapter, we have mentioned problems with the event model. Some of these cannot be fixed by the user of the AWT and will have to wait until the authors of the libraries fix them. On the other hand, many problems can be dealt with. This section looks at some techniques that an application writer can use.

When to Handle Events


If handleEvent() for a component returns false, then the event is passed to handleEvent() for its parent in the window tree. This leads to two common locations for handling events: in the object for which they were generated or in some ancestor object, typically a frame. If Frame gets used to handle events, then it generally receives events from many descendants. Consequently, it will have to figure out which descendant the event came from before it can call the relevant application code. You see much code like this in a subclass of Frame: public boolean action(Event evt, Object what) { String name = (String) what; if (name.equals("File")) // handle File button else if (name.equals("Quit")) // handle Quit button // etc } This process can lead to very fragile code. Consider an application with a few hundred buttons:
q

Each button must have a different name, or Frame cannot distinguish between them. You could have a severe maintenance problem in that each programmer needs to have the name of every other object that can generate ACTION_EVENT. The application code for every button will be called from Frame's action() procedure, which could lead to a huge and inefficient method branching to application code for each button. It doesn't encourage modularity at all.

On the other hand, this technique makes it easy to deal with small applications. Because most of the applets and applications seen on the Net so far are fairly small, many examples exist using this method. It doesn't scale, though.
file:///C:/temp/java%20guru/ch11.htm (22 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

If each object handles its own events, then you have to subclass each object to hold the application code. Each individual action() method will only contain the application code for that object because it won't need the distinction test between objects. The downside is that it leads to a large number of subclasses, each there just to supply application code for that object. Some intermediate possibilities exist, of course, but these two extremes are the most commonly seen. Of the two, the second seems preferable, where each object handles its own events. It means that you can rename the object, move it around the window hierarchy, and so on without having to worry about losing the association between the object and its application code. Neither of these extremes satisfy demands for separation of GUI object and application code-these concerns are addressed in the section "Major Surgery to the Event Model."

Missing Methods
For some types of events, handleEvent() calls a convenience method. For example, an event of type KEY_PRESS gets handled by keyDown(). Many events, unfortunately, are not, introducing an inconsistency in handling them. This section discusses how consistency can be brought in for these other event types. Basically it is quite simple: Define a subclass of the object affected, redefine handleEvent() for this subclass to use convenience methods for these event types, and from then on, use the new class and its new convenience methods rather than the old one.

List Events
List objects generate LIST_SELECT and LIST_DESELECT events. No branch occurs in handleEvent() of component for these event types, so they usually get handled by redefining handleEvent() in application code for each List object needed or deferring the problem to a frame that knows about all of the lists. A much simpler solution is to fix the problem once in a subclass of List and then to use this subclass when needed. class SelectList extends List { public boolean handleEvent(Event evt) { List list = (List) evt.target; int index = ((Integer) evt.arg).intValue(); switch (evt.id) { case Event.LIST_SELECT: return selected(evt, index, list.getItem(index));
file:///C:/temp/java%20guru/ch11.htm (23 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

case Event.LIST_DESELECT: return deselected(evt, index); default: return super.handleEvent(evt); } } public boolean selected(Event evt, int index, String item) { return false; } public boolean deselected(Event evt, int index) { return false; } } The SelectList class defines two convenience methods, selected() and deselected(). Each of these methods has as a parameter the index of the item selected/deselected. In the case of selection, it is also worthwhile to include the item selected, for convenience. (The SelectList also requires constructor methods like those of List. They do not appear here.) An example of this is given by the application of Listing 11.4, which displays a list of colors and repaints the label when a color is selected. The application appears in Figure 11.2. Figure 11.2 : Colors application.

Listing 11.4. Colors.java. import java.awt.*; class Colors extends Frame { final Color colors[] = {Color.red, Color.blue, Color.green}; final String colorLabels[] = {"red", "blue", "green"}; Label label; public static void main(String argv[]) { new Colors().show(); } Colors() {
file:///C:/temp/java%20guru/ch11.htm (24 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

ColorList list = new ColorList(); for (int n = 0; n< colors.length; n++) list.addItem(colorLabels[n]); label = new Label("Hello World"); // set geometry add("West", list); add("Center", label); resize(300, 100); } public void setColor(int index) { label.setForeground(colors[index]); } } class ColorList extends SelectList { public boolean selected(Event evt, int index, String item) { ((Colors) getParent()).setColor(index); return true; } } class SelectList extends List { public boolean handleEvent(Event evt) { List list = (List) evt.target; int index = ((Integer) evt.arg).intValue(); switch (evt.id) { case Event.LIST_SELECT: return selected(evt, index, list.getItem(index)); case Event.LIST_DESELECT: return deselected(evt, index); default: return super.handleEvent(evt); } } public boolean selected(Event evt, int index, String item) { return false;

file:///C:/temp/java%20guru/ch11.htm (25 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

} public boolean deselected(Event evt, int index) { return false; } }

Scrollbar Events
Events related to Scrollbar are SCROLL_LINE_UP, SCROLL_LINE_DOWN, SCROLL_PAGE_UP, SCROLL_PAGE_DOWN, and SCROLL_ABSOLUTE. No convenience method gets called by handleEvent()for these event types. Fix this up in the same way as for the List class by defining a subclass with a set of convenience methods. From then on, use this new class. class SelectScrollbar extends Scrollbar { public boolean handleEvent(Event evt) { switch (evt.id) { case Event.SCROLL_ABSOLUTE: return scrollAbsolute(evt, evt.arg); case Event.SCROLL_LINE_DOWN: return scrollLineDown(evt, evt.arg); case Event.SCROLL_LINE_UP: return srcollLineUp(evt, evt.arg); case Event.SCROLL_PAGE_DOWN: return scrollPageDown(evt, evt.arg); case Event.SCROLL_PAGE_UP: return scrollPageUp(evt, evt.arg) default: return super.handleEvent(evt); } } public boolean scrollAbsolute(Event evt, Object what) { return false; } public boolean scrollLineDown(Event evt, Object what) { return false; }
file:///C:/temp/java%20guru/ch11.htm (26 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

public boolean scrollLineUp(Event evt, Object what) { return false; } public boolean scrollPageDown(Event evt, Object what) { return false; } public boolean scrollPageUp(Event evt, Object what) { return false; } }

WINDOW Events
WINDOW events are also not dealt with by handleEvent(),which leads to two problems:
q

When a WINDOW_DESTROY is received, the application should exit, as it is a signal from the user clicking on the Windows frame Exit button. Of course, the application may need to do some cleanup work first. Anyway, the default should be to exit rather than ignore the event-users don't like applications that refuse to go away. When a WINDOW_ICONIFY is received in JDK version 1.0.1, the application may want to pause some graphics operations but continue with other operations. At present, the default is to ignore this event. In at least one case, this default is wrong, such as when an applet has created another top-level window such as a dialog box. If the dialog is iconified, the event travels all the way up to the AppletViewer, which thinks that it has been iconified! Its action is to stop the applet just because a dialog has been iconified, which is clearly wrong. This behavior has been rectified in JDK 1.0.2.

These problems should have a default handler that can be overridden by application-specific code. Again, you need new classes with new convenience methods implementing the default behavior. Window events are generated for Frame, Dialog, and Window. You need a new subclass for each. class WindowHandlingFrame extends Frame { public boolean handleEvent(Event evt) { switch (evt.id) { case Event.WINDOW_ICONIFY: return windowIconified((); case Event.WINDOW_DESTROY: return windowQuit();
file:///C:/temp/java%20guru/ch11.htm (27 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

default: return super.handleEvent(evt); } public boolean windowIconified() { return true; } public boolean windowQuit() { System.exit(0); return true; } } You should have similar classes for Dialog and Window.

Menu Events
Event handling within menus has been designed to be different than component, which can be a nuisance. When a MenuItem is selected, an event with id set to ACTION_EVENT is generated, and postEvent() is executed for that object. postEvent(), however, does not call handleEvent() like component objects do-no method handleEvent() exists for MenuItems. Instead, it calls the postEvent() method for the parent in the GUI tree. Consequently, it walks up the GUI tree until it gets to the ancestor Frame object and executes handleEvent() for the Frame. Because no handleEvent() method exists for MenuComponent objects, none of the convenience methods events such as action() or mouseDown() exist to handle menu events. This approach seems poor because it requires the Frame object to know all sorts of detail information about the structure of its menu, which will make the application difficult to change as it evolves. You can easily make menus behave in the same way as components, but it means tampering with a method that the designers of the AWT toolkit would probably prefer you not bother: the postEvent method. This is really part of the implementation side that normally should not be overridden. That solution seems the simplest way to get consistency in event handling. You have no interest in actions for MenuBar and Menu-only in MenuItem. Define a new subclass of MenuItem with two methods postEvent and action, which shortcuts the component mechanism that also uses the handleEvent method. class MenuButton extends MenuItem { public boolean postEvent(Event evt) {
file:///C:/temp/java%20guru/ch11.htm (28 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

if (evt.id == Event.ACTION_EVENT) if (action(evt, evt.arg)) return true; } return super.postEvent(evt); } public boolean action(Event evt, Object what) { return false; } } This code uses the old event model, which is currently correct for MenuItem. If later versions of AWT change menu handling to the new event model, then you need to modify this code to call peer object handleEvent() on failure. In the future, subclass all menu buttons from MenuButton, with application logic in action(): class QuitButton extends MenuButton { public boolean action(Event evt, Object what) { System.out.println("Exiting..."); System.exit(0); return true; } } For example, consider a program with a label in a frame, where you can change the foreground color of the label by selection from a menu. This program is similar in intent to the program Colors.java of Listing 11.4 but uses a menu to select the color instead of a list. The complete code appears in Listing 11.5.

Listing 11.5. ColorMenu.java. import java.awt.*; public class ColorMenu extends Frame { private Label label; public static void main(String argv[]) { ColorMenu cm = new ColorMenu(); cm.show();
file:///C:/temp/java%20guru/ch11.htm (29 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

} ColorMenu() { label = new Label("Hello"); add("Center", label); CreateMenu(); resize(100, 100); } private void CreateMenu() { MenuBar mb = new MenuBar(); Menu fileB = new Menu("Color"); mb.add(fileB); ColorMenuButton blueB = new ColorMenuButton("Blue", this); ColorMenuButton redB = new ColorMenuButton("Red", this); fileB.add(blueB); fileB.add(redB); setMenuBar(mb); } public void changeColor(Color col) { label.setForeground(col); } } class MenuButton extends MenuItem { MenuButton(String name) { super(name); } public boolean postEvent(Event evt) { if (evt.id == Event.ACTION_EVENT) { if (action(evt, evt.arg)) return true; } return super.postEvent(evt); } public boolean action(Event evt, Object what) {
file:///C:/temp/java%20guru/ch11.htm (30 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

return false; } } class ColorMenuButton extends MenuButton { ColorMenu toplevel; ColorMenuButton(String name, ColorMenu top) { super(name); toplevel = top; } public boolean action(Event evt, Object what) { String name = (String) what; if (name.equals("Red")) toplevel.changeColor(Color.red); else toplevel.changeColor(Color.blue); return true; } }

Now, look at selected parts of this code. The class ColorMenu is a top-level frame extended to hold a label and a menu. An additional method is used to set the color of the label. MenuButton derives from MenuItem and adds the new postEvent and action() methods. You also have to add in a constructor method that just calls the super constructor. This step needs to occur because MenuItem has only one constructor, which takes a String parameter, and such constructors cannot be inherited in Java. The buttons you actually want in the menu are of class ColorMenuButton, which derives from the MenuButton class and overrides the action() method. action() calls the method changeColor() of the top-level ColorMenu, which resets the foreground of its label. The top level is passed through as a parameter in the constructor. An alternative could be to walk up the parent tree when required until the top level is reached.

A Complete Example
The following example enables the selection of an image from a list and shows the image in a clipped
file:///C:/temp/java%20guru/ch11.htm (31 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

area. This setup uses the modified List class of SelectList to handle the selection and the modified Scrollbar class of SelectScrollbar to handle the interaction with the scrollbars. These two classes are omitted from the following listing because they have already been presented. A screen capture of this applet running from within the AppletViewer appears in Figure 11.3, with the program shown in Listing 11.6. The applet HTML appears in Listing 11.7. Figure 11.3 : An image viewer applet.

Listing 11.6. ImageViewerApplet.java. import java.awt.*; import java.awt.image.*; import java.applet.Applet; /** * An applet class to view images */ public class ImageViewerApplet extends Applet { String imageStrings[] = {"udp.gif", "tcp.gif"}; ImageViewer imageViewer; // initialize applet to show first image public void init() { Image image = getImage(getDocumentBase(), imageStrings[0]); imageViewer = new ImageViewer(image); ImageList imageList = new ImageList(imageStrings); imageList.select(0); setLayout(new BorderLayout()); add("West", imageList); add("Center", imageViewer); } // set a new image in the viewer public void setImage(String item) { Image image = getImage(getDocumentBase(), item); imageViewer.setImage(image);
file:///C:/temp/java%20guru/ch11.htm (32 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

} } /** * a class to display a list of image names and set a new image */ class ImageList extends SelectList { ImageList(String items[]) { for (int n = 0; n < items.length; n++) { addItem(items[n]); } } public boolean selected(Event evt, int index, String item) { ((ImageViewerApplet) getParent()).setImage(item); return true; } } /** * a composite class to show an image with scrollbars */ class ImageViewer extends Panel {

ImageCanvas imageCanvas; ImageScrollbar horizontalSB, verticalSB; ImageViewer(Image image) { imageCanvas = new ImageCanvas(image); horizontalSB = new ImageScrollbar(Scrollbar.HORIZONTAL, 0, 200, 0, 200, imageCanvas); verticalSB = new ImageScrollbar(Scrollbar.VERTICAL, 0, 200, 0, 200, imageCanvas); // Set the geometry layout GridBagLayout gridBag = new GridBagLayout();
file:///C:/temp/java%20guru/ch11.htm (33 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

setLayout(gridBag); // first the imageCanvas GridBagConstraints c = new GridBagConstraints(); c.weightx = 1.0; c.weighty = 1.0; c.fill = GridBagConstraints.BOTH; gridBag.setConstraints(imageCanvas, c); add(imageCanvas); // then the horizontal Scrollbar c.weightx = 0.0; c.weighty = 0.0; c.gridx = 0; c.gridy = 1; gridBag.setConstraints(horizontalSB, c); add(horizontalSB); // finally, the vertical Scrollbar c.gridx = 1; c.gridy = 0; gridBag.setConstraints(verticalSB, c); add(verticalSB); } void setImage(Image img) { imageCanvas.setImage(img); } void setHorizontalScrollbar(int value, int visible, int min, int max) { horizontalSB.setValues(value, visible, min, max); } void setVerticalScrollbar(int value, int visible, int min, int max) { verticalSB.setValues(value, visible, min, max); } int getHorizontalValue() { return horizontalSB.getValue(); }

file:///C:/temp/java%20guru/ch11.htm (34 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

int getVerticalValue() { return verticalSB.getValue(); } } /** * a class to display the image */ class ImageCanvas extends Canvas { Image image; int imageWidth, imageHeight; int top = 0, left = 0; int width = 200, height = 200; ImageCanvas(Image img) { image = img; } // display/redisplay the image public void paint(Graphics g) { int x, y; ImageViewer parent = (ImageViewer) getParent(); // find current scrollbar values x = parent.getHorizontalValue(); y = parent.getVerticalValue(); g.drawImage(image, -x, -y, this); // reset scrollbar values in case image changed // or resized imageHeight = image.getHeight(this); imageWidth = image.getWidth(this); height = this.size().height; width = this.size().width; parent.setHorizontalScrollbar(x, width, 0, imageWidth - width); parent.setVerticalScrollbar(y, height, 0, imageHeight - height); } // install a new image and display it
file:///C:/temp/java%20guru/ch11.htm (35 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

public void setImage(Image img) { image = img; repaint(); } } /** * a class to provide a scrollbar to manage an image display */ class ImageScrollbar extends SelectScrollbar { ImageCanvas canvas; ImageScrollbar(int o, int v, int vis, int min, int max, ImageCanvas c) { super(o, v, vis, min, max); canvas = c; } public boolean scrollLineUp(Event evt, Object what) { canvas.repaint(); return true; } public boolean scrollLineDown(Event evt, Object what) { canvas.repaint(); return true; } public boolean scrollPageUp(Event evt, Object what) { canvas.repaint(); return true; } public boolean scrollPageDown(Event evt, Object what) { canvas.repaint(); return true; } public boolean scrollAbsolute(Event evt, Object what) { canvas.repaint(); return true; } }

file:///C:/temp/java%20guru/ch11.htm (36 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

Listing 11.7. HTML to show Image Viewer. <html> <head> <title> An Image Viewer </title> </head> <body> <h1 align="center">An Image Viewer</h1> <applet code="ImageViewerApplet.class" width=400 height=300> Since your browser cannot run applets, this is what the application looks like: <br> <img src="ImageViewer.gif" align="center"> </applet> </body>

Major Surgery to the Event Model


The AWT event model has generated a lot of complaints from programmers. Mostly, these issues revolve around confusion about what is actually going on, with two event models, many bugs, and no clear statement about what events can be generated for each object class. These concerns are serious and can have a practical everyday effect; this chapter has attempted to resolve some of these issues. If you step back a little from these immediate concerns, the AWT event model still shows fundamental problems. Consider the two major ones:
q

The current model either leads to a frame knowing too much about all of its component objects or to a proliferation of subclasses which just override a small number of methods to supply application code. It is important to try to separate any application into functionally separate components; this is what OO programming is all about. In particular, it is important to try to separate presentation from application, for example, GUI code from application code. The current model does not

file:///C:/temp/java%20guru/ch11.htm (37 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

encourage this separation. Just as the AWT event model is built above other event models, it is possible to build new event models above AWT. The following sections discuss two other models that have become publically available. The first is the awtExt package of Sal Cataudella. The second is the awtCommand class of Jan Newmarch, the author of this chapter.

The awtExt Package


The Xt and Motif toolkits for the X Window System use an event handling mechanism called callback functions. When a GUI object is created, so-called callback functions can be added to the object that gets executed when events occur in the object. For example, the Motif Pushbutton can have callback functions added that will be called on pressing the button down or on releasing it. Application code gets placed in these callback functions. Basically, all that an Xt/Motif application has to do to add application code is to have the event handling mechanism execute these callback functions without requiring the application to do any event dispatching. The awtExt package transports this idea into the Java realm. Each AWT GUI object gets subclassed by one which knows about callback methods. A callback method is an arbitrary method of an arbitrary class that can be attached to one of these new objects. Because the awtExt package handles and dispatches events itself, you have no need for any overriding of handleEvent() or its convenience methods. Because each GUI object of the awtExt class can have callbacks attached, you don't need to create subclasses of the GUI objects just to hold application code, which solves the first set of problems in the AWT model. Selecting and Showing Colors Look at how the Colors.java program is done using this event handling package. The revised program, ExtColors.java, appears in Listing 11.8.

Listing 11.8. Colors using awtExt. import java.awt.Frame; import java.awt.Label; import java.awt.Color; import sysExt.*;
file:///C:/temp/java%20guru/ch11.htm (38 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

import awtExt.*; public class ExtColors extends Frame { final Color colors[] = {Color.red, Color.blue, Color.green}; final String colorLabels[] = {"red", "blue", "green"}; Label label; public static void main(String argv[]) { new ExtColors().show(); } public ExtColors() { List list = new List(); for (int n = 0; n < colors.length; n++) list.addItem(colorLabels[n]); // add the callback method try { list.eventDispatch.LIST_SELECT = Callback.newRef(this, "listSelect"); } catch(Exception e) {e.printStackTrace();} label = new Label("Hello World"); // set geometry add("West", list); add("Center", label); resize(300, 100); } public void setColor(int index) { label.setForeground(colors[index]); } public void listSelect(CallbackInfo cbi) { List list = (List) cbi.thisAwtObj; setColor(list.getSelectedIndex()); } }

The awtExt package defines a subclass of each of the standard AWT GUI objects, awtExt.Button, awtExt.List, etc. This reuse of names can be a little bit of a nuisance. Instead of importing all of
file:///C:/temp/java%20guru/ch11.htm (39 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

java.awt and all of awtExt, you have to be more selective about which classes get imported, or use package-qualified names such as java.awt.Button. Anyway, this point is minor. Ordinary AWT objects and awtExt objects may get mixed in the same program. The example uses objects from the java.awt package, such as java.awt.Frame, and objects from the awtExt package, such as awtExt.List. The interesting part comes from the fact that you do not need to subclass List just to hold application code. Instead, a callback method is added to the awtExt list for this, which is done in the lines list.eventDispatch.LIST_SELECT = Callback.newRef(this, "listSelect"); Consequently, the method this.listSelect() will be executed automatically when the List object receives a LIST_SELECT event. The newRef() method exploits a generally unknown part of Java, the capability to generate a method call from the name of the method. By passing in the String "listSelect", the sysExt package included as part of awtExt can later execute a call to the listSelect() method. The code has an exception handler around it because newRef() can throw an InvalidMethodRefException exception. When the method listSelect() executes, it does so with a parameter of class CallbackInfo. CallbackInfo has public fields of the event and also of the awtExt object in which the event occurred. The awtExt object is used in the method to find the current index selected. Basically, Frame doesn't need to override handleEvent(), and you haven't had to subclass List. Although this example seems fairly trivial, the technique scales well; even if you had thousands of objects, you would not have to override handleEvent() or subclass the GUI objects. Many very large Xt/Motif programs have been written using this type of event model. A number of methods exist to install and manipulate callback methods. Many of them are convenience ones for cases where only one callback should occur (button with an ACTION_EVENT callback is one case). These areas can make the code look simpler. Availability The awtExt package is available from the Web page http://www.panix.com/~rangerx/packages.html.

The awtCommand Package


The other major issue in the AWT model is separation of GUI code from application code. This concept
file:///C:/temp/java%20guru/ch11.htm (40 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

becomes important because an object whose purpose is to read in the contents of a file should not need to know anything about the FileDialog object that allowed the filename to be selected; a request to show a piece of information to the user should not be dealt with directly in terms of Dialog objects, but should be given to an object that can deal with such objects. The support given by AWT for the separation between GUI code and general application code is fairly low. The majority of applets and applications available today have application code scattered throughout GUI code. Whenever a change is made to any part of the GUI interface, rewrites of the application code are often required. These changes are often minor: changing a GUI object's name or its path in the window tree. This setup also tends to promote the nearest equivalent to global variables: Every GUI object is known by a unique name to the top-level frame! For example, the following type of code is common: public boolean action(Event evt, Object what) { String name = (String) what; if (name.equals("File")) if (fileDialog == null) fileDialog = new FileDialog(...); fileDialog.show(); // etc } This process requires FileDialog to be an object known to this object. The code becomes fragile with respect to name clashes and other more subtle considerations-how would you translate this application to a French version? Of course, you cannot completely separate the two: The application code does need to communicate with the GUI side after all! The GUI code, however, should not need to know the detailed internals of the application objects, and vice versa. Ideally, you should be able to swap a command-line interface for a Windows interface without changing any of the application objects. AWT does not give direct support for separation. How about the awtExt package described in the preceding section? The example given was poor in one respect: It cast the application code into the Frame, often considered a bad thing; however, the example was small enough to get away with it. The awtExt package, in fact, gives some support for separation because any method of any object could have been used as the callback. For example, the callback could have been set as list.eventDispatch.LIST_SELECT = Callback.newRef(new ApplicationObject(), "colorSelected");

file:///C:/temp/java%20guru/ch11.htm (41 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

to place the processing within the colorSelected() method of an ApplicationObject. This object may have a very limited knowledge of the user interface. The Command Class Model The awtExt package allows separation but does not enforce it. A more disciplined approach is to use an event model that enforces separation by default, which the Command class of the awtCommand package supplies. The Command class model separates GUI objects from application behavior by placing application behavior in subclasses of Command objects. For example, when the application wants a file to be saved, it should call on the FileSaveCommand object to perform this action, instead of making GUI objects, such as a frame or a MenuItem, perform this task. The book Design Patterns by Gamma, Helm, Johnson and Vlissides provides an excellent look above the language level to identify "good" patterns of usage and design of object classes. For event handling, it identifies this Command class as appropriate for this. Each GUI object has associated Command objects to handle the application code. Each Command object has a method execute() that is invoked to perform this application-specific code. This object uses as little information as possible about its GUI environment to perform its tasks. A GUI object from the awtCommand package does not perform application-specific code itself. It "installs" a Command object. When an event of interest to the GUI object occurs, the object invokes the execute() method on its Command object. This capability allows Command objects to be written more or less independently of GUI objects. The implementation of both the GUI code and the application code can then be varied independently, as long as they use the same Command objects. The Command Class The Command class defines one abstract method execute(). This could be implemented either as an abstract class or as an interface. An application will be expected to have a fairly complex class structure of its own. An interface allows the Java "multiple inheritance" model to work well here, so Command is defined as an interface. Each object has a set of events that it handles. For example, a List object will generate LIST_SELECT, LIST_DESELECT, and ACTION_EVENT events. There will be a (possibly) different Command object used to handle each of these. The LIST_SELECT event will be handled by a selectCommand object, the EVENT_ACTION event will be handled by an actionCommand object, etc.

file:///C:/temp/java%20guru/ch11.htm (42 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

The awtCommand package subclasses all of the relevant AWT classes. Each class is prefixed with "C" (really, the prefix should be "Command," but that is too verbose). So CList is a subclass of List, CFrame is a subclass of Frame, etc. Each of these classes has additional methods over the parent class to allow a Command object to be attached. These methods have names based on the event types that they handle. In order to associate Command objects with awtCommand objects, a method sets the Command object for each event type. For example, CList has additional methods: setSelectCommand(Command c) setDeselectCommand(Command c) setActionCommand(Command c) When an event occurs for which a Command object has been registered, the awtCommand package invokes the following method of the Command object: execute(Object target, Event evt, Object what) The actual Command object is an instance of a subclass, which contains the application code in the execute method. The targetparameter is the object the event occurred in, and the what parameter is similar to the what parameter of component methods such as action(). If no Command object is registered for a particular type of event, then the original event processing is done. (For example, for component objects, the method handleEvent will pass the event to its parent in the GUI tree. For MenuComponent objects, the method postEvent will pass the event to its parent.) This setup allows the event-handling techniques of the AWT tookit to be still used if needed. For example, an AWT application will continue to work if all AWT objects are changed to awtCommand objects without other changes. This allows several ways of writing applications using Command objects:
q q q

Use the ordinary AWT techniques. In this case, why bother with this toolkit? Attach Command objects to the GUI objects that generate events, which is the most common use. Attach Command objects to ancestors of the GUI objects, which may be appropriate if Command objects are shared by many GUI objects. For example, a Command object attached to a CMenu could handle all the events from CMenuItem children.

Selecting and Showing Colors The following application is another variation of the program, which shows a list of colors next to a label. When one of the colors is selected, the label's foreground changes to that color. This is the program of Listing 11.4 adapted to use the Command class and is given as Listing 11.9.

file:///C:/temp/java%20guru/ch11.htm (43 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

A Command object is used to process the LIST_SELECT events. It is created and installed by ColorCommand command = new ColorCommand(this, colors); list.setSelectCommand(command); Two parameters are passed through in the constructor: the list of colors and the top-level frame. The list of colors is passed so that the execute()method can later determine which color is selected. The frame is passed through in an attempt to minimize the amount of knowledge the Command object needs to have about the GUI side. The "application code" here is fairly trivial-it just has to figure out what color was selected and then call back into the GUI code to set the color. Sufficient information is passed into the Command object's constructor and in the parameters to execute() to do all of this. The Command object knows very little about the structure of the GUI side, just calling on a method of the top-level frame to set the color. To see the separation of application code from GUI code even in this simple example, consider the changes that would need to be made if the label was changed into a button. For the Command object, no changes would be needed at all. For the Frame object, the occurrences of the label would be changed into a button. More substantial changes, such as changing the color of a tree of windows, not just a single one, would also only need changes on the frame side. On the other hand, changing from a List selection to a Menu selection would involve changes to the Command object because the execute() method can only examine the String name of the selected menu item. The changes are still relatively minor, involving adding String handling.

Listing 11.9. CommandColors.java. import java.awt.*; import java.awtCommand.*; class CommandColors extends CFrame { final Color colors[] = {Color.red, Color.blue, Color.green}; final String colorLabels[] = {"red", "blue", "green"}; Label label; public static void main(String argv[]) { new CommandColors().show(); } CommandColors() {
file:///C:/temp/java%20guru/ch11.htm (44 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

// a CList showing the color choices CList list = new CList(); for (int n = 0; n < colors.length; n++) list.addItem(colorLabels[n]); // set a Command invoked on button select ColorCommand command = new ColorCommand(this, colors); list.setSelectCommand(command); label = new Label("Hello World"); // set geometry add("West", list); add("Center", label); resize(300, 100); } public void setColor(Color color) { label.setForeground(color); } } class ColorCommand implements Command { CommandColors app; Color colors[]; // Constructor stores local info ColorCommand(CommandColors app, Color colors[]) { this.app = app; this.colors = colors; } public void execute(Object target, Event evt, Object what) { int index = ((Integer) what).intValue(); app.setColor(colors[index]); } }

Availability
file:///C:/temp/java%20guru/ch11.htm (45 of 46) [2/3/2003 8:19:19 AM]

Chapter 11 -- Advanced Event Handling

The awtCommand package is available from http://pandonia.canberra.edu.au/java/ or by anonymous ftp from ftp://ftp.canberra.edu.au/pub/motif/command/.

Summary
AWT event handling is fraught with problems. Obvious bugs exist, and no clear specifications to resolve these issues have been created. Two event models exist with a vague promise that events will move from the old to the new model. Inconsistencies occur in event handling with missing methods, inappropriate defaults, and different handlers between component and MenuComponent objects. The release of JDK 1.0 has stated that the libraries have basically been frozen-which is clearly a mistake as far as event handling is concerned. One can only hope that this part of JDK 1.0 will be allowed to evolve rapidly. Apart from the bugs, most of these problems can be worked around. This chapter has shown how new subclasses can be defined that resolve many of these issues. It has also supplied much information that is needed by the AWT programmer to use the toolkit effectively. The AWT event models do not directly support separation of view from the model. Two alternative event models have been built above AWT which try to fix this. They are both available in source code form, free to use in your own projects. They are written in Java so that they may be used in both applets and applications. Each model represent ways of handling events in large scale Java systems where the simple techniques used in current small systems will break down.

file:///C:/temp/java%20guru/ch11.htm (46 of 46) [2/3/2003 8:19:19 AM]

Chapter 12 -- Image Filters and Color Models

Chapter 12
Image Filters and Color Models

CONTENTS
q q q q q q q q q q

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

One of the most compelling features of Java is its wide support for the presentation of graphical information. Along with providing a simple means of displaying static images, Java enables developers to manipulate and animate images in ways previously impossible in Web content. At the heart of Java graphics and imaging are Java color models. This chapter takes a close look at what a color model is, along with how color models impact image handling and Java graphics in general. In this chapter, you also learn about Java image filters and how they are used to manipulate graphical images. Image filtering is a powerful feature of Java that is tightly linked to color models. Java provides a variety of image filter classes that interact together to form a framework for easily filtering graphical images. You can extend the standard Java image filtering classes and build your own image filters to perform just about any type of image processing you can imagine. You learn how to implement your own image filters near the end of this chapter. Together, color models and image filters form an integral part of the advanced Java graphics API. By the end of this chapter, you will be well on your way to becoming a Java graphics wizard!

file:///C:/temp/java%20guru/ch12.htm (1 of 24) [2/3/2003 8:19:25 AM]

Chapter 12 -- Image Filters and Color Models

Understanding Color
Before jumping into the specifics of what a color model is and how it works in Java, it's important to understand how color is represented on a computer in general. Although most operating systems have some degree of platform-dependent handling of color, they all share a common approach to the general representation of colors. Knowing that all data in a computer is ultimately stored in a binary form, it stands to reason that physical colors are somehow mapped to binary values (numbers) in the computer domain. The question is, how are colors mapped to numbers? One way to come up with numeric representations of colors would be to start at one end of the color spectrum and assign numbers to each color until you reach the other end. This approach solves the problem of representing a color as a number, but it doesn't provide any way to handle the mixing of colors. As anyone who has experienced the joy of Play-Doh can tell you, colors react in different ways when combined with each other. The way colors mix to form other colors goes back to physics, which is a little beyond this discussion. A computer color system needs to be able to handle mixing colors with accurate, predictable results. The best place to look for a solution to the color problem is a color computer monitor. A color monitor has three electron guns: red, green, and blue. The output from these three guns converge on each pixel of the screen, exciting phosphors to produce the appropriate color (see Figure 12.1). The combined intensities of each gun determine the resulting pixel color. This convergence of different colors from the monitor guns is very similar to the convergence of different colored Play-Doh The primary difference is that monitors use only these three colors (red, green, and blue) to come up with every possible color that can be represented on a computer. (Actually, the biggest difference is that Play-Doh can't display highresolution computer graphics, but that's another discussion.) Figure 12.1 : Electron guns in a color monitor converging to create a unique color. Knowing that monitors form unique colors by using varying intensities of the colors red, green, and blue, you might be thinking that a good solution to the color problem would be to provide an intensity value for each of these primary colors. This is exactly how computers model color. Computers represent different colors by combining the numeric intensities of the primary colors red, green, and blue. This color system is known as RGB (Red Green Blue) and is fully supported by Java. Although RGB is the most popular computer color system in use, there are others. Another popular color system is HSB, which stands for Hue Saturation Brightness. In this system, colors are defined by varying degrees of hue, saturation, and brightness. The HSB color system is also supported by Java.

Color Images in Java


file:///C:/temp/java%20guru/ch12.htm (2 of 24) [2/3/2003 8:19:25 AM]

Chapter 12 -- Image Filters and Color Models

Bitmapped computer images are composed of pixels that describe the colors at each location of an image. Each pixel in an image has a unique color that is usually described using the RGB color system. Java provides support for working with 32-bit images, which means that each pixel in an image is described as using 32 bits. The red, green, and blue components of a pixel's color are stored in these 32 bits, along with an alpha component. The alpha component of a pixel refers to the transparency or opaqueness of the pixel. A 32-bit Java image pixel is therefore composed of red, green, blue, and alpha components. By default, these four components are packed into a 32-bit pixel value, as shown in Figure 12.2. Notice that each component is described by 8 bits, yielding possible values between 0 and 255 for each. These components are packed into the 32-bit pixel value from high-order bits to low-order bits in the following order: alpha, red, green, and blue. It is possible for the pixel components to be packed differently, but this is the default pixel storage method used in Java. Figure 12.2 : The four components of a pixel in a 32-bit Java image. A color component value of 0 means the component is absent, and a value of 255 means it is maxed out. If all three color components are 0, the resulting pixel color is black. Likewise, if all three components are 255, the color is white. If the red component is 255 and the others are 0, the resulting color is pure red. The alpha component describes the transparency of a pixel, independent of the color components. An alpha value of 0 means a pixel is completely transparent (invisible), and an alpha value of 255 means a pixel is completely opaque. Values between 0 and 255 enable the background color to show through a pixel in varying degrees. The color components of a Java image are encapsulated in a simple class called Color. The Color class is a member of the AWT package and represents the three primary color components: red, green, and blue. This class is useful because it provides a clean abstraction for representing color, along with useful methods for extracting and modifying the primary components. The Color class also contains predefined constant members representing many popular colors.

Color Models
In Java, pixel colors are managed through color models. Java color models provide an important abstraction that enables Java to work with images of different formats in a similar fashion. More specifically, a color model is a Java object that provides methods for translating from pixel values to the corresponding red, green, and blue color components of an image. At first, this may seem like a trivial chore, knowing that pixel color components are packed neatly into a 32-bit value. However, there are different types of color models reflecting different methods of maintaining pixel colors. The two types of color models supported by Java are direct color models and index color models.

file:///C:/temp/java%20guru/ch12.htm (3 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

Direct Color Models


Direct color models are based on the earlier description of pixels, where each pixel contains specific color and alpha components. Direct color models provide methods for translating these types of pixels into their corresponding color and alpha components. Typically, direct color models extract the appropriate components from the 32-bit pixel value using bit masks.

Index Color Models


Index color models work differently from direct color models. In fact, index color models work with pixels containing completely different information than you've learned thus far. Pixels in an image using an index color model don't contain the alpha and RGB components like the pixels used in a direct color model. An index color model pixel contains an index into an array of fixed colors (see Figure 12.3). This array of colors is called a color map. Figure 12.3 : An index color model pixel and its associated color map. An example of an image that uses an index color model is a 256-color image. 256-color images use 8 bits to describe each pixel, which doesn't leave much room for RGB components. Rather than try to cram these components into 8 bits, 256-color pixels store an 8-bit index in a color map. The color map has 256 color entries that each contain RGB and alpha values describing a particular color. Index color models provide methods for resolving pixels containing color map indices into alpha, red, green, and blue components. Index color models handle looking up the index of a pixel in the color map and extracting the appropriate components from the color entry. Index color models provide an additional feature not available in direct color models: support for a transparent pixel color. Using an index color model, you can specify a color in the color map as the transparent color for the image. When the image is drawn, pixels having the transparent color are left out. The background shows through these pixels, effectively resulting in the pixels being completely transparent. The transparency feature is very useful when working with images that have an irregular shape. All images are stored as rectangles and typically are drawn in a rectangular region. By using a transparent color to define the region around the irregular shape, the image can be drawn on a background without erasing a rectangular area of the background. Figure 12.4 shows the difference between images drawn with and without a transparent color. Figure 12.4 : The effects of using a transparency color to draw an image.

file:///C:/temp/java%20guru/ch12.htm (4 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

The Color Model Classes


Java provides standard classes for working with color models. At the top of the hierarchy is the ColorModel class, which defines the core functionality required of all color models. Two other classes are derived from ColorModel, representing the two types of color models supported by Java: DirectColorModel and IndexColorModel.

ColorModel
The ColorModel class is an abstract class containing the basic support required to translate pixel values into alpha and color components. ColorModel contains the following methods:
q q q q q q q q

ColorModel(int bits) static ColorModel getRGBdefault() int getPixelSize() abstract int getRed(int pixel) abstract int getGreen(int pixel) abstract int getBlue(int pixel) abstract int getAlpha(int pixel) int getRGB(int pixel)

The ColorModel method is the only creation method defined for the ColorModel class. It takes a single integer parameter that specifies the pixel width of the color model in bits. The getRGBdefault method is a class method that returns a ColorModel object based on the default RGB pixel component storage, as described earlier in this chapter (0xAARRGGBB). The getPixelSize method returns the current pixel width of the color model. For example, the default color model would return 32 as the number of bits used to represent each pixel. The following piece of code shows how you can check this yourself: System.out.println(ColorModel.getRGBdefault().getPixelSize()); The four methods that get each different pixel component are all defined as abstract. This means that a derived color model class must provide the specific implementation for these methods. The reason for this goes back to the issue of supporting different types of color models. Getting the color components of a pixel is completely dependent on how each pixel represents colors in an image, which is determined by the color model. For direct color models, you can extract the components by simply masking out the correct 8-bit values. For an index color model, however, you have to use each pixel's value as an index into a color map and then retrieve the components from there. In keeping with the object-oriented structure of Java, the ColorModel class provides the method descriptions but leaves the specific
file:///C:/temp/java%20guru/ch12.htm (5 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

implementations to more specific color model classes. The last method in the ColorModel class is getRGB, which returns the color of a pixel using the default color model. You can use this method to get a pixel value in the default RGB color model format.

DirectColorModel
The DirectColorModel class is derived from ColorModel and provides specific support for direct color models. If you recall, pixels in a direct color model directly contain the alpha and color components in each pixel value. DirectColorModel provides the following methods:
q q

q q q q q q q q q

DirectColorModel(int bits, int rmask, int gmask, int bmask) DirectColorModel(int bits, int rmask, int gmask, int bmask, int amask) final int getRedMask() final int getGreenMask() final int getBlueMask() final int getAlphaMask() final int getRed(int pixel) final int getGreen(int pixel) final int getBlue(int pixel) final int getAlpha(int pixel) final int getRGB(int pixel)

The first two methods are the creation methods for DirectColorModel. The first creation method takes the pixel width of the color model, along with the masks used to specify how each color component is packed into the pixel bits. You may have noticed that there is no mask parameter for the alpha component. Using this creation method, the alpha component is forced to a value of 255, or fully opaque. This is useful if you don't want any alpha information to be represented. The second creation method is just like the first, with the exception that it lets you specify an alpha mask. Note A mask is used to extract specific bits out of an integer value. The bits are extracted by bitwise ANDing the mask with the value. Masks themselves are integers and are typically specified in hexadecimal. For example, to mask out the high-order word of a 32bit value, you use the mask 0xFFFF0000.

If you're a little shaky with masks, think about the masks for the default pixel component packing. Remember, the components are packed from high-order to low-order in the following order: alpha, red,
file:///C:/temp/java%20guru/ch12.htm (6 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

green, and blue. Each component is eight bits, so the mask for each component extracts the appropriate byte out of the 32-bit pixel value. Table 12.1 shows the default RGB pixel component masks. Table 12.1. The default pixel component masks. Pixel Component Alpha Red Green Blue Mask 0xFF000000 0x00FF0000 0x0000FF00 0x000000FF

With DirectColorModel, there are four methods that simply return the pixel component masks: getRedMask, getGreenMask, getBlueMask, and getAlphaMask. Notice that these methods are defined as final, meaning that they cannot be overridden in a derived class. The reason for this is that the underlying native Java graphics code is dependent on this specific implementation of these methods. You'll notice that this is a common theme in the color model classes. The four abstract methods defined in ColorModel are implemented in DirectColorModel: getRed, getGreen, getBlue, and getAlpha. These methods return the appropriate component values of a pixel in the range 0 to 255. Like the mask methods, these methods are also defined as final, so that no derived classes can override their behavior. The getRGB method returns the color of a pixel in the default color model format. This method is no different than the one implemented in ColorModel.

IndexColorModel
The IndexColorModel class is also derived from ColorModel and provides support for index color models. Recall from the earlier discussion of color models that pixels in an index color model contain indices into a fixed array of colors known as a color map. The IndexColorModel class provides the following methods:
q q

IndexColorModel(int IndexColorModel(int int trans) IndexColorModel(int byte a[]) IndexColorModel(int boolean hasalpha) IndexColorModel(int

bits, int size, byte r[], byte g[], byte b[]) bits, int size, byte r[], byte g[], byte b[], bits, int size, byte r[], byte g[], byte b[], bits, int size, byte cmap[], int start, bits, int size, byte cmap[], int start,

file:///C:/temp/java%20guru/ch12.htm (7 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

q q q q q q q q q q q

boolean hasalpha, int trans) final int getMapSize() final int getTransparentPixel() final void getReds(byte r[]) final void getGreens(byte g[]) final void getBlues(byte b[]) final void getAlphas(byte a[]) final int getRed(int pixel) final int getGreen(int pixel) final int getBlue(int pixel) final int getAlpha(int pixel) final int getRGB(int pixel)

The first five methods are the creation methods for the IndexColorModel class. They look kind of messy with all those parameters, but they really aren't that bad. First, all the creation methods take as their first parameter the width of each pixel in bits. They all also take as their second parameter the size of the color map array to be used by the color model. In addition to the pixel width and color map array size, the first three creation methods also take three byte arrays containing the red, green, and blue components of each entry in the color map. These arrays should all be the same length, which should match the color map size passed in as the second parameter. The second creation method enables you to specify the array index of the transparent color. The third creation method enables you to specify an array of alpha values to go along with the color component arrays. Rather than using parallel arrays of individual component values, the last two creation methods take a single array of "packed" pixel component values-the color components are stored sequentially in a single array instead of in separate parallel arrays. The start parameter specifies the index to begin including colors from the array. The hasalpha parameter specifies whether the colors in the array include alpha information. The only difference between these two methods is that the second version enables you to specify the array index for the transparent color. The getMapSize method returns the size of the color map used by the color model. The getTransparentPixel method returns the array index of the transparent pixel color, if it is defined. Otherwise, getTransparentPixel returns -1. There are four methods for getting the color values from the color map: getReds, getGreens, getBlues, and getAlphas. Each method takes an array of bytes as the only parameter and fills it with the color map values for the appropriate pixel component. These methods are final, so you can't override them in a derived class.

file:///C:/temp/java%20guru/ch12.htm (8 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

IndexColorModel provides implementations for the four abstract methods defined in ColorModel: getRed, getGreen, getBlue, and getAlpha. These methods return the appropriate component values of a pixel in the range 0-255. These methods are also defined as final, so derived classes aren't allowed to modify their behavior. The getRGB method returns the color of a pixel in the default RGB color model format. Because the default color model is a direct color model, this method effectively converts an index color to a direct color.

Working with Color Models


Okay, so you know all about color models and the Java classes that bring them to life. Now what? Most of the time they act behind the scenes. It is fairly rare that you will need to create or manipulate a color model directly. Color models are used extensively in the internal implementations of the various image processing classes, however. What does this mean to you, the ever-practical Java programmer? It means that you now know a great deal about the internal workings of color in the Java graphics system. Without fully understanding color models and how they work, you would no doubt run into difficulties when trying to work with the advanced graphics and image processing classes provided by Java. Take a look at the Gradient sample program in Figure 12.5. The Gradient sample program uses an IndexColorModel object with 32 varying shades of green. It creates an image based on this color model and sets the image pixels to a horizontal gradient pattern. The complete source code for this program is shown in Listing 12.1. It is also included on the CD-ROM in the file Gradient.java. Figure 12.5 : The Gradient sample program.

Listing 12.1. The Gradient sample program. // Gradient Class // Gradient.java // Imports import java.applet.Applet; import java.awt.*; import java.awt.image.*; public class Gradient extends Applet {

file:///C:/temp/java%20guru/ch12.htm (9 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

final final final Image

int colors = 32; int width = 200; int height = 200; img;

public void init() { // Create the color map byte[] rbmap = new byte[colors]; byte[] gmap = new byte[colors]; for (int i = 0; i < colors; i++) gmap[i] = (byte)((i * 255) / (colors - 1)); // Create the color model int bits = (int)Math.ceil(Math.log(colors) / Math.log(2)); IndexColorModel model = new IndexColorModel(bits, colors, rbmap, gmap, rbmap); // Create the pixels int pixels[] = new int[width * height]; int index = 0; for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) pixels[index++] = (x * colors) / width; // Create the image img = createImage(new MemoryImageSource(width, height, model, pixels, 0, width)); } public void paint(Graphics g) { g.drawImage(img, 0, 0, this); } }

The Gradient program starts off by declaring a few final member variables for determining the number of colors used in the index color model, along with the size of the image. It then creates a color map by building two arrays for the color components. The first array, rbmap, is used for both the red and blue color components. The second array, gmap, is used for the green color
file:///C:/temp/java%20guru/ch12.htm (10 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

component. The rbmap array is left in its initial state. Remember, Java automatically initializes all member variables to zero. The gmap array is initialized to equally spaced values between 0 and 255, dependent on the number of colors in the color map (as specified by colors). The arrays are set up this way because you want the color map to contain shades of green. This is accomplished by specifying nonzero values for the green component of the color map and leaving the red and blue components set to 0. With the color map created, you are ready to create the index color model. The creation method for IndexColorModel requires you to specify the pixel width of the color model. The pixel width is simply how many bits are required for a pixel to store an index in the color map. Calculating the pixel width is a matter of determining how many bits (b) are necessary to index an array of (n) colors. I won't go into the details of where the equation comes from, but the following equation yields the desired result: b = log(n) / log(2) To understand the implications of this equation, think about the number of colors used in the program. The final member variable colors is set to 32, meaning that the color model contains 32 color entries. Each pixel needs to be able to distinguish between (or index) these 32 different entries. Using the previous equation, you'll find that 5 bits per pixel are enough to index an array of 32 colors. Likewise, 8 bits per pixel are required to index an array of 256 colors. You may notice that the equation used in Gradient is a little different; it calls the ceil method as well as the log method, like this: int bits = (int)Math.ceil(Math.log(colors) / Math.log(2)); The call to ceil is there to make sure there are enough bits in case the number of colors is set to a value that is not a power of 2. For example, what if you change colors to 45 instead of 32? The result of the original equation would be 5.49, but the .49 would be lost in the cast to a byte. The resulting 5 would not be enough bits per pixel to keep up with 45 colors. The trick is always to use the smallest whole number greater than or equal to the floating-point result before casting. This is exactly what the ceil method does. With the IndexColorModel creation method, you pass in the newly calculated pixel width, the number of colors, and the three color component arrays. The zero-filled rbmap array is used for both the red and blue component arrays. Now the color model is created and ready to go, but there is still some work to be done. A bitmap image based on an index color model is composed of pixels that reference the colors in the color map. To create an image, you simply build up an array of pixels with the length equal to the width times the height of the image. The pixel array for the new image is created and each pixel initialized using nested for loops. Each pixel is initialized using the following equation:

file:///C:/temp/java%20guru/ch12.htm (11 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

pixels[index++] = (x * colors) / width; This equation results in an equal distribution of colors (gradient) horizontally across the image. The image is actually created with a call to the createImage method. This method takes a MemoryImageSource object as its only parameter. The MemoryImageSource class uses an array of pixel values to build an image in memory. The creation method for MemoryImageSource takes the width, height, color model, pixel array, pixel array offset, and scan line width for the image. It's simply a matter of plugging in the information you've already created. At this point, you have an image made up of gradient pixels that contain indices into a color model with 32 shades of green. Now the fun part-drawing the image! A simple call to drawImage in the paint method is all it takes.

Image Filters
A thriving area of software research and development is image processing. Most popular paint programs contain image processing features, such as sharpening or softening an image. Typically, image processing developers have to build complex libraries of routines for manipulating images. Java provides a simple, yet powerful framework for manipulating images. In Java, image processing objects are called image filters, and they serve as a way to abstract the filtering of an image without worrying about the details associated with the source or destination of the image data. A Java image filter can be thought of quite literally as a filter into which all the data for an image must enter and exit on its way from a source to a destination. Take a look at Figure 12.6 to see how image data passes through an image filter. Figure 12.6 : Image data passing through an image filter. While passing through a filter, the individual pixels of an image can be altered in any way as determined by that filter. By design, image filters are structured to be self-contained components. The image filter model supported by Java is based on three logical components: an image producer, an image filter, and an image consumer. The image producer makes the raw pixel data for an image available. The image filter in turn filters this data. The resulting filtered image data is then passed on to the image consumer where it has usually been requested. Figure 12.7 shows how these three components interact with each other. Figure 12.7 : The relationship between an image producer, an image filter, and an image consumer. Breaking down the process of filtering images into these three components provides a very powerful object-oriented solution to a complex problem. Different types of image producers can be derived that
file:///C:/temp/java%20guru/ch12.htm (12 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

are able to retrieve image data from a variety of sources. Likewise, filters can ignore the complexities associated with different image sources and focus on the details of manipulating the individual pixels of an image.

The Image Filter Classes


Java support for image filters is scattered across several classes and interfaces. You don't necessarily have to understand all these classes in detail to work with image filters, but it is important that you understand what functionality they provide and where they fit into the scheme of things. Here are the Java classes and interfaces that provide support for image filtering:
q q q q q q q q

ImageProducer FilteredImageSource MemoryImageSource ImageConsumer PixelGrabber ImageFilter RGBImageFilter CropImageFilter

ImageProducer
The ImageProducer interface provides the method descriptions necessary to extract image pixel data from Image objects. Classes implementing the ImageProducer interface provide implementations for these methods specific to the image sources they represent. For example, the MemoryImageSource class implements the ImageProducer interface and produces image pixels from an array of pixel values in memory.

FilteredImageSource
The FilteredImageSource class implements the ImageProducer interface and produces filtered image data. The filtered image data produced is based on the image and the filter object passed in FilteredImageSource's creation method. FilteredImageSource provides a very easy way to apply image filters to Image objects.

MemoryImageSource
The MemoryImageSource class implements the ImageProducer interface and produces image data based on an array of pixels in memory. This is very useful in cases where you need to build an Image object directly from data in memory. You used the MemoryImageSource class earlier in this
file:///C:/temp/java%20guru/ch12.htm (13 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

chapter in the Gradient sample program.

ImageConsumer
The ImageConsumer interface provides method prototypes necessary for an object to retrieve image data from an image producer. Instantiated classes implementing the ImageConsumer interface are attached to an image producer object when they are interested in its image data. The image producer object delivers the image data by calling methods defined by the ImageConsumer interface.

PixelGrabber
The PixelGrabber class implements the ImageConsumer interface and provides a way of retrieving a subset of the pixels in an image. A PixelGrabber object can be created based on either an Image object or an object implementing the ImageProducer interface. The creation method for PixelGrabber enables you to specify a rectangular section of the image data to be grabbed. This image data is then delivered by the image producer to the PixelGrabber object.

ImageFilter
The ImageFilter class provides the basic functionality of an image filter that operates on image data being delivered from an image producer to an image consumer. ImageFilter objects are specifically designed to be used in conjunction with FilteredImageSource objects. The FilterImage class is implemented as a null filter, which means that it passes image data through unmodified. Nevertheless, it implements the overhead for processing the data in an image. The only thing missing is the actual modification of the pixel data, which is left up to derived filter classes. This is actually a very nice design, because it enables you to create new image filters by deriving from ImageFilter and overriding a few methods.

RGBImageFilter
The ImageFilter class operates on an image using the color model defined by the image producer. The RGBImageFilter class, on the other hand, derives from ImageFilter and implements an image filter specific to the default RGB color model. RGBImageFilter provides the overhead necessary to process image data in a single method that converts pixels one at a time in the default RGB color model. This processing takes place in the default RGB color model regardless of the color model used by the image producer. Like ImageFilter, RGBImageFilter is meant to be used in conjunction with the FilteredImageSource image producer. The seemingly strange thing about RGBImageFilter is that it is an abstract class, so you can't instantiate objects from it. It is abstract because of a single abstract method, filterRGB. The
file:///C:/temp/java%20guru/ch12.htm (14 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

filterRGB method is used to convert a single input pixel to a single output pixel in the default RGB color model. filterRGB is the workhorse method that handles filtering the image data; each pixel in the image is sent through this method for processing. To create your own RGB image filters, all you must do is derive from RGBImageFilter and implement the filterRGB method. This is the technique you use later in this chapter to implement your own image filters. The RGBImageFilter class contains a member variable that is very important in determining how it processes image data: canFilterIndexColorModel. The canFilterIndexColorModel member variable is a boolean that specifies whether the filterRGB method can be used to filter the color map entries of an image using an index color model. If this member variable is false, each pixel in the image is processed.

CropImageFilter
The CropImageFilter class is derived from ImageFilter and provides a means of extracting a rectangular region within an image. Like ImageFilter, the CropImageFilter class is designed to be used with the FilteredImageSource image producer. You may be a little confused by CropImageFilter, because the PixelGrabber class mentioned earlier sounds very similar. It is important to understand the differences between these two classes because they perform very different functions. First, remember that PixelGrabber implements the ImageConsumer interface, so it functions as an image consumer. CropImageFilter, on the other hand, is an image filter. This means that PixelGrabber is used as a destination for image data, where CropImageFilter is applied to image data in transit. You use PixelGrabber to extract a region of an image to store in an array of pixels (the destination). You use CropImageFilter to extract a region of an image that is sent along to its destination (usually another Image object).

Writing Your Own Image Filters


Although the standard Java image filter classes are powerful as a framework, they aren't that exciting to work with by themselves. Image filters don't really get interesting until you start implementing your own. Fortunately, the Java classes make it painfully simple to write your own image filters. All the image filters you develop in this chapter are derived from RGBImageFilter, which enables you to filter images through a single method, filterRGB. It really is as easy as deriving your class from RGBImageFilter and implementing the filterRGB method.

A Color Image Filter


Probably the simplest image filter imaginable is one that filters out the individual color components (red,

file:///C:/temp/java%20guru/ch12.htm (15 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

green, and blue) of an image. The ColorFilter class does exactly that. Listing 12.2 contains the source code for the ColorFilter class. It is located on the CD-ROM in the file ColorFilter.java.

Listing 12.2. The ColorFilter class. // Color Filter Class // ColorFilter.java // Imports import java.awt.image.*; class ColorFilter extends RGBImageFilter { boolean red, green, blue; public ColorFilter(boolean r, boolean g, boolean b) { red = r; green = g; blue = b; canFilterIndexColorModel = true; } public int filterRGB(int x, int y, int rgb) { // Filter the colors int r = red ? 0 : ((rgb >> 16) & 0xff); int g = green ? 0 : ((rgb >> 8) & 0xff); int b = blue ? 0 : ((rgb >> 0) & 0xff); // Return the result return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0); } }

The ColorFilter class is derived from RGBImageFilter and contains three boolean member variables that determine which colors are to be filtered out of the image. These member variables are set by the parameters passed into the creation method. The member variable inherited from RGBImageFilter, canFilterIndexColorModel, is set to true to indicate that the color map entries can be filtered using filterRGB if the incoming image is using an index color model.

file:///C:/temp/java%20guru/ch12.htm (16 of 24) [2/3/2003 8:19:26 AM]

Chapter 12 -- Image Filters and Color Models

Beyond the creation method, ColorFilter implements only one method, filterRGB, which is the abstract method inherited from RGBImageFilter. filterRGB takes three parameters: the x and y position of the pixel within the image, and the 32-bit (integer) color value. The only parameter you are concerned with is the color value (rgb). Recalling that the default RGB color model places the red, green, and blue components in the lower 24 bits of the 32-bit color value, it is easy to extract each one by shifting out of the rgb parameter. These individual components are stored in the local variables r, g, and b. Notice, however, that each color component is shifted only if it is not being filtered. For filtered colors, the color component is set to zero. The new color components are shifted back into a 32-bit color value and returned from filterRGB. Notice that care is taken to ensure that the alpha component of the color value is not altered. The 0xff000000 mask takes care of this, because the alpha component resides in the upper byte of the color value. Congratulations, you've written your first image filter! You have two more to go before you plug them all into a test program.

An Alpha Image Filter


It isn't always apparent to programmers how the alpha value stored in the color value for each pixel impacts an image. Remember, the alpha component specifies the transparency or opaqueness of a pixel. By altering the alpha values for an entire image, you can make it appear to fade in and out. This works bec