0% found this document useful (1 vote)
2K views889 pages

Hacking Java Professional Resource Kit

Hacking Java Professional Resource Kit
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 (1 vote)
2K views889 pages

Hacking Java Professional Resource Kit

Hacking Java Professional Resource Kit
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/ 889

CONTENTS

JAVA Expert Solutions


by Mark Wutka, et. al.

C O N T E N T S

Introduction Chapter 1
G G G G

What Is Java?

Java as a Web Programming Language Java as an Applications Programming Language New Features on the Horizon Java as an Embedded Systems Language

Chapter 2
G

Embedding Applets in Web Pages

G G

No Java? No Problem H Displaying an Image in Place of an Applet Passing Parameters to Applets Improving Applet Startup Time

Chapter 3
G

Applet Security Restrictions

Applet Security

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (1 of 22) [8/14/02 10:52:30 PM]

CONTENTS
G G G G

File Access Restrictions Network Restrictions Other Security Restrictions Getting Around Security Restrictions H Using Digital Signatures for Increased Access H Creating a Customized Security Manager

Chapter 4
G G

Displaying Images

G G

Images in Java Displaying Simple Images H Shrinking and Stretching Images Creating Your Own Images Displaying Other Image Formats H The Microsoft Windows Bitmap (BMP) File Format Manipulating Images H Performing Image-Processing Algorithms Filtering Image Colors H Filtering Based on Pixel Position Downloading Images

Chapter 5
G G G G G G G

Animating Images

Animation An Animation Driver Animating Image Sequences Animating Portions of an Image Animating with a Filter Cycling the Color Palette Animating Graphics H Redrawing the Entire Screen H Doing Animation with XOR Eliminating Flicker H Double-Buffering

Chapter 6

Communicating with a Web Server

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (2 of 22) [8/14/02 10:52:30 PM]

CONTENTS
G

Java and Web Servers Getting Files Using the URL C

G G G G G G

lass

Getting Files Using Sockets Performing a Query with GET Posting Data with the URL Class Posting Data Using Sockets Supporting the Cookie Protocol

Chapter 7
G G G G G G G

Creating Smarter Forms

Smarter Forms Creating Forms with the AWT Checking for Errors on the Client Side Adding Context-Sensitive Help Creating Dynamic Forms Loading Another URL from an Applet Creating Image Maps with Hot Spots

Chapter 8 Applet
G G

Reading and Writing Files from an

G G

Applets and Files Using the JFS Filesystem for Applets H Printing Files Using JFS H Accessing Other Web Servers from JFS Saving Files Using HTTP Post Storing and Retrieving Files with FTP H Sending FTP Commands H Establishing an FTP Session H Sending Simple FTP Commands H Establishing a Data Connection

Chapter 9 Creating Reusable Graphics Components


file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (3 of 22) [8/14/02 10:52:30 PM]

CONTENTS

G G

Reusable Components The Command Pattern H Invoking Commands from a Menu Creating a Reusable Image Button H Setting the Size of a Canvas H Handling Input Events H Painting the Canvas H Watching for Image Updates Creating a CommandImageButton Using the Observer Interface H The Model-View-Controller Paradigm H Observables and the Model-View-Controller Paradigm Using Observables for Other Classes
H

Chapter 10
G G G G

Inter-Applet Communication

Locating Other Applets Exchanging Data Using Piped Streams Creating Multi-Client Pipes Sharing Information with Singleton Objects

Chapter 11
G G G

Sending E-Mail from an Applet

Sending E-Mail Sending E-Mail Using the SMTP Protocol Accessing Your Mailbox with the POP3 Protocol

Chapter 12
G G G G G

Protecting Applet Code

Protecting Your Code from Unauthorized Use Embedding Copyrights in Your Code Verifying the Origin of the Applet Hiding Information in Your Applet Obfuscating a Working Program H Make All Your Function and Variable Names Meaningless H Perform Occasional Useless Computations or Loops

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (4 of 22) [8/14/02 10:52:30 PM]

CONTENTS
H H H H

Hide Small Numbers in Strings Create Large Methods Spread Methods Out Among Subclasses Using a Commercial Obfuscator

Chapter 13
G G G G

Running Applets as Applications

Differences Between Applets and Applications Allowing an Applet to Run as an Application The Applet's Runtime Environment Creating an Applet Context

Chapter 14 Files
G G G G G G

Creating Your Own Class Archive

Class Archive Files Creating Your Own Archive File with Info-ZIP Viewing the Contents of a Zip Archive Adding Classes Directly to the Browser's Library Creating Class Archives with Other Zip Archivers Creating Cabinet Files for Internet Explorer

Chapter 15
G

Accessing Databases with JDBC

Organizing Your Data for a Relational Database H Using SQL H Combining Data from Multiple Tables Using Joins Designing Client/Server Database Applications H Client/Server System Tiers H Handling Transactions H Dealing with Cursors H Replication H How Does JDBC Work? H JDBC Security Model H Accessing ODBC Databases with the JDBC-ODBC Bridge H JDBC Classes-Overview

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (5 of 22) [8/14/02 10:52:31 PM]

CONTENTS

G G

G G G G

Anatomy of a JDBC Application H JDBC API Examples The Connection Class Handling SQL Statements H Creating and Using Direct SQL Statements H Creating and Using Compiles SQL Statements (PreparedStatement) H Calling Stored Procedures (CallableStatement) Retrieving Results in JDBC Handling Exceptions in JDBC-SQLException Class Handling Exceptions in JDBC-SQLWarnings Class Handling Date and Time H java.sql.Date H java.sql.Time H java.sql.Timestamp Handling SQL Types H java.sql.Types JDBC in Perspective
H

Chapter 16 Creating 3-Tier Distributed Applications with RMI


G G G

G G G

Creating 3-Tier Applications RMI Features Creating an RMI Server H Defining a Remote Interface H Creating the Server Implementation H Creating the Stub Class Creating an RMI Client Creating Peer-to-Peer RMI Applications Garbage Collection, Remote Objects, and Peer-to-Peer

Chapter 17
G G G

Creating CORBA Clients

Defining IDL Interfaces Compiling IDL Interfaces for Java Clients Writing a Client Applet

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (6 of 22) [8/14/02 10:52:31 PM]

CONTENTS
G G G G G

Handling Exceptions CGI Programs, Java.net.*, and Java.io.* May Not Be the Best Choices Using the Dynamic Invocation Interface and the Interface Repository Using Filters Some Points About Distributed System Architecture

Chapter 18
G G

Using CORBA IDL with Java

G G

What Is CORBA? Sun's IDL to Java Mapping H IDL Modules H IDL Constants H IDL Data Types H Enumerated Types Structures H Unions H Sequences and Arrays H Exceptions H Interfaces H Attributes Using CORBA in Applets H Choosing Between CORBA and RMI Creating CORBA Clients with JavaIDL Creating CORBA Clients with VisiBroker

Chapter 19
G

Creating CORBA Servers

G G

Creating a Basic CORBA Server H Using Classes Defined by IDL Structs H VisiBroker Skeletons H Using the VisiBroker TIE Interface H JavaIDL Skeletons Creating Callbacks in CORBA Wrapping CORBA Around an Existing Object H Mapping to and from CORBA-Defined Types H Creating Remote Method Wrappers H Implementing Wrapped Callbacks

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (7 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Chapter 20
G

Increasing Graphics Performance

Double-Buffering to Speed Up Drawing H Detecting the Best Drawing Method at Runtime Creating an Autodetecting update Performing Selective Updates Redrawing Changed Areas
H

Method

G G

Chapter 21
G G

Download Strategies

Huffman Coding and Lempel-Ziv Compression Delayed Downloading H Delayed Instantiation H Downloading in the Background Providing Local Libraries H Installing Local Libraries for Hotjava and Appletviewer H Installing Local Libraries for Netscape H Installing Local Libraries for Internet Explorer Downloading Classes in Zipped Format H Zip Downloading in Netscape Navigator Version 3 H A Zipfile Class Loader Packaging Classes in Jars and Cabinets

Chapter 22
G G

Faster Image Downloads

Reducing Image Size Image Strips H Using the Graphics.clipRect Method H Creating Another Graphics Context Storing Only Parts on an Image Strip

Chapter 23
G G

Creating Web Services in Java

Using Java Objects Instead of CGI The Servlet API

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (8 of 22) [8/14/02 10:52:31 PM]

CONTENTS
G G G

The Web Server as a Computing Server Adding Web Access to Your Java Applications Migrating off the Web Server in the Future

Chapter 24
G G

Writing Web Services for Jeeves

What Is Jeeves? The Jeeves HTTP Server H Architectural Overview H Installing and Running the Jeeves HTTP Server H Administering the Jeeves Web Server H HTTP Server Security Extending Jeeves' Functionality with Servlets H Employing the Servlet API H Using the Jeeves Development Toolkit Building a Database Servlet H Getting the Information from the Users H Connecting Your Servlet to a JDBC Database H Inserting Data in the Database H Searching the Database Building a Simple Autonomous Agent System with Jeeves H Using Object Serialization to Transport Agents Across the Internet H Building the Remote Agency H Creating a Generic Agent Interface H Implementing a Database Search Agent H Building the Home Agency H Launching the Agent H Debriefing the Agent

Chapter 25
G

Writing Web Services for Jigsaw

Architectural Overview H Handling the HTTP Protocol with the Daemon Module H Managing the Server Information Space with the Resource Module H Maintaining Server State via Object Persistence H Pre and Post Request Processing with Resource Filters Jigsaw Interface

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (9 of 22) [8/14/02 10:52:31 PM]

CONTENTS

G G G G G

The HTTPResource Class H The FilteredResource Class H The DirectoryResource Class H The FileResource Class Installation and Setup of the Jigsaw HTTP Server Adding Content to the Jigsaw Server Extending the Server with Java Writing Resource Filters in Java Handling Forms and the POST Method in Java
H

Chapter 26 Signatures
G G G G

Securing Applets with Digital

G G

What Are Digital Signatures? Allowing More Access for Signed Applets Using a Third Party for Applet Signatures Potential Security Problems with Digital Signatures H Using Phony Signatures H Receiving Old Software H Mistaken Trust in Signed Applets H Running a Phony Web Browser Obtaining a Digital Signature Certificate Other Uses for Digital Signatures

Chapter 27
G G

Encrypting Data

Choosing the Right Kind of Encryption Guarding Against Malicious Attacks H Resisting a Playback Attack H Don't Store Keys in Your Applets H Using Public Key Encryption to Exchange Session Keys H Using Secure HTTP to Thwart Impersonations Getting Encryption Software H Getting SSLava, the Secure Sockets Library H Getting the Cryptix Library H Getting the Acme Crypto Package

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (10 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Chapter 28
G G G G

Accessing Remote Systems Securely

G G G

Getting a Secure Web Server Preventing Impersonations Accessing Remote Data Passing Keys to Clients H Don't Reuse Symmetric Keys H Using Public Key Encryption to Get a Private Key H Passing a Private Key as an Applet Parameter Implementing a Single-Client Secure Server Implementing a Multiclient Secure Server Creating Other Secure Remote Access Programs

Chapter 29
G G

Creating a Java Shopping Cart

Designing a Basic Shopping Cart Creating a Shopping Cart User Interface H Creating a Catalog Applet H Creating the Shopping Cart Applet

Chapter 30
G G G

Performing Secure Transactions

G G

Letting Customers Digitally Sign Orders Using Encryption in All Network Communications Creating Java Services for Netscape Servers H Creating a Server-Side "Hello World" H Installing a New Server-Side Java Applet H Handling Forms from Server-Side Applets H Sending Files as a Response H Returning Multi-Part Responses H Maintaining Information Between Applet Invocations Making Server-Side Applets Work on Different Web Servers Performing Secure Transactions

Chapter 31

Java Electronic Commerce

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (11 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Framework (JECF)
G

G G

G G

G G

The Difficulties of Electronic Commerce H Theft of Information H Fraudulent Programs H Proprietary Solutions H Static Solutions H Platform-Dependence Creating Online Services with the JECF Storing Information in the Wallet Database H Keeping Data Safe H Performing Transactions Implementing a Shopping Cart Applet with the JECF Offering Services with Cassettes H Creating Other Wallet Services H Ensuring Cassette Security H Dealing with System Failures JECF Availability Getting More Information About the JECF

Chapter 32
G G G G G

Encapsulating Legacy Systems

G G G G

Focusing on Function, not Form Providing Access to New Systems Using CORBA to Open Up a Closed System Encapsulating a TCP/IP System Encapsulating with Native Method Calls H Wrapping Java Around a Native Interface H Writing Native Methods in C Encapsulating by Emulating a User Getting Assistance from the Legacy System Presenting a Different Interface Combining Multiple Systems H Handling Deletions Originating in the Legacy System H Using a Two-Phase Commit Protocol H Implementing a Two-Phase Commit Some Real-World Examples

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (12 of 22) [8/14/02 10:52:31 PM]

CONTENTS
H H H H

An Example Legacy System Creating a New Application for the Existing Terminal Base Creating a New Interface for an Existing Application Clearing a Path for Migration off the Legacy System

Chapter 33
G

Web-Enabling Legacy Systems

Using Encapsulations to Access Legacy Data H Aiming for Session-Less Transactions H Storing Session Information in the Web Page H Using HTTP Cookies to Preserve Session Information H Choosing a Good Session Identifier H Clearing Out Old Sessions Accessing Legacy Data from Servlets

Chapter 34
G G G G G G

Interfacing with CICS Systems

A Thumbnail Sketch of CICS The CICS External Call Interface The Java-CICS Gateway API Creating Multiple-Call LUWs Creating Web Interfaces to CICS Providing a CORBA Interface to CICS H Creating a CORBA-CICS Gateway H Creating CORBA Interfaces to CICS Programs

Chapter 35 HotJava
G

Adding Additional Protocols to

Writing a Protocol Handler H Step One: Decide Upon a Package Name H Step Two: Create the Directories H Step Three: Set Your CLASSPATH H Step Four: Implement the Protocol H Step Five: Create the Handler Class H Step Six: Compile the Sources

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (13 of 22) [8/14/02 10:52:31 PM]

CONTENTS
G

Using Protocol Handlers with HotJava H Step One: Update the properties File H Step Two: Run HotJava Using Protocol Handlers with Your Own Applications H The main() Method: Starting FetchWhois H The FetchWhois Constructor: Where the Work Gets Done H The whoisUSHFactory Class: Registering the Protocol Handler H Running FetchWhois More on URLStreamHandlerFactory

Chapter 36
G

Adding New MIME Types to HotJava

Writing Content Handlers H Step One: Decide upon a Package Name H Step Two: Create the Directories H Step Three: Set Your CLASSPATH H Step Four: Write the Content Handler H Step Five: Compile the Source Using Content Handlers with HotJava H Step One: Disable Special MIME Handling H Step Two: Update the PROPERTIES File H Step Three: Run HotJava Using Content Handlers with Your Own Applications H Start FetchFuddify H The ContentHandlerFactory Implementation H Running the Application

Chapter 37
G G

Creating Multi-User Programs in Java

G G

Designing Multi-User Applications Adding Socket-Based Access to Multi-User Applications H Creating a Socket-Based Server H Sending Messages over Sockets Other Issues When Dealing with Sockets Adding RMI Access to Multi-User Applications

Chapter 38

Creating On-Demand Multimedia

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (14 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Services
G G

G G

G G

Java's Suitability for On-Demand Applications Using the On-Demand Audio Applet H Logging In H Playing Audio Clips Adding Sound to Applets On-Demand Music Applet Code Review H Applet Architecture H Initialization and Registration H Song Selection H Playing the Songs Java Shortcomings New Features

Chapter 39 Implementing a Multimedia Encyclopedia


G

G G G

G G

Java's Suitability for Multimedia Applications H Java Is Portable H Java Is Compact H Java Can Handle Streaming Data H Java Is Based on the Client/Server Model H Java Supports PDAs Easily Using the Multimedia Encyclopedia Adding Images and Sound to Applets The On-Line Multimedia Encyclopedia In-Depth H Applet Architecture H Index Window H Topic Window Shortcomings New Features

Chapter 40 Implementing Java Interfaces for NonTraditional Devices


file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (15 of 22) [8/14/02 10:52:31 PM]

CONTENTS

G G G

Characteristics of Non-Traditional Devices The New Computing Model Designing Applications to Support Non-Traditional Devices H Separating the User Interface from the Application H Avoiding Large, Monolithic Applications H Sticking to Standard Libraries H Avoiding Long, Complex Transactions Designing User Interfaces for Small Devices H Creating Obvious, Self-Documenting Interfaces H Avoiding Extraneous Pictures or Information H Keeping Everything Readable H Supporting Multiple Sources of Input Creating Reusable Components for Small Devices H Using the CardLayout Layout Manager as a Stack H Creating a Keyboard/Keypad Input Filter H Creating a Pop-Up Keypad for Pen and Touch-Screen Users

Credits

Java Expert Solutions


Copyright 1997 by Que Corporation. All rights reserved. Printed in the United States of America. No part of this book may be used or reproduced in any form or by any means, or stored in a database or retrieval system, without prior written permission of the publisher except in the case of brief quotations embodied in critical articles and reviews. Making copies of any part of this book for any purpose other than your own personal use is a violation of United States copyright laws. For information, address Que Corporation, 201 W. 103rd Street, Indianapolis, IN 46290. You may reach Que's direct sales line by calling 1-800-428-5331. ISBN: 0-7897-0935-x

HTML conversion by : M/s. LeafWriters (India) Pvt. Ltd. Website : http://leaf.stpn.soft.net e-mail : [email protected]
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (16 of 22) [8/14/02 10:52:31 PM]

CONTENTS

President Publishing Manager Editorial Services Director Director of Marketing Acquisitions Editor Production Editor

Roland Elgey Jim Minatel Elizabeth Keaffaber Lynn E. Zingraf Stephanie Gould Sean Dixon

Publisher Title Manager Managing Editor

Joseph B. Wikert Steven M. Schafer Sandy Doell

Acquisitions Manager Cheryl D. Willoughby Product Directors Editors Mark Cierzniak, Jon Steever Kelly Brooks, Judith Goode, Sidney Jones, Kelly Oliver Christy M. Miller Jim Hoffman, Russ Jacobs, Ernie Sanders, Eugene W. Sotirescu, Steve Tallon Jane K. Brownlow Andrea Duvall Barbara Kordesh

Product Marketing Strategic Marketing Manager

Kim Margolius Barry Pruett

Assistant Product Marketing Manager Technical Editors

Technical Support Specialist Software Relations Coordinator Interior Book Designer Production Team

Nadeem Muhammed Patty Brooks Barbara Kordesh

Acquisitions Coordinator Editorial Assistant Cover Designer

Kevin Cliburn, Tammy Graham, Jason Hand, Heather Howell, Dan Julian, Bob LaRoche, Casey Price, Erich Richter, Laura Robbins, Marvin Van Tiem, Paul Wilson Chris Barrick

Indexer

Acknowledgments
Writing a book like this is quite an experience, and one of the most important parts of that experience has been the people I have worked with and the people who helped me get through it. I would especially like to thank my wife Ceal, who somehow managed to keep me close to my normal level of sanity (which is minimal at best). Thanks also go to Chris, Amy, Samantha, and Kaitlynn, who had to endure endless hours of clicking keys and to my Mom, who taught me, by example, how to work hard and to strive constantly to improve myself.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (17 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Joe Weber, author of Special Edition Using Java, provided some excellent suggestions about the outline for this book, as well as some good advice about being an author. In addition, Cliff McCartney provided me with technical feedback on various aspects of the book, especially in the area of legacy system migration-a subject near and dear to both of our hearts. This book was not written by a single person. I am extremely grateful for the work of the other authors. The technical expertise that each of them brought to this book has truly made it a book of expert solutions. I would also like to thank the staff at Que, who have been great to work with-Stephanie Gould, Mark Cierzniak, Ben Milstead, Jon Steever, Sean Dixon, and the many people behind the scenes. Finally, I would like to thank Geddy, Alex, and Neil for EXCELLENT music to code by. You guys have gotten me through hundreds of thousands of lines of code.

About the Authors


David W. Baker is a systems developer for BBN Planet, a business unit of BBN Corporation. He specializes in software development and system integration for Internet solutions. He also works as a freelance game writer, authoring materials for various roleplaying games. Until recently, he worked for Second Nature Interactive, a software development company, where he served as a Senior Game Writer. David's home page is available at http://www.netspace.org/users/dwb/. David P. Boswell lives in Brigham City, Utah, with his wife Carma and four children. He works as a programmer/analyst for Thiokol Corporation and as an independent Internet consultant. David can be reached via http://www.daves.net or [email protected]. Ken Cartwright is a software engineer with Science Applications International Corp. He has received a bachelor's degree from the University of Oklahoma and a master's degree in information systems and software engineering from George Mason University. He has been developing complex software systems for several years and has spent the last three years concentrating on object-oriented distributed system development using CORBA and object-oriented databases. Some of his most recent projects utilized the combination of Java and CORBA to support client/server systems. Ken can be reached at [email protected]. David Edgar Liebke ([email protected]) works at The Scripps Research Institute in La Jolla, CA. A member of the Research Computing group, he develops Java-based information systems and deals with issues of network security. David graduated from UC San Diego with a B.S. in cognitive science, where he studied artificial intelligence, neural networking, and emergent computation. He currently lives in Irvine, CA with his spousal-type unit, Rochelle, who is completing her doctorate in cognitive science. David hopes to one day rid the world of tyranny, or at least proprietary software "standards." Tom Lockwood has 12 years' experience as a technical writer and marketing specialist with several computer graphic companies. He is currently employed at Cinebase Software where he championed the development of its Web site. Tom is also a freelance writer, a softball coach, and, most proudly, an Aries. He can be reached at [email protected] or via his personal Web site at http://www.cris.com/~tlockwoo.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (18 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Stephen N. Matsuba is cofounder of Alt.Reality Technologies Corporation, a company developing virtual reality and multimedia applications, and SHOC Interactive, a company developing multimedia games and educational applications. In his other life, he is completing his Ph.D. in computational linguistics and English literature at York University, Canada. His research interests include Shakespeare, literary theory, computational linguistics, artificial intelligence and cognitive science, computer applications in humanities research and education, VR, and multimedia design. He also coauthored Special Edition Using VRML (Que, 1996) with Bernie Roehl. George Menyhert is currently the Director of the Harmony Product and a member of the technical staff at Cinebase Software where he concentrates on multimedia application engineering. He is also a freelance Java developer. George has a degree in engineering from the University of Cincinnati. He can be reached via his Web page at http://w3.one.net/~menyhert or through one of his various e-mail accounts: [email protected], [email protected], or [email protected]. Krishna Sankar has been a computer professional since 1980. He has worked on strategic business systems for companies like HP, AT&T, Pratt & Whitney, Testek, Ford, TRW, Caterpillar, Qantas Airlines, and Air Canada, as well as for the U.S. Air Force and U.S. Navy. He still believes in information re-engineering and development of competitive business systems and is excited about the possibilities of intranet applets and servlets in those areas. He has two master's degrees, one in production engineering and the other in computer science. He is now pursuing his MBA. He is a Microsoft Product Specialist as well as a Lotus Certified Professional. He is the founder of U.S. Systems & Services, a Silicon Valley intranet systems and Java technology company. Nowadays, you can meet him in the corridors of venture capitalists and banks promoting products "for those whose life is not Internet but want to leverage the net to enjoy it." Mark Wutka is a senior systems architect who refuses to give up his programming hat. For the past two years he has worked as the chief architect on a large, object-oriented distributed system providing automation for the flight operations division of a major airline. Over the past eight years, he has designed and implemented numerous systems in C, C++, Smalltalk, and Java for that same airline. He is currently the Vice President of Research and Development for Pioneer Technologies, a consulting firm specializing in distributed systems and legacy system migration. He can be reached via e-mail at [email protected]. He also claims responsibility for the random bits of humor found at http://www.webcom.com/wutka.

We'd Like to Hear from You!


As part of our continuing effort to produce books of the highest possible quality, Que would like to hear your comments. To stay competitive, we really want you, as a computer book reader and user, to let us know what you like or dislike most about this book or other Que products. You can mail comments, ideas, or suggestions for improving future editions to the address below, or send us a fax at (317) 581-4663. Our staff and authors are available for questions and comments through our Internet site, at http://www.mcp.com, and Macmillan Computer Publishing also has a forum on CompuServe (type GO QUEBOOKS at any prompt). In addition to exploring our forum, please feel free to contact me personally to discuss your opinions of this book: I'm [email protected] on the Internet.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (19 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Thanks in advance-your comments will help us to continue publishing the best books available on new computer technologies in today's market. Mark Cierzniak Product Development Specialist Que Corporation 201 W. 103rd Street Indianapolis, Indiana 46290 USA

Introduction
by Mark Wutka Java is one of the most significant software products to hit the scene in a long time. Unlike Netscape, whose impact was big and immediate, Java's full impact won't be realized for a long time. Java is more than just a programming language. It requires a different mindset when developing applications. Sure, you can use Java to spruce up your Web pages-it works quite well for that. This book will even give you some tips on ways to do it. But that's not the main purpose of Java. If you only use it for pretty Web pages, you are missing a lot. Hacking Java: The Java Professional's Resource Kit not only gives you lots of useful Java classes and programming tips, it relates the "vision" of Java. You get an overview from the 30,000-foot level, as well as from down in the trenches, to borrow some management clichs. Both of these views are important. When you're digging a trench, you still need to look up to see where you're headed. Java will have a significant impact on the future of software development, and even the future of technology. If you don't already understand why this is so, you need this book. One of the important things to realize about Java is that it is young and still evolving. There are many features yet to come, and many more uses of Java to be discovered. This book will help guide you in making design decisions that may be affected by some of these new applications of Java.

Who Should Read This Book


This book addresses Java on several different levels. Some people will be interested in hard-core programming techniques. There are plenty of those here. You may know the language, but you want to use it to solve different problems that aren't addressed in any of the Java programming books. You may be a software designer, looking for new design techniques. This book has plenty of good object-oriented design strategies that apply not only to Java but to other O-O languages as well. If you're a system architect creating your company's information infrastructure, there's plenty in here for you, too. This book discusses many architectural issues and shows you situations where you can use Java that you've probably never

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (20 of 22) [8/14/02 10:52:31 PM]

CONTENTS

thought of. This is especially true when it comes to the overall philosophy of Java and its multitude of uses. This book is not an introduction to programming in Java. There is no discussion of what classes and methods are. Special Edition Using Java by Que will give you a good introduction to Java. This book is meant to complement Special Edition Using Java, giving you the kind of advice that you don't get from a book on programming.

What This Book Is About


Hacking Java: The Java Professional's Resource Kit is more than just a how-to book. It's also a what-to book. A how-to book assumes that you already know exactly what you want to do, and it gives you step-by-step instructions showing you how to do it. This book gives you ideas about what to do with Java, and then tells you how to implement them.

How This Book Is Organized


This book starts out by addressing some of the burning issues of creating applets. It provides suggestions for improving the performance of your applets, as well as ways to get around some of the restrictions imposed on applets. Section II discusses some of the aspects of Java applications, including a way to run an applet as an application. This section also discusses the JDBC database interface and the remote method invocation facility. Section III discusses some of the CORBA products available for Java, how to use them, and what you can do with them. If you are unfamiliar with CORBA, you also get a brief introduction to CORBA. Section IV shows you how you can speed up your applets, both in the download phase and once the applet is running. Section V introduces some of the Java Web servers that are now available. You can use Java to implement new Web services that you previously could only do with CGI. In addition, since the servers are written in Java, you can run them anywhere you can run Java. Section VI delves into some of the deeper aspects of security. It introduces digital signatures and data encryption, and discusses some of the issues involved in protecting your communications. Section VII shows you how you can use Java to do business over the Web. It discusses some of the aspects of electronic commerce and shows you how to perform secure transactions. Section VIII deals with "legacy" systems and how you can use Java to connect these older systems to the Internet. There is a large amount of system design philosophy in this section, much of which is applicable no matter what language you are using. Section IX shows you how to expand capabilities of the HotJava browser, which is written entirely in Java. You will learn how you can add new networking protocols and how you can make HotJava understand new data formats. Section X introduces some of the multimedia capabilities of Java. This is one field where Java will be expanding greatly. This section suggests possible uses of Java in the multimedia realm.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (21 of 22) [8/14/02 10:52:31 PM]

CONTENTS

Section XI discusses some of the issues involved with running Java on small devices like cellular phones and personal digital assistants (PDA). As these devices become more readily available, your systems will have to cooperate with them. This section gives you guidelines that let you start planning for these devices now. On the CD, three chapters will show you how Java integrates with the Virtual Reality Markup Language (VRML). You will see how you can add whole new dimensions to your Java programs, literally.

How to Use This Book


You can use this book either as a cookbook or as a learning tool. If you have a specific problem that you need to solve, you can consult the book for the solution, as you might use a reference book. This book is also of immense use as a learning tool. It covers issues faced every day by professional programmers. These issues are rarely covered in a typical programming book. While many of the example programs solve complex problems, you will find most of them to be relatively straightforward. If you are still fairly new to programming, you will learn a lot just by studying the example programs. They are fairly wellcommented and are quite readable.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/index.htm (22 of 22) [8/14/02 10:52:31 PM]

Chapter 1 -- What Is Java?

Chapter 1 What Is Java?


by Mark Wutka

CONTENTS
G G G G

Java as a Web Programming Language Java as an Applications Programming Language New Features on the Horizon Java as an Embedded Systems Language

Because this book assumes that you already know how to program in Java, you already have a good idea of what features are in the Java language. This book will notteach you how to program in Java; it takes the next step byshowing you what you can do with Java and how to do it.

Java as a Web Programming Language


Much of the initial appeal of Java comes from the fact that it can be embedded in Web pages. It allows you to go beyond the static nature of Web pages by making your pages come alive.Using Java's Abstract Windowing Toolkit (AWT), you can create interactive forms that go beyond the simple act of filling in abunch of fields and clicking the Submit button. You can perform error checking on forms, provide context-sensitive help, even give the user suggestions or examples. Some of these things you can do without Java, but not as quickly. Java allows you to improve the interaction between the client and the server. The HTTP protocol, the native language of the World Wide Web, is very specific and somewhat restrictive as far as the interaction between the client and server. Whenever a client needs to send data to the server, it must open up a network connection, send a set of headers and the request data, and then sit and wait for a response. The server has very few options for sending data to a client. It must wait for the client to send it information, and the only option it has for sending back multiple responses is the "multi-part" message, in which the server sends part of a response, and then later sends more of the response. Given the static nature of Web pages, this has always been considered acceptable. Also, because the network connection
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch1.htm (1 of 7) [8/14/02 10:52:35 PM]

Chapter 1 -- What Is Java?

is closed after a server has sent a response back to the client, there is no notion of a session within HTTP. Clients and servers have had to come up with their own interesting ways of maintaining session information between requests. The Netscape Cookie protocol is one such method. The server puts Netscape cookies in a Web page when it sends information back to the browser. The pieces of information are tagged as being cookies, which the browser watches for and saves for later use. The next time the browser accesses that server, it sends the cookies back to the server. This allows the server to save information at the client-side and then receive the information at a later time. Cookies are discussed more fully in Chapter 6 "Communicating with a Web Server." When you are writing serious applications, however, you need the interaction between client and server to be much more flexible. A client should be able to send information to a server at any time, and the server should be able to send data back to the client at any time. Java's networking support allows you to do this by creating a socket connection between the client and the server. Look at an example of a real-world application and see how Java can improve yourapplications drastically. Suppose you work for an airline and you are creating a program to display the current position of any of the company's aircraft. You would like this program to run on any Web browser within the company. Your server will be gathering aircraft position data and sending the information out to the browsers. You obviously want this to be a graphical program-you don't just want to list coordinates. You want the president of the company to be able to see immediately that flight 1313 is halfway between Cleveland and Detroit, without having to estimate its distance based on the latitude and longitude shown on some chart. If you were to do this application using the traditional Web server and HTML forms, your server would have to generate entire images and send them to the client. Anytime a plane's position changed, you'd have to generate new images for each client that was watching that plane. Even if a plane's position changes once a minute, if you watch ten planes, you'll be receiving an average of one image every six seconds. That's an incredible burden to place on your server. Now, suppose you were to create the same application in Java. The Java applet would download a blank map from the server and then open up a socket connection to the server. Anytime the Java applet wanted to watch a new plane, or stop watching a plane, it would send a message to the server. The server would track what clients were watching what planes. One of the keys here is that the connection between the client and the server stays up. This allows the server to keep track of clients based on their sockets. Now, suppose the server receives a position update for a plane. It looks through its tables and finds every client that was watching that plane and sends the new position down to that client. It does not have to perform any image generation. The amount of data sent to the client is probably 100-1,000 times smaller than the image that would be sent under the previous architecture.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch1.htm (2 of 7) [8/14/02 10:52:35 PM]

Chapter 1 -- What Is Java?

The Java applet is responsible for creating the new image of the aircraft. Although this may take a little longer to generate on the client than on the server, the server is able to handle many times more clients than it otherwise would, because it doesn't have to do as much work for each client. If you step back and take a look at this application, you'll see that the applet is really just implementing the user interface for the flight tracking system. The bulk of the work in gathering the flight data and analyzing it is done by the server. The interaction between the server and client is a clearly defined set of actions. The client starts watching a plane, the client stops watching a plane, the server sends a flight position to the client. That'sa pretty simply protocol! The client does what it does best-it interacts with the user. The server does what it does best-it gathers and analyzes information. Keep this in mind as you design and develop new applications. Don't heap all the work on the applet, just let it do what it does best-interact with the user. Realizing that applets are going to need a reasonable way to communicate with the actual applications, Sun added two important subsystems to Java. Remote Method Invocation (RMI) allows a Java object to invoke methods in another Java applet somewhere else on the network. You don't have to come up with your own way of transmitting data between the applet and the application on the server. The applet can simply invoke methods on the server using RMI. RMI is a nice feature, and is very easy to use since it blends into your applet and application almost seamlessly. There is another way to invoke methods remotely, however.It's called the Common Object Request Broker Architecture, or CORBA. There are many differences between RMI and CORBA. One of the biggest is that CORBA is a multi-language protocol. You can use CORBA in an applet to invoke methods in a C++ application running on your server. You will be able to choose between RMI and CORBA for your applets. They will both be supported as part of the core of Java. You can expect both mechanisms to be present in a Java-compliant Web browser, or any Java-compliant environment.

Java as an Applications Programming Language


It's unfortunate that Java has gotten the reputation of being solely a Web programming language. It is a full-fledged application programming language. It contains all the features you need to write some pretty hefty programs-and they will all run on any system that runs Java! Java is young and is still experiencing growing pains. One of these pains is the fact that although Java runs on multiple platforms, it doesn't quite run exactly the same on every platform. Most of the time, these differences are in the implementation of the AWT, causing the problems to appear more often in applets than applications (unless you're creating a graphical application, of course). Because most people see only the graphical programs, the platform-to-platform variations in Java look worse than they actually

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch1.htm (3 of 7) [8/14/02 10:52:35 PM]

Chapter 1 -- What Is Java?

are. The Jigsaw WWW server, discussed in Chapter 25, "Writing Web Services for Jigsaw," is written entirely in Java-over 30,000 lines! It runs very well across all Java-enabled platforms. The big difference between a Java application and a Java applet is the lack of security restrictions. Java applications are given free reign over the system (although they can't get around the operating system's security). A Java application is free to open a socket connection to any host it wants, open any file, and create its own custom class loaders. If you have been banging your head against a wall because you couldn't do these things in an applet, you might be tempted to turn your applets into applications (in other words, make them stand-alone) so you can have all these features. That is, of course, your choice. But you should seriously consider keeping the user interface and the application separate. For some quick hack program that isn't very significant, it probably won't matter. However, if you're writing a big commercial application, it does matter. There are many advantages to being able to run applets in a browser; one of the biggest advantages is that the browser performs automatic software distribution for you. You don't have to install the applet on a system ahead of time in order for someone to use it. If you start writing everything as a stand-alone application, you fall back into the old trap of trying to maintain a program on a large number of machines. Java's database API, called JDBC, is a boon for application programming. You now have a standard interface for accessing a relational database. JDBC frees you from being tied to a specific database API, meaning you not only can create cross-platform applications, you can also create cross-database applications. Java is a great language for handling little ten-minute hack programs, as well. You have immediate access to an excellent set of libraries that handle many tedious functions that you won't find in the standard library set of C or C++. You can buy these libraries for other languages, of course, but why bother if you get them free with Java? You may soon find that you are writing Java programs when you previously wrote C programs or Perl scripts.

New Features on the Horizon


While the Java language itself will remain fairly static for now, with only a few additions, the available APIs for the language will be growing at a rapid pace. The original APIs that came with Java were enough to generate great interest in Java. Now, as more and more companies use Java for serious work, they are discovering areas that Java hasn't addressed up to now. Multimedia has become a hot topic on the World Wide Web. You can now use your Web browser to view animated news clips, listen to samples of new record albums, and even make video telephone calls halfway around the world. Java's support of multimedia has been minimal, so far, but that is being addressed. You will soon see a flurry of new multimedia APIs for Java. These APIs will provide improved audio service, full-motion video, 2-D and 3-D graphics, and even telephony. The APIs will really help level the playing field when it comes to selecting a platform for running your programs. You will be able to count on having these services available on whatever platform you choose, whether it be
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch1.htm (4 of 7) [8/14/02 10:52:35 PM]

Chapter 1 -- What Is Java?

Windows, UNIX, or Mac. In the area of audio, you will be able to synchronize your audio a little better, allowing you to create animation that is in sync with the audio. You should also be able to support varying sample rates. Most important of all, you will be able to find out when an audio clip finishes playing. This is one of the most glaring omissions in the current API. You can't even create a simple music jukebox under the current API, because you never know when to start the next piece of music. The video API will allow you to display video clips in different formats, and even synchronize them with the audio. Rather than sticking to a single video format, the video API allows you to plug in different kinds of video handlers. You could support MPEG and QuickTime, for instance. The 2-D API provides a rich set of drawing routines that is badly needed. The Graphics class in the AWT provides only the most basic drawing features. You will be able to perform complex pattern fills with the new API, for example. There will also be an API for doing sprite animation. Sprites are essentially graphical objects that move around the screen. You can do something similar right now, but you have to write all the animation and redrawing code yourself. The sprite API will take care of that for you, and will do it in a much more efficient manner. This should result in a lot of neat new games for Java, many approaching the capabilities of some home gaming systems. You will be able to create interesting new effects with the 3-D API. There will be support for simple 3-D objects, as well as animated 3-D objects, and even some of the features you now find in VRML systems. Again, since these are part of the native Java environment, they should be very efficient. The telephony API addresses the growing integration between the telephone and the computer. Essentially, the telephony API is a mechanism for placing and receiving phone calls. You may need special hardware to interface with the actual phone equipment, but eventually you'll be able to redirect phone calls over your home network to whatever device you happen to be near, whether it is your WebTV, your PDA, or your old desktop computer. Network management is another important topic, especially at large companies. Right now, many operating systems and most network devices support the SNMP network management protocol. There are a number of tools available for configuring and monitoring SNMP-enabled devices. The JavaManagement API will allow you to create new programs for monitoring network devices. You will be able to monitor SNMP devices, or plug in your own protocols to manage devices using other network management protocols. The advantage here is that you will be able to take advantage of Java's ease of use, and create network management applications that will run on any Java-enabled platform. One of the most exciting new Java APIs to come along is the Beans API. Beans is an API for creating and using software components. One of the dreams in software development has always been that software components could be used like electronic components. When you buy electronic components, they have a standard interface. Many times, the same kind of component is offered by a number of
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch1.htm (5 of 7) [8/14/02 10:52:35 PM]

Chapter 1 -- What Is Java?

vendors, giving you freedom of choice. You can create an electronic board by looking at the specifications for the components and designing the board. Once you finally assemble the components, you have an excellent chance of things working as you had planned. In the software arena, this is rarely the case. Beans doesn't necessarily solve this problem, but it brings you one step closer. The philosophy behind the Beans API is that you have a nice development tool for creating new applications. In a way, it's like your workbench. You buy software components from different companies, and each component is a bean. You add the beans to your development tool, and the tool uses the Beans API to find out what interfaces each new bean supports. In addition, the Beans API defines mechanisms for customizing a bean. For example, you might buy a nifty new pushbutton bean and add it to your graphical development environment. Your graphical environment presents you with a visual toolkit of all the beans you have. You could select the pushbutton and drag it onto your new application. Next, you could pop up a configuration menu that allowed you to customize the pushbutton. The Beans API uses a new Java feature called reflection to discover the parts of the bean that can be customized. As an alternative, you could supply your own customizer for a bean. If you think you can do a lot with Java now, imagine what you'll be able to do when these new features become available.

Java as an Embedded Systems Language


The ability to run programs inside small devices like cellular phones and PDAs (Personal Digital Assistants) is one of Java's best kept secrets. Sun, and other hardware manufacturers, realized that Java's virtual machine could easily be implemented in silicon and placed in a wide variety of devices. Already, companies have created cellular phones and PDAs that run Java. In fact, Java itself came from a project that created a small handheld device. One day, you may have a refrigerator that runs Java. What would that mean to you? Probably nothing, unless it makes you play Tetris in order to use the ice maker. The toaster has always been the appliance that everyone wants to connect to the Internet. Yes, someone has demonstrated a Java-enabled toaster. In fact, Sun has considered changing its trademark phrase "The Network Is The Computer" over to "The Toaster Is The Computer." Okay, not really. Still, if things continue in the direction they are going, you will have more and more pieces of equipment in your house that run Java. This can be upsetting to an application designer who is accustomed to thinking of the desktop as the sole realm of applications. This is where the notion of separating the user interface from the application really becomes important. You can't cram some behemoth of an application into a cell phone. You shouldn't even try. Take the flight tracking system as an example.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch1.htm (6 of 7) [8/14/02 10:52:35 PM]

Chapter 1 -- What Is Java?

Suppose the airline president handed you his Java-enabled organizer and said, "I want to see flights on this thing." Fortunately for you, you separated the application from the user interface, so all you have to do is create a special user interface for the organizer.If you had written the flight tracking system as a big stand-alone application, you would have already torn your hair out in big clumps trying to figure out how you were going to fit all that code into an itty-bitty living space. You may, in the future, have a completely different computing model at home than you do now. Right now, you probably have a single computer, a printer, a monitor, and a modem. Some of you even have your own ethernet networks now. In the future, you may have an application server on which all your favorite programs reside-your e-mail system, your word processor, and yes, your favorite games. This server may not even have a keyboard or a monitor, just a connection to your home network. On your desktop, you might have a Java-enabled monitor and keyboard that are also hooked to the network. In the living room, your Java-enabled television is also on the network. With the coming of digital TV and highspeed networking to the home, there may no longer be a difference between a computer monitor and a television. When you want to read your e-mail, you can access it from the computer monitor, your TV, or even your wireless digital assistant, all using your home network to access the e-mail application running on your home server. You may not even have a server at home-you might subscribe to an e-mail service over the network and access a server somewhere in Tuscaloosa. The point is that there are more and more ways for you to interact with computer systems, and in the future, one single way will no longer be sufficient. As you design your applications, keep the image of a cell phone or a personal digital assistant hovering like a dark cloud over you, whispering menacingly, "Will your application run on me?"

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch1.htm (7 of 7) [8/14/02 10:52:35 PM]

Chapter 6 -- Communicating with a Web Server

Chapter 6 Communicating with a Web Server


by Mark Wutka

CONTENTS
G G G G G G G

Java and Web Servers Getting Files Using the URL Class Getting Files Using Sockets Performing a Query with GET Posting Data with the URL Class Posting Data Using Sockets Supporting the Cookie Protocol

Java and Web Servers


The Web server is the home of a Java applet. Not only does the applet live there, it must rely on the Web server for any information or files it wants to download. As some of the applet security restrictions are lifted and replaced with a better security mechanism, this will not be the case. For now, however, the Web server is the only place an applet can count on being able to access. Applications, on the other hand, may access data in countless ways, but still find Web servers to be a wonderful source of information.

Getting Files Using the URL Class


The URL class allows you to access any URL on the World Wide Web, as long as your browser or Java environment supports the protocol for the URL. You can safely assume that your environment supports the HTTP protocol. Other protocols such as FTP may not be available, however. Keep in mind that if you open an URL and read it yourself, there is no way to take the data you read and display it as a fully-formatted HTML page unless you do it yourself. If you want to open an URL and have your browser display it, you should use the showDocument method in the Applet class, which is discussed in the section, "Loading Another URL from an Applet," in Chapter 7 "Creating Smarter Forms." The URL class is really just a class for naming resources on the World Wide Web, much like the File class represents file names, but not the contents of a file. In order to get the contents of an URL, you need to open an input stream to the URL. You can do this one of two ways. The simplest way is to call the openStream method in the URL class: URL someURL = new URL("http://abcdef.com/mydocument.html"); InputStream inStream = someURL.openStream();

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (1 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

This input stream will provide you with the contents of the file named by the URL. This method is most useful when you are only concerned with the file contents and not with any of the HTTP headers associated with the file. To get these, you need to use the URLConnection class. The URLConnection class represents a network connection to a WWW resource. When you open an input stream on an URL, it really opens an URLConnection and then calls the getInputStream in the URLConnection object. The following code fragment is the equivalent of the previous example: URL someURL = new URL("http://abcdef.com/mydocument.html"); URLConnection urlConn = someURL.openConnection(); InputStream inStream = urlConn.getInputStream(); The advantage of the URLConnection class is that it gives you much finer control over an URL connection. For example, you can retrieve the headers associated with the file. The two header fields that you will probably be most interested in are the content type and content length. You can fetch these with the getHeaderField and getHeaderFieldInt methods: String contentType = urlConn.getHeaderField("content-type"); int contentLength = urlConn.getHeaderFieldInt( "content-length", -1); // returns -1 if length isn't specified These header fields are so popular, in fact, that they have their own special methods that do the equivalent of the above code-getContentType and getContentLength: String contentType = urlConn.getContentType(); int contentLength = urlConn.getContentLength(); Listing 6.1 shows a sample applet that uses an URL class to read its own .class file.

Listing 6.1 Source Code for FetchURL.java import import import import java.applet.*; java.awt.*; java.net.*; java.io.*;

// This applet demonstrates the use of the URL and URLConnection // class to read a file from a Web server. The applet reads its // own .class file, because you can always be sure it exists. public class FetchURL extends Applet { byte[] appletCode; // Where to store the contents of the .class file public void init() { try {

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (2 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

// Open a URL to this applet's .class file. You can locate it by // using the getCodeBase method and the applet's class name. URL url = new URL(getCodeBase(), getClass().getName()+".class"); // Open a URLConnection to the URL URLConnection urlConn = url.openConnection(); // See if you can find out the length of the file. This allows you to // create a buffer exactly as large as you need. int length = urlConn.getContentLength(); // Because you can't be sure of the size of the .class file, use a // ByteArrayOutputStream as a temporary container. Once you are finished // reading, you can convert it to a byte array. ByteArrayOutputStream tempBuffer; // If you don't know the length of the .class file, use the default size if (length < 0) { tempBuffer = new ByteArrayOutputStream(); } else { tempBuffer = new ByteArrayOutputStream(length); } // Get an input stream to this URL InputStream instream = urlConn.getInputStream(); // Read the contents of the URL and copy it to the temporary buffer int ch; while ((ch = instream.read()) >= 0) { tempBuffer.write(ch); } // Convert the temp buffer to a byte array (you don't do anything with // the array in this applet other than take its size). appletCode = tempBuffer.toByteArray(); } catch (Exception e) { e.printStackTrace(); } } public void paint(Graphics g) { g.setColor(Color.black); if (appletCode == null) { g.drawString("I was unable to read my .class file", 10, 30); } else { g.drawString("This applet's .class file is "+ appletCode.length+" bytes long.", 10, 30);
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (3 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

} } }

Figure 6.1 shows the output from the FetchURL applet. Figure 6.1 : An applet can perform an HTTP GET using the URL class. The FetchURL applet is a typical example of an applet that opens an URL and reads data from it. For example purposes, the applet reads its own .class file. There is no advantage to reading a .class file, but for example purposes it is quite handy, because you know for sure that the .class file must be there. If the .class file wasn't there, the applet wouldn't run in the first place. The applet first opens the URL, and then gets an input stream for the URL. It tries to get the content length, which indicates how much data there is to retrieve. This value isn't always available, however, so the applet uses ByteArrayOutputStream as a temporary storage mechanism. Tip Vectors and byte array output streams are extremely handy storage containers when you don't know the size of the data you are storing. You should use a vector whenever you need to store an unknown number of objects. The byte array output stream is a handy alternative to the vector when you are storing bytes.

Once the applet has read its .class file, it simply displays a message telling how many bytes it read.

Getting Files Using Sockets


If, for some reason, you decide that you want to bypass the URL and URLConnection classes and speak HTTP directly over a socket, you are probably a glutton for punishment or just a genuine bit-head. Actually, the HTTP protocol is very simple, so it isn't that big a deal to open up a socket and fetch information. All you need to do is open the socket, send a GET message, and start reading. When you read data from an HTTP server directly over a socket, you'll get all the header information first. Each line in the header is terminated by a carriage return and then a line feed (in Java, "\r\n"). The end of the header section is marked by a blank line. After that comes the data, in whatever form the server sends it. The "content-type" header tells you what type of data to expect. If you're just reading a text file, it should be "text/plain." Listing 6.2 shows an applet that uses a socket connection to fetch a file from a Web server. Like the example in Listing 6.1, this applet fetches its own .class file. import java.applet.*; import java.awt.*;

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (4 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

Listing 6.2 Source Code for FetchSockURL.java import java.net.*; import java.io.*; // This applet shows you how to open up a socket to an HTTP server // and read a file. The applet reads its own .class file, because // you can always be sure it exists. public class FetchSockURL extends Applet { byte[] appletCode; // Where to store the contents of the .class file public void init() { try { // If the port number returned for the code base is -1, use the // default http port of 80. int port = getCodeBase().getPort(); if (port < 0) port = 80; // Open up a socket to the Web server where this applet came from Socket sock = new Socket(getCodeBase().getHost(),port); // Get input and output streams for the socket connection DataInputStream inStream = new DataInputStream( sock.getInputStream()); DataOutputStream outStream = new DataOutputStream( sock.getOutputStream()); // // // // // // Send the GET request to the server The request is of the form: GET filename HTTP/1.0 In this case, the filename will be the applet's filename as returned by the getCodeBase method. Notice that you send two \r\n's The first one terminates the request line, the second indicates the end of the request header. outStream.writeBytes("GET "+ getCodeBase().getFile()+getClass().getName()+ ".class HTTP/1.0\r\n\r\n"); // Just to show you how it's done, look through the headers for // the content length. First, assume it's -1. int length = -1; String currLine; // Read the next line from the header, quit if you hit EOF

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (5 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

while ((currLine = inStream.readLine()) != null) { // if the length of the line is 0, you just hit the end of the header if (currLine.length() == 0) break; // See if it's the content-length header if (currLine.toLowerCase().startsWith( "content-length:")) { // "content-length:" is 15 characters long, so parse the length starting at // offset 15 (the 16th character). Catch any exceptions when parsing // this number - it's not so important that you have to quit. try { length = Integer.valueOf( currLine.substring(15)). intValue(); } catch (Exception ignoreMe) { } } } // Because you can't be sure of the size of the .class file, use a // ByteArrayOutputStream as a temporary container. Once you are finished // reading, you can convert it to a byte array. ByteArrayOutputStream tempBuffer; // If you don't know the length of the .class file, use the default size if (length < 0) { tempBuffer = new ByteArrayOutputStream(); } else { tempBuffer = new ByteArrayOutputStream(length); } // Read the contents of the URL and copy it to the temporary buffer int ch; while ((ch = inStream.read()) >= 0) { tempBuffer.write(ch); } // Convert the temp buffer to a byte array (you don't do anything with // the array in this applet other than take its size. appletCode = tempBuffer.toByteArray(); } catch (Exception e) { e.printStackTrace(); } } public void paint(Graphics g) { g.setColor(Color.black);

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (6 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

if (appletCode == null) { g.drawString("I was unable to read my .class file", 10, 30); } else { g.drawString("This applet's .class file is "+ appletCode.length+" bytes long.", 10, 30); } } }

Like the FetchURL applet, the FetchSockURL applet reads its own .class file from the Web server. FetchSockURL doesn't use the built-in URL class, however. Instead, it creates a socket connection to the Web server. Once this connection is made, the applet sends a GET request to the Web server to retrieve the .class file. The GET request usually looks something like this: GET /classes/FetchSockURL.class HTTP/1.0 This line is followed by a blank line, indicating the end of the HTTP headers. You can send your own headers immediately after the GET request if you like. Just make sure they appear before the blank line. The FetchSockURL applet actually writes out the blank line in the same statement where it writes out the GET request, so you'll need to remove the \r\n from the end of the writeBytes statement if you add your own headers. If you do that, don't forget to write out a blank line after your headers. Once the GET request has been sent to the server, the applet begins reading lines from the socket connection. The server will send a number of header lines, terminated by a blank line. This will be followed by the actual content of the page. The FetchSockURL applet scans through the headers looking for the content length header field, which usually looks like this: Content-length: 1234 Like the FetchURL applet, the FetchSockURL applet can handle situations where the content length is unknown. It uses the same technique of writing the data to a byte array output stream as it reads it. You can tell when you have reached the end of the content because you'll hit the end of file on the socket (the read method will return -1).

Performing a Query with GET


Many Web servers allow you to get information based on a query. In other words, you don't just ask for a file, you ask for a file and pass some query parameters. This determines the information you get back. This is most often used in Web search engines. Most Web servers support an interface called CGI-Common Gateway Interface. While you don't really need to know the intricacies of CGI to write queries, you do need to know how it expects queries to look. A CGI query looks like a regular URL except it has extra parameters on the end. The query portion starts with a "?" and is followed by a list of parameters. Each parameter in the query is separated by a "&", and parameter values are specified in a "name=value" format. Parameters are not required to have values. A CGI query to run a script called find-people, taking parameters called name, age, and occupation, would look like this:
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (7 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

http://localhost/cgi-bin/find-people?occupation=engineer&age=30&name=smith Knowing this, you can easily write a class that takes an URL and a set of parameters and generates a query URL. Listing 6.3 shows just such a class.

Listing 6.3 Source Code for URLQuery.java import java.net.*; import java.util.*; // // // // This class provides a way to create an URL to perform a query against a Web server. The query takes the base URL of the the program you are sending the query to, and a set of properties that will be converted into a query string.

public class URLQuery extends Object { public static URL createQuery(URL originalURL, Properties parameters) throws MalformedURLException { // Queries have the file name followed by a ? String newFile = originalURL.getFile()+"?"; // Now append the query parameters to the filename Enumeration e = parameters.propertyNames(); boolean firstParameter = true; while (e.hasMoreElements()) { String propName = (String) e.nextElement(); // Parameters are separated by &'s, if this isn't the first parameter // append a & to the current query string (file name) if (!firstParameter) newFile += "&"; // Add the variable name to the query string newFile += URLEncoder.encode(propName); // Get the variable's value String prop = parameters.getProperty(propName); // If the variable isn't null, append "=" followed by the value if (prop != null) { newFile += "="+URLEncoder.encode(prop); } firstParameter = false; }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (8 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

// // // //

Return the full URL consisting of the original protocol, host, and port and the new, enhanced filename, which contains all the query parameters. This URL is suitable for opening with showDocument or any other URL operation. return new URL(originalURL.getProtocol(), originalURL.getHost(), originalURL.getPort(), newFile); }

You retrieve the results of a query just like you retrieve any other file on the Web. You can open up a stream directly from the URL, you can get a URLConnection object, or you can open up a socket and speak directly to the server. Because queries frequently return Web pages, you may want to use the openDocument method in the Applet class. This enables you to see the results of the query all neatly formatted by the Web browser instead of the raw HTML codes that you get from an input stream. Listing 6.4 shows an applet that submits a query to the Lycos search engine (http://www.lycos.com) and displays the results using showDocument.

Listing 6.4 Source Code for LycosQuery.java import import import import java.applet.*; java.util.*; java.net.*; java.io.*;

// This applet performs a query against the Lycos search engine // and opens up the results as a new document. public class LycosQuery extends Applet { public void init() { try { // Create the base URL to the lycos query URL url = new URL( "http://www.lycos.com/cgi-bin/pursuit"); Properties queryProps = new Properties(); // // // // Fill in the query variables. These were determined by looking at the Lycos query form. You search on the terms "java" and "cgi" requesting a maximum of 20 entries. The minscore value of .5 is what Lycos calls a "good match". queryProps.put("query", "java cgi"); queryProps.put("matchmode", "and");

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (9 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

queryProps.put("maxhits", "20"); queryProps.put("minscore", ".5"); queryProps.put("terse", "standard"); // Create the query URL URL fullURL = URLQuery.createQuery(url, queryProps); // Open up the results as a new document getAppletContext().showDocument(fullURL); } catch (Exception e) { e.printStackTrace(); } } }

Figure 6.2 shows the results of the Lycos query generated by the LycosQuery applet. Figure 6.2 : You can create a query and then use showDocument to display the results.

Posting Data with the URL Class


Web queries are actually something of a hack. They use the HTTP GET message, which was originally intended to retrieve files. A query actually sends data to the Web server embedded in the name of the file it is requesting. One of the problems you can encounter with Web queries is that they are limited in size. You can't use a query to send back a large block of text like an e-mail message or a problem report. The HTTP POST message can handle large blocks of data. In fact, that's what it was intended for. Most query programs, at least the well-written ones, can handle requests either as a GET or a POST message. A GET method sends only an HTTP header in its message. A POST, on the other hand, has both a header and content. In this way, the POST message is very similar in structure to an HTTP response. You are required by the HTTP protocol to include a Content-length: field in a POST message. You have to do a number of extra things when sending a POST message with the URLConnection class. First, you must enable output on the connection by calling setDoOutput: myURLConnection.setDoOutput(true); For good measure, you should also call setDoInput: myURLConnection.setDoInput(true); Next, you should disable caching. You want to make sure that your information goes all the way to the server, and that the response you receive is really from the server and not from the cache: myURLConnection.setUseCaches(false);

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (10 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

You should set a content type for the data you are sending. A typical content type would be application/octetstream: myURLConnection.setRequestProperty("Content-type", "application/octet-stream"); You are required to send a content length in a POST message. You can set this the same way you set the content type: myURLConnection.setRequestProperty("Content-length", ""+stringToSend.length()); // cheap way to convert int to string Once you have the headers taken care of, you can open up an output stream and write the content to the stream: DataOutputStream outStream = new DataOutputStream( myURLConnection.getOutputStream()); outStream.writeBytes(stringToSend()); Make sure that the string you send is terminated with \r\n. Once you have sent the information for the post, you can open up an input stream and read the response back from the server just as you did with a GET. Listing 6.5 shows an application that sends a POST message to one of the NCSA's example CGI programs.

Listing 6.5 Source Code for URLPost.java import java.net.*; import java.io.*; public class URLPost extends Object { public static void main(String args[]) { try { URL destURL = new URL( "http://hoohoo.ncsa.uiuc.edu/cgi-bin/test-cgi/foo"); // The following request data mimics what the NCSA example CGI // form for this CGI program would send. String request = "button=on\r\n"; URLConnection urlConn = destURL.openConnection(); urlConn.setDoOutput(true); // we need to write urlConn.setDoInput(true); // just to be safe... urlConn.setUseCaches(false); // get info fresh from server // Tell the server what kind of data you are sending - in this case, // just a stream of bytes.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (11 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

urlConn.setRequestProperty("Content-type", "application/octet-stream"); // Must tell the server the size of the data you are sending. This also // tells the URLConnection class that you are doing a POST instead // of a GET. urlConn.setRequestProperty("Content-length", ""+request.length()); // Open an output stream so you can send the info you are posting DataOutputStream outStream = new DataOutputStream( urlConn.getOutputStream()); // Write out the actual request data outStream.writeBytes(request); outStream.close(); // Now that you have sent the data, open up an input stream and get // the response back from the server DataInputStream inStream = new DataInputStream( urlConn.getInputStream()); int ch; // Dump the contents of the request to System.out while ((ch = inStream.read()) >= 0) { System.out.print((char) ch); } inStream.close(); } catch (Exception e) { e.printStackTrace(); } } }

Figure 6.3 shows the working of this application. Figure 6.3 : A Java applet or application can use the URL class to perform an HTTP POST.

Posting Data Using Sockets


You have already seen the basic differences between the GET and the POST messages. If you want to perform a POST with a raw socket connection, rather than using the URLConnection class, you don't have
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (12 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

to do a whole lot. It is basically the same method you used when you wrote a socket-based HTTP GET, but in addition to sending the GET command, you must also send the "Content-type," and "Content-length" messages, as well as the request data. Listing 6.6 shows the socket-based equivalent of the example program in Listing 6.5.

Listing 6.6 Source Code for PostSockURL.java import java.net.*; import java.io.*; // This applet shows you how to open up a socket to an HTTP server // and post data to a server. It posts information to one of the // example CGI programs set up by the NCSA. public class PostSockURL extends Object { public static void main(String args[]) { try { // Open up a socket to the Web server where this applet came from Socket sock = new Socket("hoohoo.ncsa.uiuc.edu", 80); // Get input and output streams for the socket connection DataInputStream inStream = new DataInputStream( sock.getInputStream()); DataOutputStream outStream = new DataOutputStream( sock.getOutputStream()); // This request is what is sent by the NCSA's example form String request = "button=on\r\n"; // Send the POST request to the server // The request is of the form: POST filename HTTP/1.0 outStream.writeBytes("POST /cgi-bin/test-cgi/foo "+ " HTTP/1.0\r\n"); // Next, send the content type (don't forget the \r\n) outStream.writeBytes( "Content-type: application/octet-stream\r\n"); // Send the length of the request outStream.writeBytes( "Content-length: "+request.length()+"\r\n"); // Send a \r\n to indicate the end of the header outStream.writeBytes("\r\n");
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (13 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

// Now send the information you are posting outStream.writeBytes(request); // Dump the response to System.out int ch; while ((ch = inStream.read()) >= 0) { System.out.print((char) ch); } // We're done with the streams, so close them inStream.close(); outStream.close(); } catch (Exception e) { e.printStackTrace(); } } }

Supporting the Cookie Protocol


One of the early problems that plagued Web page designers was how to give information to the client browser for it to remember. If you had one million people accessing your Web server, you don't want to keep information for each one of them on your server if their browsers could just as easily store the information. Fortunately, Netscape noticed this problem fairly early and came up with the notion of a cookie. A cookie is really just a piece of information that has a name, a value, a domain, and a path. Whenever you open up an URL to the cookie's domain and access any files along the cookie's path, the cookie's name and value are passed to the server when you open the URL. A typical use of this might be an access count or a user name. Netscape defined a request header tag called "Cookie:" that is used to pass cookie name-value pairs to the server. A server can set cookie values in a browser by sending a Set-cookie tag in the response header. You should now be able to create Java applications that can open up URLs directly, without the interference of a browser, so you may want to support the cookie protocol. It would be nice if this protocol could be built right into the URL and URLConnection classes. You are welcome to tackle this problem. At first, it would seem like a simple thing to do, but you will find that the URLConnection class, although it has methods to set the desired fields in a request header, will not actually pass these fields to the server. This means that you can call setRequestProperty("Cookie", "Something=somevalue") all day long and the server will never see it. If you want to speak cookies, you'll have to speak HTTP over a socket. Luckily for you, this chapter contains code to do just that. Listing 6.7 shows a Cookie class that represents the information associated with a cookie. It doesn't actually send or receive cookies; it is more like a Cookie data type. One interesting feature is that its constructor can create a cookie from the string returned by the cookie's toString method, making it easy to store cookies in a file and retrieve them.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (14 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

Tip It is often useful to create a string representation of an object that can be used to recreate the object at a later time. While you can use object serialization to read and write objects to a file, a string representation can be edited with a simple text editor.

Listing 6.7 Source Code for Cookie.java import java.net.*; import java.util.*; // // // // This class represents a Netscape cookie. It can parse its values from the string from a Set-cookie: response (without the Set-cookie: portion, of course). It is little more than a fancy data structure.

public class Cookie { // Define the standard cookie fields public public public public public public // // // // String name; String value; Date expires; String domain; String path; boolean isSecure;

cookieString is the original string from the Set-cookie header. Just save it rather than trying to regenerate for the toString method. Note that since this class can initialize itself from this string, it can be used to save a persistent copy of this class! public String cookieString;

// Initialize the cookie based on the origin URL and the cookie string public Cookie(URL sourceURL, String cookieValue) { domain = sourceURL.getHost(); path = sourceURL.getFile(); parseCookieValue(cookieValue); } // Initialize the cookie based solely on its cookie string public Cookie(String cookieValue) { parseCookieValue(cookieValue);
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (15 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

} // Parse a cookie string and initialize the values protected void parseCookieValue(String cookieValue) { cookieString = cookieValue; // Separate out the various fields, which are separated by ;'s StringTokenizer tokenizer = new StringTokenizer( cookieValue, ";"); while (tokenizer.hasMoreTokens()) { // Eliminate leading and trailing white space String token = tokenizer.nextToken().trim(); // See if the field is of the form name=value or if it is just // a name by itself. int eqIndex = token.indexOf('='); String key, value; // If it is just a name by itself, set the field's value to null if (eqIndex == -1) { key = token; value = null; // Otherwise, the name is to the left of the '=', value is to the right } else { key = token.substring(0, eqIndex); value = token.substring(eqIndex+1); } isSecure = false; // convert the key to lowercase for comparison with the standard field names String lcKey = key.toLowerCase(); if (lcKey.equals("expires")) { expires = new Date(value); } else if (lcKey.equals("domain")) { if (isValidDomain(value)) { domain = value; } } else if (lcKey.equals("path")) { path = value; } else if (lcKey.equals("secure")) { isSecure = true; // If the key wasn't a standard field name, it must be the cookie's name
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (16 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

// You don't use the lowercase version of the name here. } else { name = key; this.value = value; } } } // // // // // // isValidDomain performs the standard cookie domain check. A cookie domain must have at least two portions if it ends in .com, .edu, .net, .org, .gov, .mil, or .int. If it ends in something else, it must have 3 portions. In other words, you can't specify .com as a domain, it has to be something.com, and you can't specify .ga.us as a domain, it has to be something.ga.us. protected boolean isValidDomain(String domain) { // Eliminate the leading period for this check if (domain.charAt(0) == '.') domain = domain.substring(1); StringTokenizer tokenizer = new StringTokenizer(domain, "."); int nameCount = 0; // just count the number of names and save the last one you saw String lastName = ""; while (tokenizer.hasMoreTokens()) { lastName = tokenizer.nextToken(); nameCount++; } // At this point, nameCount is the number of sections of the domain // and lastName is the last section. // More than 2 sections is okay for everyone if (nameCount > 2) return true; // Less than 2 is bad for everyone if (nameCount < 2) return false; // Exactly two, you better match one of these 7 domain types if (lastName.equals("com") || lastName.equals("edu") || lastName.equals("net") || lastName.equals("org") || lastName.equals("gov") || lastName.equals("mil") || lastName.equals("int")) return true; // Nope, you fail - bad domain! return false; } // Use the cookie string as originally set in the Set-cookie header // field as the string value of this cookie. It is unique, and if you write
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (17 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

// this string to a file, you can completely regenerate this object from // this string, so you can read the cookie back out of a file. public String toString() { return cookieString; } }

The Cookie class is basically a holder for cookie data. The only methods in the Cookie class deal with converting strings into cookies and vice versa. The parseCookieValue method in the Cookie class implements a crucial part of the cookie protocol. It takes a string containing the settings for a cookie. The settings are of the form name=value and are separated by semicolons. The settings include the name of the cookie, the cookie's value, its expiration date, and the path name for the cookie. The domain setting for a cookie specifies which hosts should receive the cookie. Whenever a URL in the cookie's domain is opened and the URL is in the cookie's path, the server for that URL is passed the cookie. For example, if you set the domain to mydomain.com and the path to /me/stuff, then the URL http://mydomain. com/me/stuff/mycgi will receive the cookie. An URL of http://mydomain.com/you/files would not receive the cookie, because the paths don't match. There are some restrictions on the cookie's domain, too. If the domain ends in .com, .edu, .org, .net, .gov, .mil, or .int, you only need two components in the domain. In other words, you need one other name in addition to the ending. For example, mydomain.com is a valid domain. If the domain ends with any other name, you must have at least three components in the domain. For example, mydomain.au would not be a valid cookie domain, but mydomain.outback.au would be valid. Because cookies are supposed to be persistent, you need a class to manage your cookies-preferably by storing them in a file or a database. Listing 6.8 presents a portion of the CookieDatabase class that maintains a table of known cookies. The full source to the class is available on the CD-ROM that comes with this book. It has methods to store the table in a file and retrieve the table from a file. It can also examine an URL and return a string of cookie values for that URL. The CookieDatabase class does not actually read cookies from a Web server or write them to the server. It simply keeps a table of known cookies. If presented with a host name and path name, the CookieDatabase class will determine which cookies are valid for that host name and path name and will return the appropriate cookie string. The getCookieString method from the CookieDatabase class, shown in Listing 6.8, performs the matching between an URL and a cookie. It decides what cookies should be sent for a particular URL and creates a string containing all the cookie values that need to be sent.

Listing 6.8 getCookieString Method from CookieDatabase // // // // getCookieString does some rather ugly things. First, it finds all the cookies that are supposed to be sent for a particular URL. Then it sorts them by path length, sending the longest path first (that's what Netscape's specs say to do - I'm only following orders).

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (18 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

public static String getCookieString(URL destURL) { if (cookies == null) { cookies = new Vector(); } // sendCookies will hold all the cookies you need to send Vector sendCookies = new Vector(); // currDate will be used to prune out expired cookies as we go along Date currDate = new Date(); for (int i=0; i < cookies.size();) { Cookie cookie = (Cookie) cookies.elementAt(i); // See if the current cookie has expired. If so, remove it if ((cookie.expires != null) && (currDate.after( cookie.expires))) { cookies.removeElementAt(i); continue; } // You only increment i if you haven't removed the current element i++; // If this cookie's domain doesn't match the URL's host, go to the next one if (!destURL.getHost().endsWith(cookie.domain)) { continue; } // If the paths don't match, go to the next one if (!destURL.getFile().startsWith(cookie.path)) { continue; } // Okay, you've determined that the current cookie matches the URL, now // add it to the sendCookies vector in the proper place (i.e. ensure // that the vector goes from longest to shortest path). int j; for (j=0; j < sendCookies.size(); j++) { Cookie currCookie = (Cookie) sendCookies. elementAt(j); // If this cookie's path is longer than the cookie[j], you should insert // it at position j. if (cookie.path.length() < currCookie.path.length()) { break; }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (19 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

} // If j is less than the array size, j represents the insertion point if (j < sendCookies.size()) { sendCookies.insertElementAt(cookie, j); // Otherwise, add the cookie to the end } else { sendCookies.addElement(cookie); } } // Now that the sendCookies array is nicely initialized and sorted, create // a string of name=value pairs for all the valid cookies String cookieString = ""; Enumeration e = sendCookies.elements(); boolean firstCookie = true; while (e.hasMoreElements()) { Cookie cookie = (Cookie) e.nextElement(); if (!firstCookie) cookieString += "; "; cookieString += cookie.name + "=" + cookie.value; firstCookie = false; } // Return null if there are no valid cookies if (cookieString.length() == 0) return null; return cookieString; }

Finally, Listing 6.9 shows you an example application that fetches a Web page that contains a cookie. Whenever the application runs, it loads its cookie table from a file called cookies.dat. After you run the program, you can look at the cookies.dat file. It is printable text. The program accesses a Web page called "Andy's Netscape HTTP Cookie Page" (http://www.illuminatus.com/cookie), which is a great resource for learning about cookies and seeing them in action. Since the CookieDatabase class does not automatically look for cookies in a response from a Web server, and does not automatically send cookie data, you have to do that yourself. Cookies are sent to the server in the header portion of an HTTP command. Note You can set only a few specific header values in the URL class, and the cookie string is not one of them. This means that you have to use sockets to perform a GET or POST that supports cookies.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (20 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

Whenever you open an URL, you can get the cookie string for the URL by calling getCookieString in the CookieDatabase class. When reading the response from the Web server, you must scan the header results for the Setcookie command. Whenever you find this command, you pass the cookie string from the Set-cookie command to the addCookie method in the CookieDatabase class. The method will extract all the important information from the cookie string.

Listing 6.9 Source Code for TestCookie.java import java.net.*; import java.io.*; // // // // // // // // // This application demonstrates the CookieDatabase and Cookie classes. It first loads the cookie database from cookies.dat, then it opens up Andy's Netscape HTTP Cookie Page, which happens to assign you a cookie. Because the Java URL classes do not let you set arbitrary header strings (GRR!!!), you have to do cookie stuff MANUALLY (double-GRR!!) Much of this code was taken from the example of doing a GET with raw sockets.

public class TestCookie extends Object { public static void main(String args[]) { try { CookieDatabase.loadCookies("cookies.dat"); } catch (IOException ignore) { } try { // URL to Andy's Netscape HTTP Cookie Page, it's quite helpful URL url = new URL("http://www.illuminatus.com/cookie"); int port = url.getPort(); if (port < 0) port = 80; // Open a socket to the server Socket socket = new Socket(url.getHost(), port); // Create an output stream so you can write out the request header DataOutputStream outStream = new DataOutputStream( socket.getOutputStream()); // Write the GET command outStream.writeBytes( "GET "+url.getFile()+" HTTP/1.0\r\n"); // See if there are any valid cookies for this URL
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (21 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

String cookieString = CookieDatabase. getCookieString(url); // If so, write out a cookie header if (cookieString != null) { outStream.writeBytes("Cookie: "+ cookieString+"\r\n"); } // Write out \r\n for the end of the header area outStream.writeBytes("\r\n"); // Now read the response from the server DataInputStream inStream = new DataInputStream( socket.getInputStream()); String line; // Read the header strings scanning for a set-cookie tag, which // means you have to update the cookie database while ((line = inStream.readLine()) != null) { if (line.length() == 0) break; // if you got a set-cookie, create a new cookie and add it to the database if (line.toLowerCase().startsWith( "set-cookie: ")) { CookieDatabase.addCookie( new Cookie(url, line.substring(12))); } } // Now that you've finished with the header, just dump out the // contents of the page. This won't look too pretty, it's all pure // HTML. int ch; while ((ch = inStream.read()) >= 0) { System.out.print((char) ch); } // Save the cookie database for later use CookieDatabase.saveCookies("cookies.dat"); } catch (Exception e) { e.printStackTrace(); } } }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (22 of 23) [8/14/02 10:52:42 PM]

Chapter 6 -- Communicating with a Web Server

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch6.htm (23 of 23) [8/14/02 10:52:42 PM]

Chapter 7 -- Creating Smarter Forms

Chapter 7 Creating Smarter Forms


by Mark Wutka

CONTENTS
G G G G G G G

Smarter Forms Creating Forms with the AWT Checking for Errors on the Client Side Adding Context-Sensitive Help Creating Dynamic Forms Loading Another URL from an Applet Creating Image Maps with Hot Spots

Smarter Forms
In the beginning, Web pages were not very lively. You could read information, click certain words and pictures, and view other unlively pages. Then, the forms interface came along and added some degree of interaction with a page. You were able to enter data and then click a button and send your information to a server, which would analyze what you sent and return the results. Unfortunately, these forms were also lacking a certain "lively" quality. All the error checking was left up to the server, as was any other form of interaction such as context-sensitive help. Java enables you to spice up your old Web forms. You can perform error checking before you ever send data to the server, drastically improving response time to the user and cutting down on server usage. You can also add context-sensitive help. You can even create dynamic forms that change depending on the other information added.

Creating Forms with the AWT


Java's AWT toolkit contains a set of GUI components that are very useful for constructing forms suitable for a Web page. All the form components provided in HTML have equivalent AWT components, so you can simulate any existing HTML form. Listing 7.1 shows just such a form, a query entry form for the Lycos Web searching engine (http://www.lycos.com). It enables you to enter the keywords to search for, and some other search parameters, and then it sends the query to the Lycos server just as if you had used, the Lycos page. Note

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (1 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

This applet is meant for demonstration purposes only. While it will function with the real Lycos server, it does not display the advertisements from the normal Lycos search page. Although you may consider this a plus, it really isn't. Advertisements keep companies like Lycos in business and allow them to provide these wonderful services to you at no cost. Please do not use this applet or any other program to thwart a company's advertising displays. It hurts everyone in the long run.

Listing 7.1 Source Code for LycosForm.java import import import import // // // // java.awt.*; java.applet.*; java.net.*; java.util.*;

This applet demonstrates the use of AWT components as an alternative to the HTML forms interface. It creates a query for the Lycos search engine and displays the results using the showDocument method.

public class LycosForm extends Applet { protected TextField queryString; // the terms to search for protected Choice matchTerms; // how many terms to match String matchTermValues[] = { "and", "or", "2", "3", "4", "5", "6", "7" }; protected Choice matchStrength; // how good a match String matchStrengthValues[] = { ".1", ".3", ".5", ".7", ".9" }; protected Choice resultCount; // how many matches to show String resultCountValues[] = { "10", "20", "30", "40" }; protected Choice resultType; // how much information to display String resultTypeValues[] = { "terse", "standard", "verbose" }; protected Button searchButton; // perform the query public void init() { // Arrange the query form as a 3 horizontal grid elements setLayout(new GridLayout(3, 0)); // Create the element with the query string and submit button add(createQueryPanel()); // Create the element containing search options

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (2 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

add(createSearchOptionsPanel()); // Create the element containing display options add(createDisplayOptionsPanel()); }

The AWT layout managers provide a reasonable way to place components on the screen without putting them in fixed positions. This allows your applet to adapt to different screen sizes. Unfortunately, it is often difficult to arrange the components the way you want them. The GridBagLayout class provides the most flexible way to arrange components, but it is often rather cumbersome to use. As an alternative to the GridBagLayout class, or even in conjunction with it, you can use different panels to group your components, nesting some panels within others. The LycosQuery class uses this technique. It creates a main panel that uses a grid layout with three rows. The first row is another panel that uses a flow layout, while the last two rows use two-column grid layouts. Tip Grid layouts expand components to fill all available space. If you want to maximize a component's size, the grid layout is a good choice. Flow layouts, on the other hand, don't adjust the component size, so they tend to use the minimum required space. Grid bag layouts let you choose either of these options.

Listing 7.1 Source Code for LycosForm.java (continued) // createQueryPanel creates a panel containing a text field // for query terms and the button used to send the query to Lycos protected Panel createQueryPanel() { Panel panel = rEw Panel(); panel.setLayout(new FlowLayout(FlowLayout.LEFT)); panel.add(new Label("Query: ")); queryString = new TextField(30); panel.add(queryString); searchButton = new Button("Search"); panel.add(searchButton); return panel; } // createSearchOptionsPanel creates a panel containing the // choices for the number of terms to match and the strength // of the matches.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (3 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

protected Panel createSearchOptionsPanel() { Panel panel = new Panel(); panel.setLayout(new GridLayout(0, 3)); panel.add(new Label("Search Options:")); matchTerms = new Choice(); matchTerms.addItem("match all terms (AND)"); matchTerms.addItem("match any term (OR)"); matchTerms.addItem("match 2 terms"); matchTerms.addItem("match 3 terms"); matchTerms.addItem("match 4 terms"); matchTerms.addItem("match 5 terms"); matchTerms.addItem("match 6 terms"); matchTerms.addItem("match 7 terms"); matchTerms.select(1); panel.add(matchTerms); // default on the OR option

matchStrength = new Choice(); matchStrength.addItem("loose match"); matchStrength.addItem("fair match"); matchStrength.addItem("good match"); matchStrength.addItem("close match"); matchStrength.addItem("strong match"); matchStrength.select(0); // default on the loose match panel.add(matchStrength); return panel; } // createDisplayOptionsPanel creates a panel containing the choices for // the number of matches returned and the amount of detail to return. protected Panel createDisplayOptionsPanel() { Panel panel = new Panel(); panel.setLayout(new GridLayout(0, 3)); panel.add(new Label("Display Options:")); resultCount = new Choice(); resultCount.addItem("10 results resultCount.addItem("20 results resultCount.addItem("30 results resultCount.addItem("40 results resultCount.select(0); panel.add(resultCount);

per per per per

page"); page"); page"); page");

// Default to 10 results per page

resultType = new Choice(); resultType.addItem("Summary Results");


file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (4 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

resultType.addItem("Standard Results"); resultType.addItem("Detailed Results"); resultType.select(1); panel.add(resultType); return panel; } // Default to Standard Results

The URLQuery class used in this next part of the LycosQuery class was introduced in the section, "Performing a Query with GET," in Chapter 6, "Communicating with a Web Server." It allows you to create an HTTP query from an URL and a properties table containing the query parameters. It would be nice if you could examine the data coming back from the query and still let the browser display the actual HTML codes returned, but on most browsers you can't. You can either examine the data coming back and display it yourself from the Java program, or use showDocument to display the data directly.

Listing 7.1 Source Code for LycosForm.java (continued) // sendRequest uses the URLGet class to create a CGI Query to Lycos. protected void sendRequest() { Properties queryProps = new Properties(); queryProps.put("query", queryString.getText()); queryProps.put("matchmode", matchTermValues[ matchTerms.getSelectedIndex()]); queryProps.put("minscore", matchStrengthValues[ matchStrength.getSelectedIndex()]); queryProps.put("maxhits", resultCountValues[ resultCount.getSelectedIndex()]); queryProps.put("terse", resultTypeValues[ resultType.getSelectedIndex()]); try { URL lycosURL = new URL( "http://www.lycos.com/cgi-bin/pursuit"); URL fullURL = URLQuery.createQuery(lycosURL, queryProps); getAppletContext().showDocument(fullURL); } catch (Exception e) { e.printStackTrace(); } }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (5 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

public boolean action(Event evt, Object whichAction) { // If someone pressed the button, send the request if (evt.target == searchButton) { sendRequest(); return true; } return false; } }

Figure 7.1 shows the original version of the Lycos query form. Figure 7.1 : The Lycos search engine is a popular Web search tool. Figure 7.2 shows a mimic of an HTML form. Figure 7.2 : You can mimic any HTML form in Java. You may be wondering why you should go through the trouble of creating a Java applet that presents a form when it is easier to define one in HTML. If you are simply presenting a form, with no help facility and no error checking, go ahead and do it in HTML. The real advantage of Java comes when you need to do things beyond the basic form facilities in HTML.

Checking for Errors on the Client Side


You can increase the response time of your forms and lessen the load on your server if you put error checking into your Java forms. When you do forms in HTML, there is no way to check to make sure the data is correct before you send it off to the server. You put all the responsibility for error checking on the server's shoulders. This also means that the user has to wait for the request to go out to the server, and a response to come back, before he knows that there was something wrong. If you use Java to do error checking, you will make the user happier because he will know instantly that he has entered incorrect data. You will also decrease the load on your server because it is no longer handling any incorrect data (hopefully). As your forms become more and more complex, the need for error checking grows dramatically. In the example of the Lycos query form, the only place in which you can make an error entering data is in the query string itself. If you fail to enter any keywords, the search engine has nothing to look for. While Lycos normally just presents you with another form, this Java version of the query form pops up a dialog box reminding you that you need to enter keywords in the query entry area. The only place you need to change the old LycosForm class is in the action method. You make it call a checkRequest method instead of sendRequest. The checkRequest method verifies the form, and if everything is correct, it calls sendRequest. Here are the updated action and sendRequest methods: public boolean action(Event evt, Object whichAction) { // If someone pressed the button, send the request
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (6 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

if (evt.target == searchButton) { checkRequest(); return true; } return false; } protected void checkRequest() { if (queryString.getText().length() == 0) { OKDialog.createOKDialog( "Please enter a list of terms to search for"); return; } sendRequest(); } This is actually a pretty minor form of error checking. On more advanced forms, you may need to check to see that information entered in one section is consistent with information entered in another area. For example, you might have a "sex" field on your form and a "maiden name" field somewhere else. If sex was "male," the maiden name doesn't apply. Your error checking routine would check to make sure that if you entered something under "maiden name," you had better be female. You can avoid some situations such as this one by creating dynamic forms, which are discussed later in this chapter.

Adding Context-Sensitive Help


Context-sensitive help is an incredibly useful feature, especially on the day when a software product comes with a 500-page manual. While you should strive towards making your program completely intuitive, requiring no special training or documentation, that isn't always possible. Context-sensitive help can lessen the need for other documentation and is much more timely and relevant than an online user's manual. In case you are unclear about what "context-sensitive help" is, it is simply help on what you are currently doing. For example, if you are entering text in a field and you suddenly press the help key, you should expect to get help for the text field you are entering. Many software products require that you pop up the online manual and skip to the page discussing the field you want information on. If you know what the user is currently doing, you know what to tell them when they ask for help. The way you present context-sensitive help is up to you. One very useful method, which is also quite passive, is to display a one-line message at the bottom of the screen giving a quick description of the area where the mouse is. For example, when you are looking at a Web page and pass the mouse over a link, your browser may display the destination URL for that link at the bottom of the page. This is a form of context-sensitive help. You can also define a particular key to be the "help" key. Tell the user to press that key any time he needs help. The F1 key in many software packages is the help key; you should seriously consider making it the help key in your applets, too. Remember that you want to give all your applets and applications a similar feel in the same way that most cars have a similar feel. It doesn't take long to figure out how to drive a car you've never seen before. It should be that way with software. Listing 7.2 shows a HelpDialog class that is useful for popping up screens containing help text. The help dialog is a simple OK dialog box-it displays a text message and a button labeled "OK" which, when clicked, makes the dialog box
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (7 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

disappear. One thing to keep in mind when you want to create dialog boxes is that you must have a parent frame for the dialog box. When you are running an applet, you can't normally access the applet's parent frame. The HelpDialog class addresses this problem by creating its own frame. It saves the frame in a static variable so it doesn't have to create a new frame the next time it needs to create a dialog window. You can actually access the parent frame for an applet. Sometimes it will work exactly like you want. It usually works for dialogs, but it fails miserably on some platforms when you try to create a menu for the parent frame. You can use the getParent method from the component class to trace back up through the component hierarchy to find the applet's parent frame. The following code fragment finds an applet's parent frame: Component parentFrame = getParent(); while ((parentFrame != null) && !(parentFrame instanceof Frame)) { parentFrame = parentFrame.getParent(); } Frame myFrame = (Frame) parentFrame; At this point, myFrame would either contain the parent frame of the applet, or null if it couldn't find the parent frame.

Listing 7.2 Source Code for HelpDialog.java import java.awt.*; // The HelpDialog class is a variation on the OKDialog class. // It allows you to create an OK dialog with a textarea instead // of a label. You can use this to display help text. public class HelpDialog extends Dialog { protected Button okButton; protected static Frame createdFrame; public HelpDialog(Frame parent, String message) { super(parent, false); // Must call the parent's constructor // Create the OK button and the message to display okButton = new Button("OK"); TextArea helpInfo = new TextArea(message, 10, 40); helpInfo.setEditable(false); setLayout(new BorderLayout()); add("Center", helpInfo); add("South", okButton); resize(500, 300); }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (8 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

// The action method just waits for the OK button to be clicked; // when it is, it hides the dialog, causing the show() method to return // back to whoever activated this dialog. public boolean action(Event evt, Object whichAction) { if (evt.target == okButton) { hide(); if (createdFrame != null) { createdFrame.remove(this); createdFrame.hide(); dispose(); return true; } } return true; } // Shortcut to create a frame automatically, the frame is a static variable // so all dialogs in an applet or application can use the same frame. public static void createHelpDialog(String helpText) { // If the frame hasn't been created yet, create it if (createdFrame == null) { createdFrame = new Frame("Help"); } // Create the dialog now HelpDialog helpDialog = new HelpDialog(createdFrame, helpText); // Shrink the frame to nothing createdFrame.resize(0, 0); // Show the dialog createdFrame.show(); helpDialog.show(); } }

In addition to the HelpDialog class, you need a way to assign help information directly to your AWT components. It would have been nice if Sun had built that right into the AWT, and maybe they will in the future, but for now you have to do it yourself. You could subclass all the AWT components to support help if you really had nothing better to do for a month or two, but there are easier ways. One simple way is just to store the components and their corresponding help text in a hash table. Whenever someone requests help from within an AWT component, look in the table and see if you have defined any help for that component. Listing 7.3 shows the HelpSystem class that enables you to assign help text to

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (9 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

AWT components. It also contains a method to display the help for a component, but it makes no assumptions on how you actually request the help in the first place.

Listing 7.3 Source Code for HelpSystem.java import java.awt.*; import java.util.*; // Help system is a container for help strings. You can add // and remove help strings for components. It also provides // a doHelp method that actually pops up the help dialog. public class HelpSystem extends Object { Hashtable helpTable; public HelpSystem() { helpTable = new Hashtable(); } public void addHelp(Component comp, String text) { helpTable.put(comp, text); } public void removeHelp(Component comp) { helpTable.remove(comp); } public boolean doHelp(Component comp) { if (comp == null) return false; String helpString = (String) helpTable.get(comp); if (helpString == null) { return false; } HelpDialog.createHelpDialog(helpString); return true; } }

Now that you have a way to display help and a way to map help strings to components, you need to add some sort of help
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (10 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

key to your applet. Going back to the Lycos search form applet, you can modify it to use F1 as the help key. The AWT components are polite enough to ignore keyDown events for keys they do not recognize, and they all leave the F1 key alone. You can trap the F1 key in your applet and display the appropriate help text. To add context-sensitive help to the LycosForm class, you need to create an instance of the help system. Since there are several methods that actually use the help system, you declare it as an instance variable: protected HelpSystem helpSystem = new HelpSystem(); Next, for each component that will have a help screen, you add the component to the help system. For example, once you create the queryString text field, you can add a help string for it with the following code fragment: helpSystem.addHelp(queryString, "QUERY HELP\nEnter the words you want to search\n"+ "for separated by spaces. Avoid common words like\n "+ "\"the\" or \"and\"."); The trickiest part of implementing the help system is grabbing the F1 key and figuring out which component the user wants help on. When you receive keyboard events, you are given an x and y coordinate where the keystroke occurred. Unfortunately, this does not really indicate where the mouse was when you pressed the key. The x and y coordinates are bounded by the component that currently has the keyboard focus. For context-sensitive help, you don't want the user to have to move the keyboard focus to another component before requesting help. If this were the case, they would have to click a button before they could get help for that button. What you must do, instead, is track the movement of the mouse all the time. You can do this very simply by creating two instance variables in your class, mouseX and mouseY: protected int mouseX; protected int mouseY; // the current X coord of the mouse // the current Y coord of the mouse

Next, you override the mouseMove method. This method is called whenever the mouse moves. You simply copy the x and y coordinates of the mouse and return: public { mouseX mouseY return } boolean mouseMove(Event evt, int x, int y) = x; = y; false;

Notice that you return false from the mouseMove method. This indicates that you haven't actually handled the mouse movement event, allowing the event to be passed to another component. If you do not want another component to see the mouse movement event, you should return true instead. The hardest part of implementing this context-sensitive help system is determining which component the user wants help on. The problem here is that you have to take the x and y coordinates of the mouse and locate the component at those coordinates. The locate method does this, sort of. The locate method takes an x and y coordinate and returns the component at those coordinates. It only looks one level deep in the component hierarchy, however. If you are using nested panels, as the LycosForm applet does, the locate method will only return the panel enclosing the component you really want.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (11 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

The solution for this problem is simple. If the locate method returns a container, you use the locate method in that container. You keep repeating the process until locate returns a component that is not a container. There is one additional little sticking point here. The locate method expects the x and y coordinates to be relative to the container you are searching. The first time you call locate, everything is fine, since the mouse x and y coordinates are relative to your applet. After that, you have to adjust them to be relative to the container returned by locate. For example, suppose you had mouse coordinates of 100, 50 and the locate method returned an instance of the Panel class for those coordinates. Suppose that the panel's upper-left corner was at 65, 45. You would subtract the panel's coordinates from the original mouse coordinates, giving a new location of 35,5. Now you call the locate method in the panel with the new coordinates. You can use the location method to get the coordinates of the upper-left corner of any component. Listing 7.4 shows a keyDown method for the LycosForm applet that uses this technique to identify the component where the F1 key was pressed.

Listing 7.4 Source Code for the keyDown Method in LycosForm3.java public boolean keyDown(Event evt, int ch) { if (ch == Event.F1) { int x = mouseX; int y = mouseY; // Find out which component this x,y is inside Component whichComp = locate(x, y); // If the component is a container, descend into the container and // find out which of its components contains this x,y while (whichComp instanceof Container) { // If you have to search within a container, adjust the x,y to be relative // to the container. x -= whichComp.location().x; y -= whichComp.location().y; Component nextComp = whichComp.locate(x, y); // if locate returns the component itself, you're done if (nextComp == whichComp) break; whichComp = nextComp; } // Display any available help on the component helpSystem.doHelp(whichComp); } return false; }

Figure 7.3 shows the LycosForm3 applet in action with a Help dialog box displayed.
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (12 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

Figure 7.3 : Context-sensitive help screens make your applets easier to use.

Creating Dynamic Forms


It's funny that with all the advanced technology running on the desktop today, the methods of recording information haven't really changed. Most online forms are just electronic versions of printed forms. This is really a shame because we now have the ability to create forms that adapt to the information you are entering. For example, suppose you are creating a personal information form containing all the typical pieces of information associated with a person. If you look at a typical form of this type, you'll see many sections with instructions such as "Fill in this section only if married." No computerized form should have instructions such as that-not when there are so many ways to avoid it. If you really must have your form look exactly the same, no matter what information is being entered, consider selectively enabling and disabling components if they apply. Listing 7.5 shows a very brief example of this technique.

Listing 7.5 Source Code for DynamicDisable.java import java.awt.*; import java.applet.*; // // // // // This applet demonstrates the technique of enabling and disabling components based on the values of other components. Specifically, it has a choice for sex of "Male" or "Female". It also has a maiden name field that is enabled only if sex is "Female".

public class DynamicDisable extends Applet { TextField maidenName; Choice sex; public void init() { // Create the sex choice sex = new Choice(); sex.addItem("Male"); sex.addItem("Female"); // Default to male sex.select(0); add(sex); // Create maiden name and disable it because sex defaults to male maidenName = new TextField(20);
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (13 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

maidenName.disable(); add(maidenName); } public boolean action(Event evt, Object whichAction) { // If you get an action event on sex, look at the current // value and enable or disable maiden name accordingly if (evt.target == sex) { // If the index is 0, "male" has been selected, so disable maiden name if (sex.getSelectedIndex() == 0) { maidenName.disable(); // otherwise, enable maiden name } else { maidenName.enable(); } return true; } return false; } }

This technique doesn't provide much of an improvement over paper forms, however. You could still be looking at a huge document full of components, some of which are enabled and some which are disabled. It would be a lot kinder to the user to show him only the items he actually needs to fill in. In other words, rather than just disabling a component, hide it-make it invisible. Hiding has its drawbacks, however. When you hide a component, the layout manager will change the layout of the components. If you aren't using a layout manager, this won't be a problem. If you are using a layout manager, pay special attention to how the form changes when you show and hide various components. You may want to perform a mixture of disabling and hiding. Listing 7.6 shows a very brief example of how to hide and show components dynamically, using the same components as the example in Listing 7.5. Notice that you must call the validate method after hiding or showing a component. This causes the layout manager to recompute the component positions.

Listing 7.6 Source Code for DynamicHide.java import java.awt.*; import java.applet.*; // // // // // This applet demonstrates the technique of hiding and showing components based on the values of other components. Specifically, it has a choice for sex of "Male" or "Female". It also has a maiden name field that is visible only if sex is "Female".

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (14 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

public class DynamicHide extends Applet { TextField maidenName; Choice sex; public void init() { // Create the sex choice sex = new Choice(); sex.addItem("Male"); sex.addItem("Female"); // Default to male sex.select(0); add(sex); // Create maiden name and hide it because sex defaults to male maidenName = new TextField(20); maidenName.hide(); add(maidenName); } public boolean action(Event evt, Object whichAction) { // If you get an action event on sex, look at the current // value and show or hide maiden name accordingly if (evt.target == sex) { // If the index is 0, "male" has been selected, so hide maiden name if (sex.getSelectedIndex() == 0) { maidenName.hide(); validate(); // otherwise, show maiden name } else { maidenName.show(); validate(); } return true; } return false; } }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (15 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

The CardLayout layout manager is another good tool for dynamic form construction. It lets you create a stack of different containers (usually panels), only one of which is displayed at any time. By using a card layout, you can create all your panels ahead of time and add them to the card layout. Then, whenever you want to display a specific panel, you tell the card which panel to display. For example, you may have panels that display information on Moe, Larry, and Curly. Listing 7.7 shows a simple example program that uses a card layout and some buttons to select the specific card.

Listing 7.7 Source Code for CardExample.java import java.applet.*; import java.awt.*; // // // // // // This applet demonstrates how a card layout can be used to display different panels. The panels are given names when added to the card layout. There are buttons at the bottom of the screen with names corresponding to the panel names. When you press a button, it tells the card layout to display the card with the same name as the button.

public class CardExample extends Applet { CardLayout cards; Panel stoogePanel; public void init() { // Need a border layout to have the stooge panel in the center and // the buttons at the bottom. setLayout(new BorderLayout()); // Create the main display panel stoogePanel = new Panel(); // Give the main display panel a card layout cards = new CardLayout(); stoogePanel.setLayout(cards); // Create the panels for the different cards. For demo purposes, each // panel just has a label on it. Panel moePanel = new Panel(); moePanel.add(new Label("Moe")); Panel larryPanel = new Panel(); larryPanel.add(new Label("Larry")); Panel curlyPanel = new Panel(); curlyPanel.add(new Label("Curly"));

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (16 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

// Add the separate panels to the stoogePanel giving them their // own card names. stoogePanel.add("Moe", moePanel); stoogePanel.add("Larry", larryPanel); stoogePanel.add("Curly", curlyPanel); // Put the stoogePanel in the middle of the applet's border layout add("Center", stoogePanel); // Now create a row of buttons for selecting the different cards. The // button names must match the names used above. Panel selectorPanel = selectorPanel.add(new selectorPanel.add(new selectorPanel.add(new new Panel(); Button("Moe")); Button("Larry")); Button("Curly"));

// Put the row of buttons at the bottom part of the border layout add("South", selectorPanel); } public boolean action(Event evt, Object whichAction) { // If the action event is for a button, whichAction is the button's // label, which is also the name of a card in this program. We just // tell the card layout to show the appropriate card. if (evt.target instanceof Button) { cards.show(stoogePanel, (String) whichAction); return true; } return false; } }

Figure 7.4 shows the CardExample applet in action. The buttons along the bottom select the different card, which simply contain a single label. Figure 7.4 : A card layout enables you to display one of several panels. When you are creating dynamic forms, you can group sections of your forms onto different cards. You can create different methods for going from one card to the next, like having a master index of the different cards, or putting Next and Prev buttons on each card. If you want to disable a section of the form, don't make that section's card available. For example, suppose you have a part of the form for entering marriage information-date, place, witnesses, and so on. If a person is single, you don't want to present that part. You can remove it from the set of cards in your card layout.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (17 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

Loading Another URL from an Applet


You may have noticed that several applets in the last two chapters actually open up URLs on the Web and display their contents in the browser. They all use the showDocument method in the AppletContext class. You can use the showDocument class to give your Java applets the same connectivity to the rest of the Web that any Web page has. This also enables you to create new and unique ways to access Web pages. The showDocument method can take a second parameter, which is the target frame for the URL. Your Java applet can open up an URL in its own frame, its parent frame, the top-most frame, a brand new frame, or a particular named frame. You can use this ability to create interesting Web page layouts. For instance, you can create an index applet in Java that provides some neat new way of listing URLs. You could run this applet in a narrow frame on the left side of the page, leaving the rest of the page for the frame where the selected URL will be loaded. Note One of the major features lacking in Java is the ability for a Java program to generate HTML data that is displayed by the browser. The showDocument method in Netscape bypasses the URLConnection class and goes straight to a native method to load an URL. You should be able to generate your own HTML data in HotJava if you create a special URLConnection class and define a protocol type for it. We can only hope there will soon be a way to do this in all browsers.

Creating Image Maps with Hot Spots


Image maps were a neat addition to the Web a few years ago. Rather than a list of textual links, you could open up an URL by clicking a particular part of an image. These image maps still have limitations, however, because they are not very interactive. You can create a Java image map, however, that contains hotspots-areas that light up when the mouse passes over them. You can also add context-sensitive help, which is always a nice thing to have. To create an image map with hot spot, you need a way to define what a "hot spot" is. You could simply create a class that represented an area of the image. This class would also be responsible for displaying whatever should appear when the mouse passes over the hot spot. Listing 7.8 shows an abstract class that defines the methods necessary to implement such a class.

Listing 7.8 Source Code for ImageRegion.java import java.awt.*; // ImageRegion is an abstract definition of the region // area supported by the ImageMap class. public abstract class ImageRegion extends Object { public ImageRegion() { }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (18 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

// select is called when you click the mouse within a region public void select() { } // mouseEnter is called when the mouse enters a region public void mouseEnter() { } // mouseLeave is called when the mouse leaves a region public void mouseLeave() { } // getBoundingBox should return the smallest rectangle that // completely encloses this region. public abstract Rectangle getBoundingBox(); // inside returns true if x,y is within this region public abstract boolean inside(int x, int y); // paint is used to draw any hotspot popup information public void paint(Graphics g) { } }

Because the ImageRegion class is an abstract class, you need something concrete to actually implement a region. You will almost certainly need to define a rectangular region at some point. Actually, it is trivial to extend a rectangular region to be a polygon region. Listing 7.9 shows an implementation of ImageRegion that supports polygon regions.

Listing 7.9 Source Code for ImageRegionPoly.java import java.awt.*; // ImageRegionPoly implements a rectangular region for // use with the ImageMap class. public class ImageRegionPoly extends ImageRegion { Polygon boundary; public ImageRegionPoly() {
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (19 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

boundary = new Polygon(); } public ImageRegionPoly(Polygon p) { boundary = p; } public Rectangle getBoundingBox() { return boundary.getBoundingBox(); } public boolean inside(int x, int y) { return boundary.inside(x, y); } }

You may also have a need for a circular region. The ImageRegionCircle class in Listing 7.10 implements a circular region.

Listing 7.10 Source Code for ImageRegionCircle.java import java.awt.*; // ImageRegionCircle defines a circular region for use // with the ImageMap class. public class ImageRegionCircle extends ImageRegion { Point center; int radius; public ImageRegionCircle() { center = new Point(0, 0); radius = 0; } public ImageRegionCircle(Point center, int radius) { this.center = center; this.radius = radius; } public Rectangle getBoundingBox()

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (20 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

{ return new Rectangle(center.x - radius, center.y - radius, 2*radius, 2*radius); } // Use the distance formula to determine if a point is inside or not. // If the distance between x,y and the center of the region is <= the // radius of the circle, the point is within the region. public boolean inside(int x, int y) { int xd = center.x - x; int yd = center.y - y; int dist = (int) Math.sqrt(xd*xd+yd*yd); return dist <= radius; } }

Now that you have a method for defining a region in an image, you need a way to display an image, add these regions to it, and track the mouse to see when it hits a region. The ImageMap class in Listing 7.11 does just that. It also shows you how to define a canvas that displays an image.

Listing 7.11 Source Code for ImageMap.java import java.awt.*; import java.util.*; // // // // // // // The image map is a canvas that displays an image and supports hotspots. The hotspots are defined by subclasses of ImageRegion. There can only be one hotspot active at a time. Whenever a hotspot is active, its paint method is called so it can paint any popup information. You could display a little box of text saying what the hotspot does, for instance. The default paint method for a hotspot does nothing.

public class ImageMap extends Canvas { Image image; Vector regions; ImageRegion selectedRegion; boolean moved; public ImageMap(Image image) { this.image = image;

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (21 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

regions = new Vector(); moved = true; } // The size of the Canvas is defined by the size of the image. public Dimension minimumSize() { return new Dimension(image.getWidth(this), image.getHeight(this)); } public Dimension preferredSize() { return minimumSize(); } public Dimension size() { return minimumSize(); } public void addRegion(ImageRegion region) { regions.addElement(region); } public void removeRegion(ImageRegion region) { regions.removeElement(region); if (region == selectedRegion) { selectedRegion = null; } } // To repaint this canvas, redraw the image. Then, if there is a hotspot // active, call that hotspot's paint method. public void paint(Graphics g) { // Draw the image g.drawImage(image, 0, 0, this); if (selectedRegion != null) { // Find the bounding box for the current region (hotspot) Rectangle r = selectedRegion.getBoundingBox(); // Create a graphics context for the bounding box Graphics regionGraphics = g.create(r.x, r.y, r.width, r.height); // Let the region paint its little area
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (22 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

selectedRegion.paint(regionGraphics); } } // Flicker-free update public void update(Graphics g) { paint(g); }

The next section of the ImageMap class demonstrates a very important concept in object-oriented design. The ImageMap class implements a framework that allows you to plug in different ImageRegion objects. You can add many new types of ImageRegion objects without changing the ImageMap class itself. It is very important to correctly assign class responsibilities in your design. In this case, the ImageMap class is responsible for displaying the master image, or background image. It is also responsible for tracking mouse movements and passing them on to affected regions. The ImageRegion class is responsible for displaying itself on the map if necessary, and for responding to a mouse click. Tip When designing classes for an application, you want to be able to add functionality by adding new classes, and not by changing existing classes. Try to identify things that may change and let those things be implemented by a separate class.

Listing 7.11 Source Code for ImageMap.java (continued) // Need to watch the mouse movement to see if the mouse hits // a hotspot or not. public boolean mouseMove(Event evt, int x, int y) { moved = true; // kludge to handle mouse-click problem // Quick shortcut here, see if you're still in the current region if ((selectedRegion != null) && selectedRegion.inside(x, y)) { return true; } // If there's a current region and you're not in it, tell the old // region that the mouse left it. if (selectedRegion != null) { selectedRegion.mouseLeave(); selectedRegion = null; }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (23 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

// Check all the regions to see if the mouse is within any of them. // If two overlap, it's on a first come, first served basis - that is, // the first region that was added has priority. Enumeration e = regions.elements(); while (e.hasMoreElements()) { ImageRegion r = (ImageRegion) e.nextElement(); // See if the mouse's x,y is within the region's area if (r.inside(x, y)) { selectedRegion = r; r.mouseEnter(); break; } } repaint(); return true; } // Mouse down handles mouse clicks, and also will keep track // of mouse movement public boolean mouseDown(Event evt, int x, int y) { // The moved flag is a kludge. Sometimes you'll get more than // one mouse click. Assume that if the mouse doesn't move // between clicks, the user doesn't want more than one click. if (!moved) return true; moved = false; // Quick shortcut here if ((selectedRegion != null) && selectedRegion.inside(x, y)) { selectedRegion.select(); return true; } if (selectedRegion != null) { selectedRegion.mouseLeave(); selectedRegion = null; } Enumeration e = regions.elements(); while (e.hasMoreElements()) { ImageRegion r = (ImageRegion) e.nextElement(); if (r.inside(x, y)) { selectedRegion = r; r.mouseEnter(); r.select();

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (24 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

break; } } repaint(); return true; } }

You may have noticed that the implementations of the image regions were incredibly small and didn't really seem to do anything. You are correct on both counts. To get any benefit out of the regions, you have to create subclasses that actually do something. Suppose you want to create a map that has a set of circular hotspots that light up with the name of the city in that section of the map. You need to keep track of the name of the city and also implement a paint method that displays the city name. Because an image map isn't very useful if you can't select items, your city hotspot should also do something when you click it. Listing 7.12 shows a circular region that represents a city. When you click the region, it pops up an OK dialog box telling you which city you clicked.

Listing 7.12 Source Code for CityRegion.java import java.awt.*; // // // // // This class implements a special version of the ImageRegionCircle class to represent cities on a map. When the mouse gets within range of a city, the city name is displayed. When you click the city, it pops up a dialog box telling you what city you clicked.

public class CityRegion extends ImageRegionCircle { String name; public CityRegion() { } // You can specify either x,y to create a CityRegion or a Point public CityRegion(String name, int x, int y) { // Set up the region as a circle with a radius of 30 pixels super(new Point(x, y), 30); this.name = name; } // radius of 30

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (25 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

public CityRegion(String name, Point p) { // Set up the region as a circle with a radius of 30 pixels super(p, 30); // radius of 30 this.name = name; } // // // // // // Paint is called when the mouse is within this city's bounding area - for this class, defined as a circle of radius 30 We just draw the city's name in blue. Note that the graphics area is bounded by the bounding box for the region (actually, the smallest rectangle that will enclose the area because the regions can be non-rectangular). public void paint(Graphics g) { g.setColor(Color.blue); g.drawString(name, 0, 35); } // If you click a city, you'll get a dialog box public void select() { OKDialog.createOKDialog("You selected the city of "+name); } }

Now that all the pieces of the puzzle are in, you can create an the image map for displaying these cities. Listing 7.13 shows the CityApplet class.

Listing 7.13 Source Code for CityApplet.java import java.awt.*; import java.applet.*; // // // // // This applet demonstrates the use of the ImageMap class It loads a map of the U.S.A. and creates a set of regions for the map. The regions are implemented in the CityRegion class. The numbers for the city coordinates are approximate, and were determined through ocular analysis (I eyeballed the map).

public class CityApplet extends Applet { public void init() {

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (26 of 27) [8/14/02 10:52:50 PM]

Chapter 7 -- Creating Smarter Forms

// Load the map image Image usaImage = getImage(getDocumentBase(), "usa.gif"); // Be naughty and use the MediaTracker to make sure the map is loaded MediaTracker mt = new MediaTracker(this); mt.addImage(usaImage, 0); try { mt.waitForAll(); } catch (Exception ignore) { } // Create an image map object for the image ImageMap imageMap = new ImageMap(usaImage); // Add city regions to the image imageMap.addRegion(new imageMap.addRegion(new imageMap.addRegion(new imageMap.addRegion(new imageMap.addRegion(new imageMap.addRegion(new imageMap.addRegion(new imageMap.addRegion(new imageMap.addRegion(new map CityRegion("Atlanta", 323, 202)); CityRegion("New York", 377, 118)); CityRegion("L.A.", 45, 196)); CityRegion("San Fran", 34, 164)); CityRegion("Seattle", 52, 74)); CityRegion("Dallas", 218, 236)); CityRegion("Chicago", 277, 123)); CityRegion("Miami", 367, 270)); CityRegion("Denver", 102, 143));

// Add the image map to the applet add(imageMap); } }

Figure 7.5 shows the output from this applet. Figure 7.5 : Image maps in Java can implement hot spots.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch7.htm (27 of 27) [8/14/02 10:52:50 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-1.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-1.gif [8/14/02 10:52:52 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-2.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-2.gif [8/14/02 10:52:54 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-3.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-3.gif [8/14/02 10:52:55 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-4.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-4.gif [8/14/02 10:52:56 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-5.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f7-5.gif [8/14/02 10:52:58 PM]

Chapter 8 -- Reading and Writing Files from an Applet

Chapter 8 Reading and Writing Files from an Applet


by Mark Wutka

CONTENTS
G G

G G

Applets and Files Using the JFS Filesystem for Applets H Printing Files Using JFS H Accessing Other Web Servers from JFS Saving Files Using HTTP Post Storing and Retrieving Files with FTP H Sending FTP Commands H Establishing an FTP Session H Sending Simple FTP Commands H Establishing a Data Connection

Applets and Files


Because of security restrictions, an applet may not read or write files on the local system. This is to protect you from a malicious applet storing a virus on your computer or deleting all your files. Even if you could only read files on the local system, someone could snoop around on your computer and retrieve private information. Before you start exploring the various methods of storing files from an applet, ask yourself first whether you want your applet manipulating files directly. If you are designing a system where you have an applet presenting the user interface for an application running on a server, you should consider doing all the file access from the application. The more code you put on the applet, the longer it takes for it to start up. Even if you have designed your application this way, you may still have reasons to directly read and write files from the applet. For example, you might want to save configuration information for the user interface (colors, fonts). It wouldn't make sense to make this part of the main application, because separating the user interface from the application also means separating the application from the user interface. In other words, the application shouldn't know anything about the user interface, except how it interacts with the applet.

Using the JFS Filesystem for Applets


The JFS filesystem, by Jamie Cameron, is one of the most useful sets of Java classes to come along. It provides NFS-like file and print services for applets. You can get this wonderful product on the Internet at http://www.ncs.com.sg/java/jfs. JFS solves a number of pesky problems for applets. It provides a way to read and write files, create and delete files and
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (1 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

directories, print files, and open URLs and socket connections to hosts other than the ones the applets came from. JFS isn't some sort of cheap hack around Java's security model; it is a full-featured server system. This means, of course, that you have to run the JFS server, which is written in Java, to use JFS. The JFSclient class is the applet's interface to the JFS server. You create an instance of JFSclient by passing the host name of the JFS server to the constructor. If you are doing this from an applet, the host must be the host that the applet was loaded from, which means your Web server has to run JFS. Once you create a JFSclient, you must send a user name and password to the JFS server. JFS has its own set of user names and passwords; these are not the operating system's user names. This is quite important because the applet must contain the user name and password in order to perform the logon. Anyone with evil intentions and a little patience can find out the user name and password that the applet sends. If these were logon IDs for your Web server, it would be simple for someone to log on to your Web server and wreak all kinds of havoc. If you forget to send the authentication information, the other methods in the JFSclient will simply hang, which may not be quite the result you were looking for. Listing 8.1 shows a very simple example that retrieves a file stored in the JFS file system.

Listing 8.1 Source Code for JFSGet.java import java.applet.*; // This program demonstrates the use of the JFSclient // class to fetch a file. public class JFSGet extends Object { public static void main(String args[]) { try { // Create a JFS client to host 192.0.0.3 JFSclient jfs = new JFSclient("192.0.0.3"); // Log on as root, with no password jfs.auth("root", ""); // Fetch the file called "volcano" byte[] volcfile = jfs.get("/home/root/volcano", 0); // Dump it to the screen for (int i=0; i < volcfile.length; i++) { System.out.print((char)volcfile[i]); } System.out.println(); } catch (Exception e) { e.printStackTrace(); } } }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (2 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

The JFSGet program is very straightforward. It creates a JFSclient object that is connected to a JFS server whose IP address is 192.0.0.3, then it authenticates itself using the name root with no password. Next, it uses the get method to retrieve a file as an array of bytes. Finally, it loops through the array of bytes and prints them to the System.out. Caution This example does not use an authentication password. In practice, you should always use a password in JFS authentication. Otherwise, you may open your system up to possible corruption from other people on the Internet.

Printing Files Using JFS


Under most of the current browser implementations, you may not print Java applets within a Web page. You will usually find an unfortunate blank space on your printout where the applet should be. There aren't even any methods or classes within Java to perform printing functions. While JFS can't quite deal with rendering the output from your applet on a printer, it does allow you to send text data to a printer. If you have a PostScript printer, you could sent PostScript codes to the printer and get graphical output. Printer access in JFS is done through a special device called "/dev/Printer." This one device file represents all the printers on your system. You choose the printer when you send a message to this device. Unlike UNIX, in which you use the same functions to write to devices or files, JFS uses special methods named devput and devget to communicate with the special devices. When you want to send data to a printer, you can pass the printer name to the device driver using the devput method. The following code fragment sends an array of bytes to a printer named myprinter: JFSclient myClient = new JFSClient(getDocumentBase().getHost()); byte databytes[] = (some way of getting data bytes) Message deviceInfo = new Message(); deviceInfo.add("Printer", "myprinter"); myClient.devput("/dev/Printer", databytes, deviceInfo); The printers are defined in a file called /etc/printers within the JFS filesystem. If you are running UNIX, you don't need to be concerned about all these /dev and /etc files. JFS maintains its own filesystem structure, the root of which can be in any directory on the system. What JFS calls /etc/printers may really be /home/mark/jfs/root/etc/printers. The /etc/printers file contains multiple lines that each contain four fields separated by colons: printer name:printer type:printer description:print command The default /etc/printers that comes with JFS contains the single line: default:Postscript:The Default Printer:lpr

Accessing Other Web Servers from JFS


One of the other problems that JFS solves for applets is the nasty restriction of not being able to access any other servers other than the one the applet came from. The /dev/Web device in JFS is a URL redirection device that enables you to retrieve data from any URL on the Net. Use the devget method to tell the /dev/Web device where to get the data from:

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (3 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

JFSclient myClient = new JFSClient(getDocumentBase().getHost()); Message deviceInfo = new Message(); deviceInfo.add("URL", "http://www.mcp.com"); Message response = myClient.devget("/dev/Web", deviceInfo); byte[] responseData = response.getdata();

Saving Files Using HTTP Post


If you don't want the overhead of JFS, you have some alternatives. You can take advantage of the existing classes that are able to post data to a Web server. Remember that when you post data to a Web server, you are essentially sending it a file. The only thing you have to do is create something to take the posted data and store it in a file. This turns out to be a trivial task under UNIX. The following shell script enables you to store data in a file: #!/bin/sh echo "Content-type: text/plain" echo dd ibs=1 count=$CONTENT_LENGTH of=$QUERY_STRING 2>/dev/null echo $? This script probably requires a bit of explanation. First of all, the filename that you are storing into is encoded in the URL and not in the posted data. If you were to call this script putfile and put it in the cgi-bin directory of your Web server, the following URL would try to post to a file called putme: http://mywebhost/cgi-bin/putfile?putme The shell script kicks into action by first printing response data that is required by every CGI program, namely the "Contexttype" information. Next, it prints a blank line, signifying the end of the header information. Now comes the key to this scriptthe dd command. This command is similar to the cat command, except that it has the ability to read and write a fixed number of records. The ibs=1 option tells dd to use an input block size of 1 byte, meaning that the count option will tell how many bytes to read. Because the http server stores the number of bytes you posted in an environment variable called CONTENT_LENGTH, you use this variable as the count parameter for dd. Finally, the of parameter is the name of the output file you are writing to. Any error messages are sent to /dev/null, but the numeric exit code is printed as the final line of the response generated by the script. If the dd command is successful, the echo $? line will echo a 0. Listing 8.2 shows a class that uses the POST command to send data to this script.

Listing 8.2 Source Code for PostPutFile.java import java.net.*; import java.io.*; // // // // This class provides a static method to post a file to the putfile script, which takes a filename as a parameter passed in the POST request itself, and then receives the bytes as the posted data.

public class PostPutFile extends Object {


file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (4 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

// // // // //

Put sends the named file to a specific URL. The URL should contain the path name of the putfile script. This method will append the ?filename to the script name. It returns 0 if the put was successful, or a non-zero number if it failed for some reason.

public static int put(URL url, String filename, byte[] bytes) throws IOException, MalformedURLException { // Run the putfile script and ask it to store the data in a file called "putme" URL destURL = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()+"?"+filename); // Define the data that you want stored in the file. URLConnection urlConn = destURL.openConnection(); urlConn.setDoOutput(true); // we need to write urlConn.setDoInput(true); // just to be safe... urlConn.setUseCaches(false); // get info fresh from server // Tell the server what kind of data we are sending - in this case, // just a stream of bytes. urlConn.setRequestProperty("Content-type", "application/octet-stream"); // Must tell the server the size of the data we are sending. This also // tells the URLConnection class that we are doing a POST instead // of a GET. urlConn.setRequestProperty("Content-length", ""+bytes.length); // Open an output stream so we can send the info we are posting OutputStream outStream = urlConn.getOutputStream(); // Write out the actual request data outStream.write(bytes); outStream.close(); // Now that we have sent the data, open up an input stream and get // the response back from the server DataInputStream inStream = new DataInputStream( urlConn.getInputStream()); String line = inStream.readLine(); inStream.close();

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (5 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

try { int result = Integer.valueOf(line).intValue(); return result; } catch (Exception parseError) { return -1; } } }

Listing 8.3 shows a simple example applet that stores a file using the PostPutFile class.

Listing 8.3 Source Code for TestPutFile.java import java.net.*; import java.applet.*; public class TestPutFile extends Applet { public void init() { try { URL destURL = new URL(getDocumentBase(), "/cgi-bin/putfile"); // Define a string we want to send String dataToSend = "This is a string that I want \n"+ "to store in the file.\n"; // The PostPutFile class wants a byte array, however, so we convert // the string to a byte array. byte[] bytes = new byte[dataToSend.length()]; dataToSend.getBytes(0, dataToSend.length(), bytes, 0); PostPutFile.put(destURL, "/home/mark/putme", bytes); } catch (Exception e) { e.printStackTrace(); } } }

Note

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (6 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

The new version of HTTP (HTTP 1.1) includes a PUT command that allows you to store a file without creating a separate CGI program to save the file. Some HTTP servers already support this new option. If you have a server that supports PUT and you want to save files from Java, you won't be able to use the URL class to send the file (until the URL class supports POST). You can, however, use the PostSockURL class from Chapter 6 "Communicating with a Web Server," with a little modification (change POST to PUT when it sends the HTTP command).

Storing and Retrieving Files with FTP


As its name implies, the File Transfer Protocol is useful for sending files back and forth between an applet and a server. You just need to make sure that your server supports FTP. If you are running UNIX or Windows NT, it should come with the operating system. The FTP protocol is defined in Internet RFC 959. The FTP protocol requires you to use two different connections between client and server. The control connection is used by the client to send commands to the server. The server sends responses over the control connection. Whenever the client or server needs to send a large block of data, a data connection must be established. Figure 8.1 illustrates the connections between a client and an FTP server. Figure 8.1 : A client uses two different connections to communicate with an FTP server.

Sending FTP Commands


The control connection used in FTP is an ASCII, line-oriented connection, similar to SMTP (Simple Mail Transfer Protocol). A client sends a command to the server as a line terminated by carriage. The response from the server is one or more ASCII lines containing a 3-digit return code and a text response. For example, when you send the USER command to tell the FTP server that you want to log in as mark, you send: USER mark The FTP server would respond with a line like: 331 Password required for mark The response codes from FTP are grouped into five categories, based on the first digit in the response code:
G

G G

1xx means that the command has been started successfully, and is in the process of running. You will get another response when the command completes. When the server starts transmitting a file it sends a 1xx response, and then sends a 2xx response when the file has been sent. 2xx indicates that the command has been completed successfully. 3xx is sent when the server accepts your command, but needs more information from you in order to proceed. This often occurs when you send a USER command and the system wants you to send a password. 4xx means that the command cannot be completed due to some temporary problem. If you send the same command again, it may be accepted. 5xx indicates that the command cannot be completed. If you try the same command again, it will be rejected again.

FTP responses can span more than one line. Whenever the server sends a multiline response, each line begins with the
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (7 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

response code followed by a dash (-). The last line of the response does not contain a dash. All you have to do when reading responses is look for a dash as the fourth character. If there is a dash, you need to read another line. Listing 8.4 shows the doCommand and getResponse methods from the FTPSession class which are included on the CD for this book. These methods are responsible for sending commands and receiving responses. The getResponse method checks for a dash to see if the response is a multiline response. You could use these same methods for other Internet protocols that use this same request-response format, like SMTP. Note The FTPSession class uses DataInputStream and DataOutputStream filters on top of the normal socket input and output streams. This allows FTPSession to send and receive whole lines of data rather than reading and writing one character at a time.

Listing 8.4 doCommand and getResponse Methods from FTPSession.java // Send a command and wait for a response public String doCommand(String commandString) throws IOException { outStream.writeBytes(commandString+"\n"); String response = getResponse(); return response; } // Get a response back from the server. Handles multi-line responses // and returns them as part of the string. public String getResponse() throws IOException { String response = ""; for (;;) { String line = inStream.readLine(); if (line == null) { throw new IOException( "Bad response from server."); } // FTP response lines should at the very least have a 3-digit number if (line.length() < 3) { throw new IOException( "Bad response from server."); } response += line + "\n";
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (8 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

// If there isn't a '-' immediately after the number, we've gotten the // complete response. ('-' is the continuation character for FTP responses) if ((line.length() == 3) || (line.charAt(3) != '-')) return response; } }

Tip If you already had a class that implemented the SMTP protocol, you might consider moving the methods for sending and receiving commands into a new superclass for the SMTP class. Then the FTP and SMTP classes would be subclasses of this new class. This kind of situation occurs often in object-oriented programming. You discover that there are parts of a class that can be used by other classes, so you split out the reusable parts into a separate class. Obviously, it would have been better if you could have anticipated that the parts would need to be reused, but you don't always realize these things ahead of time.

Establishing an FTP Session


The first step in establishing an FTP session is creating the connection to the FTP server. FTP connections are normal TCP socket connections. The default FTP port is 21, but you should allow for other port numbers, since you may be running a special version of FTP for another reason. Once you have established a connection, the FTP server will send you a response, which is really just a greeting from the server. It is very important that you be prepared to read this response once you have connected to the server. If you don't read the response and just go on and send a USER command to begin your login, you will be confused and think that the greeting is the response from the USER command, and you will probably remain confused for the rest of the session. The greeting usually looks something like this: 220 flamingo FTP server (Version wu-2.4(1) Wed May 10 21:00:32 CDT 1995) ready Once you connect to the server, you have to log on. At the minimum, you must send a USER command. This command is in the format: USER username For example, for you to sign on as mark, you would use the command line: USER mark Since most user names on an FTP server have a password (if they don't, there's a security risk), the server will most likely respond with a command like: 331 Password required for mark
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (9 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

You would then be required to send a password with the PASS command: PASS password If mark's password is Shh!!!!!, the appropriate PASS command is: PASS Shh!!!!! The response to the PASS command is usually something like this: 230 User mark logged in. The FTP protocol allows for a third login parameter called the account, which is sent using the ACCT command. If you get a response with a response code of 332 (need account for login) after sending the PASS command, you need to send an ACCT command: ACCT account The account parameter is rarely used on UNIX systems, and is not restricted to the login sequence. You could receive a 332 response code for any operation, meaning that you must supply an account parameter when performing that operation. For instance, your server may password-protect files, and could require you to send the password to a file with the ACCT command before you can retrieve the file.

Sending Simple FTP Commands


The FTP protocol supports many useful commands that allow you to perform most important file operations. Some of the simple FTP commands are shown in Table 8.1. Table 8.1 Some Common FTP Commands Command CWD directory CDUP DELE filename MKD directory RMD directory RNFR old-filename RNTO new-filename Function Changes the working directory Changes directory to the parent of the current directory (like cd .. in UNIX or Windows) Deletes a file Creates a new directory Deletes a directory Renames old-filename (must be followed by RNTO, which gives the new name) Sets the new name of a file being renamed by a RNFR command

A simple command is one that does not require a data connection. Some FTP commands require you to set up a second connection, either to send raw data to the server, or receive raw data from the server. Table 8.2 shows you the commands that require a data connection.
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (10 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

Table 8.2 FTP Commands that Require a Data Connection Command LIST LIST directory RETR filename STOR filename Function Gets a list of all the files in the current directory Gets a list of all the files in a specific directory Retrieves a file from the FTP server Sends a file to the FTP server

Establishing a Data Connection


Whenever an FTP server needs to transfer raw data to or from a client, it uses a separate data connection for the transfer. Normally, the client sets up a listen socket (in Java, a ServerSocket) to accept an incoming connection from the FTP server. The client then sends the host address and port number of the listen socket to the server using the PORT command. The format of the PORT command is: PORT h1,h2,h3,h4,p1,p2 The h1-h4 parameters are the individual bytes in the client's host address. If the client's host address was 192.0.0.1, the bytes would be 192,0,0,1. The p1 and p2 parameters are the upper and lower bytes of the listen socket's port number. The following code fragment extracts p1 and p2 from a port number: int p1 = (portNumber >> 8) & 0xff; int p2 = portNumber & 0xff; An example PORT command for a host with an address is 192.0.0.3 and a port number of 1234 is: PORT 192,0,0,3,4,210 You can verify that the port bytes of 4 and 210 are indeed port 1234 by computing (p1*256) + p2, or (4*256) + 210. Once you send the PORT command, you can issue a command that requires a data connection like RETR, STOR, or LIST. Once you issue a command that requires a data connection, the server will connect to the listen socket and either send or receive data, depending on the command. Figure 8.2 shows the typical interaction sequence between an FTP server and a client performing a RETR command. Figure 8.2 : A server connects to a client to establish a data connection. The normal method of data connection will not work for Java applet because an applet may not accept an incoming socket connection. Fortunately, the FTP protocol gives you another option for establishing a data connection-the PASV command. The PASV command is similar to the PORT command, except that it causes the server to create the listen socket. The response from the PASV command gives the host address and port number for the listen socket in the same h1,h2,h3,h4,p1,p2 format as used in the PORT command. Here is an example PASV command and the response from the server: PASV

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (11 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

227 Entering Passive Mode (127,0,0,1,6,114) Once the server returns the response, the client can establish the data connection. Figure 8.3 shows the typical interaction sequence between an FTP server and a client performing a STOR command, using PASV to set up the data connection. Figure 8.3 : The PASV command forces the server to create the listen socket for the data connection. Listing 8.5 shows the doPasvPort method from the FTPSession class. It sends a PASV command, parses the response, and then establishes a socket connection with the server.

Listing 8.5 doPasvPort Method from FTPSession.java protected synchronized Socket doPasvPort() throws IOException { // Send the PASV command String response = doCommand("PASV"); // If it wasn't in the 200s, there was an error if (response.charAt(0) != '2') { throw new IOException(response); } // The pasv response looks like: // 227 Entering Passive Mode (127,0,0,1,4,160) // We'll look for the ()'s at the end first int parenStart = response.lastIndexOf('('); int parenEnd = response.lastIndexOf(')'); // Make sure they're both there and that the ) comes after the ( if ((parenStart < 0) || (parenEnd < 0) || (parenStart >= parenEnd)) { throw new IOException("PASV response format error"); } // Extract the address bytes String pasvAddr = response.substring(parenStart+1, parenEnd); // Create a tokenizer to parse the bytes StringTokenizer tokenizer = new StringTokenizer(pasvAddr, ","); // Create the array to store the bytes int[] addrValues = new int[6]; // Parse each byte for (int i=0; (i < 6) && tokenizer.hasMoreTokens(); i++) { try {
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (12 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

addrValues[i] = Integer.valueOf( tokenizer.nextToken()).intValue(); } catch (Exception e) { throw new IOException( "PASV response format error"); } } // We ignore the host addresses, assuming that the host address is // the same as the host address we used to connect the first time. Socket newSock = new Socket(host, (addrValues[4] << 8) + addrValues[5]); return newSock; }

Listing 8.6 shows the put method from the FTPSession class. It uses the doPasvPort command to set up a data connection, then sends a STOR command to the FTP server. The STOR command should return a response code in the 100199 range, indicating that the STOR may proceed. When you finish sending the file to the FTP server, you must close down the data connection. This tells the server that you have finished. You should then receive another response from the server over the command connection, which should have a response code in the 200-299 range.

Listing 8.6 put Method from FTPSession.java public synchronized void put(String remoteFile, byte[] data, boolean doBinary) throws IOException { // If transferring in binary mode, send a type command for type I (IMAGE) if (doBinary) { String response = doCommand("TYPE I"); if (response.charAt(0) != '2') { throw new IOException(response); } // If transferring in ASCII mode, send a type command for type A (ASCII) } else { String response = doCommand("TYPE A"); if (response.charAt(0) != '2') { throw new IOException(response); } } // Open up a data connection Socket putSock = doPasvPort();

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (13 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

// Tell the server where we want it to store the data we are sending String response = doCommand("STOR "+remoteFile); // If the request is successful, the server should send a response // in the 100s and then start receiving the bytes. Once the data // connection is closed, it should send a response in the 200s. if (response.charAt(0) != '1') { putSock.close(); throw new IOException(response); } // If binary mode, just write all the bytes if (doBinary) { OutputStream out = putSock.getOutputStream(); out.write(data); // If ASCII mode, write the data a line at a time } else { DataInputStream in = new DataInputStream( new ByteArrayInputStream(data)); DataOutputStream out = new DataOutputStream( putSock.getOutputStream()); String line; while ((line = in.readLine()) != null) { out.writeBytes(line+"\r"); } } putSock.close(); response = getResponse(); // Make sure we got a 200 response if (response.charAt(0) != '2') { throw new IOException(response); } }

The FTPSession class is quite simple to use. You just create an instance of FTPSession by passing the destination host name, the user name, and the password to the constructor, and then using the get and put methods to retrieve and send files, respectively. Listing 8.7 shows an example applet that copies a file by retrieving it and then storing it under a new name.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (14 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

Listing 8.7 Source Code for TryFTP.java import java.applet.*; import java.io.*; // This applet demonstrates the use of the FTPSession class. // It copies a file called "volcano" to a file called "vol.ftp" // by fetching the file and then storing it with a new name. public class TryFTP extends Applet { public void init() { try { // Create the session to host 192.0.0.3, using a user name of anonymous // and a password of mark@localhost FTPSession sess = new FTPSession( "192.0.0.3", "anonymous", "mark@localhost"); // Fetch the file byte[] file = sess.get("/home/mark/volcano", true); // Store the file sess.put("/home/mark/vol.ftp", file, true); } catch (Exception e) { e.printStackTrace(); } } }

Caution Be extremely careful when using the FTPSession class with respect to the user name and password. Even though your applet is compiled, it is fairly trivial to look through the code and find the user name and password that are sent. You should either use anonymous FTP, or set up a user account that is not allowed to log on to your system and is allowed to use only FTP. Otherwise, you are broadcasting a free user account all over the Internet whenever you put your applet out there.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (15 of 16) [8/14/02 10:53:03 PM]

Chapter 8 -- Reading and Writing Files from an Applet

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch8.htm (16 of 16) [8/14/02 10:53:03 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f8-1.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f8-1.gif [8/14/02 10:53:04 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f8-2.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f8-2.gif [8/14/02 10:53:05 PM]

Chapter 9 -- Creating Reusable Graphics Components

Chapter 9 Creating Reusable Graphics Components


by Mark Wutka

CONTENTS
G G

Reusable Components The Command Pattern H Invoking Commands from a Menu Creating a Reusable Image Button H Setting the Size of a Canvas H Handling Input Events H Painting the Canvas H Watching for Image Updates H Creating a CommandImageButton Using the Observer Interface H The Model-View-Controller Paradigm H Observables and the Model-View-Controller Paradigm Using Observables for Other Classes

Reusable Components
Amid all the excitement and debate over Java's crossplatform abilities, Java's features as an object-oriented programming language tend to get lost. Java falls somewhere between C++ and Smalltalk on the object-oriented scale. The general structure of Java's classes are similar to C++, but it adds a few more capabilities that are closer to Smalltalk. For example, the interface mechanism allows you to invoke methods in an object without knowing the object's class hierarchy. In C++, you must have a reference to the object's class or one of its superclasses to invoke a method. Smalltalk, of course, allows you the ultimate freedom of invoking any method in any object. This gives Smalltalk GUI designers a huge advantage in creating reusable components. In Smalltalk, when you add a button to your application, you can tell the button to invoke a specific method in a specific object whenever the button is pressed. If you were allowed to do this in Java, it would look something like this: public class MyClass extends Applet { public void handleButtonPress() { // code to handle a button being pressed

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (1 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

} public void init() { Button myButton = new Button("Press Me", this, handleButtonPress); } } Unfortunately, you can't do this in Java because it doesn't support pointers or references to functions. You can't even do it effectively in C++, and it supports function pointers. This problem is solved in C++ by something called a functor, also known as the Command pattern in design pattern lingo.

The Command Pattern


The Command pattern addresses the problem of invoking arbitrary methods in an object. The basic mechanism of the Command pattern is that you create small objects that implement a specific interface-each object is responsible for invoking a different method. Suppose you want to create buttons for your applet and have each button invoke a different method in your applet. You could create your own action method in the applet and handle the action events for the buttons, but that would turn out to be too much work in a larger applet. Instead, you define an interface like the one shown in Listing 9.1:

Listing 9.1 Source Code for Command.java public interface Command { public void doCommand(); }

Now, to be able to use this interface with a button, you need a subclass of Button that invokes doCommand whenever the button is pressed. Listing 9.2 shows an implementation of a CommandButton object that does this.

Listing 9.2 Source Code for CommandButton.java import java.awt.Button; import java.awt.Event; // This class implements a Button that supports the // Command interface. When the button is pressed, it // invokes the doCommand method in the Command interface. public class CommandButton extends Button
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (2 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

{ // The interface where we will invoke doCommand protected Command buttonCommand; // It's always polite to implement the empty constructor if // you can get away with it. public CommandButton() { } // Allow a CommandButton with a command but no label public CommandButton(Command command) { buttonCommand = command; } // Allow a CommandButton to use the typical Button constructor public CommandButton(String label) { super(label); } // The most useful constructor allows a label and a command public CommandButton(String label, Command command) { super(label); buttonCommand = command; } // When we get an action event, invoke doCommand in buttonCommand public boolean action(Event evt, Object which) { // Make sure the action event is for this object if (evt.target != this) return false; // Make sure we have a buttonCommand defined! if (buttonCommand == null) return false; buttonCommand.doCommand(); return true; } // Since you can create a CommandButton without passing it a // Command interface, you need to be able to set the command later.
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (3 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

public void setCommand(Command command) { buttonCommand = command; } }

Now, suppose you want to pass parameters when the command is invoked. For example, suppose you want to assign numbers to buttons and pass the number as part of the command. You do not need to change the Command interface or the CommandButton class for this. The key to the Command pattern is the creation of small command objects that invoke the real methods. Listing 9.3 shows a small command object implementation whose doCommand method turns around and invokes changeNumber in a NumberApplet object.

Listing 9.3 Source Code for ChangeNumberCommand.java // This class implements a simple command object // that invokes a method called changeNumber in a // NumberApplet object whenever doCommand is called public class ChangeNumberCommand extends Object implements Command { protected NumberApplet applet; protected int number; public ChangeNumberCommand(NumberApplet applet, int number) { this.applet = applet; this.number = number; } public void doCommand() { applet.changeNumber(number); } }

The ChangeNumberCommand object illustrates the key feature of the Command pattern.It acts as an intermediary between the CommandButton and the NumberApplet. The CommandButton says doCommand(), the NumberApplet wants to hear changeNumber(5), the ChangeNumberCommand object performs the translation. Listing 9.4 shows the implementation of the NumberApplet class.

Listing 9.4 Source Code for NumberApplet.java

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (4 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

import java.applet.Applet; import java.awt.Label; // // // // // This applet displays a label containing a number, followed by three buttons which change the number. It uses the ChangeNumberCommand to translate the doCommand method in the CommandButton to the changeNumber method in this object.

public class NumberApplet extends Applet { Label number; public void init() { // Start the label out at 0 number = new Label("0"); add(number); // Create the object to change the label to 1 add(new CommandButton("1", new ChangeNumberCommand(this, 1))); // Create the object to change the label to 2 add(new CommandButton("2", new ChangeNumberCommand(this, 2))); // Create the object to change the label to 3 add(new CommandButton("3", new ChangeNumberCommand(this, 3))); } // changeNumber actually performs the change public void changeNumber(int newNumber) { number.setText(""+newNumber); } }

Figure 9.1 shows the NumberApplet applet in action. Figure 9.1 : The Command pattern makes it easy to use components without subclassing them. You can also cascade commands, creating command objects that translate from one command into another. For example, the ChangeNumberCommand object invokes a changeNumber method in a NumberApplet that takes a number. It is a shame that the object is restricted to NumberApplets. You will probably have many situations where you want to do a numeric command-a command that takes a number. You should go ahead and define a NumberCommand interface to handle such situations, as shown in Listing 9.5:
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (5 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

Listing 9.5 Source Code for NumberCommand.java public interface NumberCommand { public void doCommand(int number); }

Now, because you have already seen an example where you want to take an object that invokes a regular Command and turn that into a NumberCommand, you can feel pretty confident that an object that converts from one to the other will get a lot of use. Listing 9.6 shows the class to do this.

Listing 9.6 Source Code for CommandToNumberCommand.java // This class translates a Command.doCommand() method into // a NumberCommand.doCommand(int) method. public class CommandToNumberCommand extends Object implements Command { protected NumberCommand numberCommand; protected int number; public CommandToNumberCommand(NumberCommand command, int number) { this.numberCommand = command; this.number = number; } public void doCommand() { numberCommand.doCommand(number); } }

You will soon end up with a library of commands and command conversions, such as StringCommand, StringCommandToNumberCommand, BooleanCommandToStringCommand, etc. You'll also have new components that invoke these commands, like CommandTextField, which would invoke a doCommand method in a StringCommand object. These objects take only minutes to write, but they can save you hours of coding. Figure 9.2 illustrates how some of these objects might be connected. Figure 9.2 : Different command objects can be linked together like building blocks.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (6 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

Invoking Commands from a Menu


Buttons and menu items are very similar in their usage. In fact, you can think of a menu as a hierarchy of buttons. Both of the components represent an action without any parameters. Components like a choice or an option button have an additional parameter associated with them. For example, with a choice, the parameter is the new item chosen. For an option button, the parameter is whether the button is now selected or deselected. Because of the similarity between Buttons and MenuItems, you can use the same Command interface for both menu items and buttons. This means that you can create buttons that are shortcuts for menu items, both sharing the same instance of a Command object. Listing 9.7 shows an implementation of a CommandMenuItem.

Listing 9.7 Source Code for CommandMenuItem.java import java.awt.*; // This is a menu item that supports the command // interface. Whenever an ACTION_EVENT is posted // to it, it invokes the doCommand method. public class CommandMenuItem extends MenuItem { // The Command interface to invoke protected Command whichCommand; public CommandMenuItem(String label, Command whichCommand) { super(label); this.whichCommand = whichCommand; } public boolean postEvent(Event evt) { // If we get an ACTION_EVENT event, call doCommand if (evt.id == Event.ACTION_EVENT) { whichCommand.doCommand(); return true; } // Otherwise, let the super class handle the postEvent return super.postEvent(evt); } }

Creating a Reusable Image Button


file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (7 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

The AWT Button class does not allow you to display an image in the button, only text. This is really annoying for people who would like to make toolbars and other useful GUI components. You can implement an image button pretty easily by creating a subclass of Canvas. When creating a custom component using the Canvas class, there are basically two things you have to do-draw the component and handle input events. For an image button, you also have the added burden of waiting for the image to be downloaded.

Setting the Size of a Canvas


The ImageButton class, included on the CD with this book, implements an image button as a subclass of Canvas. It does not require that the image be completely downloaded before the button can be displayed. Because you don't always know what size a component needs to be, thanks to the wonder of layout managers, your image button class needs to be flexible enough to fit into any layout. You have a choice of keeping the image the same size all the time and putting some kind of border around the image, or of resizing the image itself to match the new button size. Note Whenever you create your own subclass of Canvas, you must set the canvas size either by calling the resize method or by implementing your own minimumSize and preferredSize methods.

The ImageButton class computes its preferred size from the size of the image. The minimum size is the size of the button when there is no image (4x4, which leaves room for the shadowing effects). The ImageButtonClass also implements its own size method. The size method is used when the button size is fixed. In other words, you can specify a fixed size for the button that will never be changed by the layout manager. Listing 9.8 shows the sizing methods for the ImageButton class.

Listing 9.8 Image Sizing Methods from ImageButton.java // The minimum size is the amount of space for the shading around the // edges, plus one pixel for the image itself. public Dimension minimumSize() { return new Dimension(4, 4); } // We'd prefer to have just enough space for the shading (shading takes // 3 pixels in each direction) plus the size of the image. public Dimension preferredSize() { return new Dimension(buttonImage.getWidth(this)+3, buttonImage.getHeight(this)+3); }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (8 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

public Dimension size() { // If the sized isn't fixed, just say super-size it! (har har) if (!fixedSize) return super.size(); return preferredSize(); }

Caution If an image hasn't been downloaded yet, the getWidth and getHeight methods will return -1. This can cause unpleasant exceptions if you use these values to set the width or height of a canvas. The ImageButton class does not have that problem because it leaves space on either size of the image. If the image hasn't been downloaded, the preferredSize method will return a dimension of 22. If it hadn't added 3 to both width and height, it would be returning a dimension of -1-1, which would surely cause an error.

Handling Input Events


The ImageButton class is concerned only with mouse movements. It needs to know when someone selects the button. You might think that you would only need to look for a MOUSE_DOWN event, but it is not that simple. On most systems, if you hold the mouse down and then move the mouse away from the button before releasing the mouse, the button pops up as if you had never clicked it. You often use this functionality if you start to click the wrong button. The ImageButton class needs to handle that case. In order to allow you to move the mouse away from the button while it is down, you can't trigger the button's action on a mouse click. Instead, you must trigger it when the mouse is released. That way, you need to have the mouse pointer on the button, click the mouse, and release it before you actually perform the button's action. If you were to only implement the mouseDown and mouseUp events, you'd still have one problem. You need to make the button pop back up when the mouse leaves the button area. This means that the ImageButton class must also implement the mouseExit method. If it did not implement this method, whenever you moved the mouse away from the button, the button would appear to be stuck in the down position. It would be really nice if you could watch for the mouse entering the button's area and have the button notice whether the mouse was up or down and adjust accordingly. Unfortunately, the MOUSE_ENTER and MOUSE_EXIT events do not give any indication of whether the mouse is up or down. The reason this is nice is that most buttons will automatically pop down if you move the mouse over them while you are holding the mouse button down. If you try this with the image button, you'll see that it does not do this. Listing 9.9 shows the mouse event handlers for the image button class. The mouse event handlers manipulate a Boolean variable called isDown, which keeps track of whether the button is currently up or down. This value is then used by the paint method to determine whether the button should be drawn up or down.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (9 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

Listing 9.9 Mouse Handling Methods from ImageButton.java // If we get a mouse click, make a note it and push the button down public boolean mouseDown(Event evt, int x, int y) { isDown = true; repaint(); return true; } // If we get mouseUp, see if we thought the mouse was down. If so, // the button has been clicked. Generate an action event so this button // behaves just like a real button. public boolean mouseUp(Event evt, int x, int y) { if (isDown) { Event newEvt = new Event(this, evt.when, Event.ACTION_EVENT, x, y, 0, 0, buttonImage); this.postEvent(newEvt); } isDown = false; repaint(); return true; } // If the mouse leaves the area, move the button up. public boolean mouseExit(Event evt, int x, int y) { if (isDown) { isDown = false; repaint(); } return true; }

Painting the Canvas


The paint method is the one method that you should always see in a subclass of Canvas. You may not handle mouse events or anything else, but you need to paint the canvas. Otherwise, you'll just get a blank space. For the image button, the painting is interesting. Most window systems use the same basic effect for creating 3-D effects. You imagine that there's a light shining from the upper-left corner of the screen. Anything on the screen that is raised (coming off the screen towards you) would catch the

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (10 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

light on its upper left side, and the lower left side would be in shadow. Anything that is lowered (going inside the screen, away from you) would catch light on the lower-right corner, and the upper-left would be in shade. Also, when a button is lowered, the image or text in the image is usually shifted a small amount to the right and down. Figure 9.3 shows the image button in the raised position. Notice that the top and left edges are lighter, while the bottom and right edges are darker. Figure 9.3 : By lightening the upper-left side of an image, and darkening the lower-right, you create a raised effect. Figure 9.4 shows the image button in the lowered position. Notice that the top and left edges are now darker and the bottom and right edges are lighter. Figure 9.4 : By darkening the upper-left side of an image, and lightening the lower-right, you create a lowered effect. Listing 9.10 shows the paint method for the ImageButton class. It draws the shading for the upper-left part of the button, then draws the image before adding the shading for the lower-right.

Listing 9.10 paint Method from ImageButton.java // paint displays the shading and the image public void paint(Graphics g) { Dimension currSize = size(); int width = currSize.width; int height = currSize.height; int imgHeight = buttonImage.getHeight(this); int imgWidth = buttonImage.getWidth(this); // Display the shading in the upper left. If the button is up, the // upperleft shading is white, otherwise it's black if (isDown) { g.setColor(Color.black); } else { g.setColor(Color.white); } g.drawLine(0, 0, width-1, 0); g.drawLine(0, 0, 0, height-1); // If the button is up, we draw the image starting at 1,1 int imgX = 1; int imgY = 1; // If the button is down, move the image right and down one pixel and // draw gray shading at 1,1

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (11 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

if (isDown) { g.setColor(Color.gray); g.drawLine(1, 1, width-2, 1); g.drawLine(1, 1, 1, height-2); imgX++; imgY++; // If the button is up, draw gray shading just inside the bottom right shading } else { g.setColor(Color.gray); g.drawLine(1, height-2, width-1, height-2); g.drawLine(width-2, 1, width-2, height-2); } // Compare the width of the button to the width of the image, if // the button is wider, move the image over to make sure it's centered. int xDiff = (width - 3 - imgWidth) / 2; if (xDiff > 0) imgX += xDiff; // Compare the height of the button to the height of the image, if // the button is taller, move the image down to make sure it's centered. int yDiff = (height - 3 - imgHeight) / 2; if (yDiff > 0) imgY += yDiff; g.drawImage(buttonImage, imgX, imgY, this); // Draw the bottom right shading. If the button is up, the shading is // black, otherwise it's white. if (isDown) { g.setColor(Color.white); } else { g.setColor(Color.black); } g.drawLine(1, height-1, width-1, height-1); g.drawLine(width-1, 1, width-1, height-1); }

Watching for Image Updates


Rather than requiring that images be completely loaded, the ImageButton class redraws itself when an image finishes loading. To do this, it implements the ImageObserver interface. If you only need to know that an image has finished downloading, you only need to examine the flags parameter to see if the ImageObserver.ALLBITS flag has been set. When the ImageButton class learns that the image has finished downloading, it resizes itself and calls repaint to

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (12 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

redraw itself with the completed image. Listing 9.11 shows the imageUpdate method for the ImageButton class.

Listing 9.11 imageUpdate Method from ImageButton.java public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height) { // If we have a complete image, resize the button and ask the parent // to recompute all the component positions. Good thing this only // gets called once! if ((flags & ImageObserver.ALLBITS) != 0) { resize(img.getWidth(this)+3, img.getHeight(this)+3); getParent().validate(); } // Let the canvas class handle any other information it was looking for return super.imageUpdate(img, flags, x, y, width, height); }

Tip Any time a component resizes itself, it should call the validate method in the parent container. This causes the container to reposition its components based on the updated size.

Creating a CommandImageButton
Now that you have created your own custom component, you can see how easily you can fit it into the Command interface scheme. You only need to create a subclass of ImageButton called CommandImageButton. Listing 9.12 shows you how to do this.

Listing 9.12 Source Code for CommandImageButton.java import java.awt.Event; import java.awt.Image; // This class implements a ImageButton that supports the // Command interface. When the button is pressed, it // invokes the doCommand method in the Command interface. public class CommandImageButton extends ImageButton
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (13 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

{ // The interface where we will invoke doCommand protected Command buttonCommand; // Allow a CommandButton to use the typical ImageButton constructor public CommandImageButton(Image image) { super(image); } // Allow a CommandButton to use the typical ImageButton constructor public CommandImageButton(Image image, boolean fixedSize) { super(image, fixedSize); } // The most useful constructor allows an Image and a command public CommandImageButton(Image image, Command command) { super(image); buttonCommand = command; } // The most useful constructor allows an Image and a command public CommandImageButton(Image image, boolean fixedSize, Command command) { super(image, fixedSize); buttonCommand = command; } // When we get an action event, invoke doCommand in buttonCommand public boolean action(Event evt, Object which) { // Make sure the action event is for this object if (evt.target != this) return false; // Make sure we have a buttonCommand defined! if (buttonCommand == null) return false; buttonCommand.doCommand(); return true; }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (14 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

// Since you can create a CommandImageButton without passing it a // Command interface, you need to be able to set the command later. public void setCommand(Command command) { buttonCommand = command; } }

Now you can use the CommandImageButton in all your applications where you were using the CommandButton. The number button application earlier in this chapter needs only a few changes. Listing 9.13 shows a version of the number applet that uses image buttons instead of regular buttons and also uses the CommandToNumberCommand object to convert from a Command to a NumberCommand interface.

Listing 9.13 Source Code for NumberApplet2.java import java.applet.Applet; import java.awt.Label; import java.awt.Color; // // // // // This applet displays a label containing a number, followed by three buttons which change the number. It uses the ChangeNumberCommand to translate the doCommand method in the CommandButton to the changeNumber method in this object.

public class NumberApplet2 extends Applet implements NumberCommand { Label number; public void init() { setBackground(Color.gray); // Start the label out at 0 number = new Label("0"); add(number); // Create the object to change the label to 1 add(new CommandImageButton( getImage(getDocumentBase(), "one.gif"), new CommandToNumberCommand(this, 1))); // Create the object to change the label to 2 add(new CommandImageButton( getImage(getDocumentBase(), "two.gif"),
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (15 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

new CommandToNumberCommand(this, 2))); // Create the object to change the label to 3 add(new CommandImageButton( getImage(getDocumentBase(), "three.gif"), new CommandToNumberCommand(this, 3))); } // changeNumber actually performs the change public void doCommand(int newNumber) { number.setText(""+newNumber); } }

Figure 9.5 shows the NumberApplet2 applet in action. Figure 9.5 : It is much easier to integrate new components when you use the Command pattern.

Using the Observer Interface


The Observer interface and Observable class implement another design pattern called the Observer pattern. While you may not have used these two classes yet, you have seen a similar system in the form of the ImageObserver interface used when dealing with images. The idea behind the Observer interface is that you may have an object that wants to know when another object changes. For instance, you may be displaying a car's speed and need to know when the speed changes. Rather than creating a loop that constantly asks the car how fast it's going, you simply ask the car to tell you when its speed changes. It's a simple mechanism, but it can have a huge impact on your design.

The Model-View-Controller Paradigm


The reason the Observer interface is so effective is that it uses the Model-View-Controller (MVC) paradigm. The idea behind MVC is that an application really consists of three things-a model, some views of the model, and some controllers. The model is the part of the application that contains the actual application logic. It does the database access, it computes numbers, it manipulates data structures. It is really the heart of your application. When we talk about separating the user interface from the application, the model is the application. If the model represents the application, then the view and controller represent the user interface. The user interface is conceptually split into input components and output components. A controller is an input component. It supplies information to the model. A view is an output component-it displays information from the model. MVC is not some brand new, untested theory-it is the way Smalltalk applications have been written for many years. Pay close attention to the interfaces between the model and your views and controllers. It is very easy to let a model perform things that should rightly be part of a view or a controller. For example, suppose you are doing an aircraft tracking system and you are getting flight positions from the FAA over a modem. These positions are in the form of a text message that contains the aircraft identification and current information about it. You would have some code reading these messages
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (16 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

from the modem and passing them to another routine that parses the information out of the message. It is very easy to think of the code that reads the messages as the controller, and put the parsing mechanism in the model. It is also wrong. The parsing routine is also part of the controller. The model should have absolutely no dependence on the external representation of information. This is an extremely important point, because it greatly affects the reusability of your code. You should be able to change input sources and change output formats without touching the model. In other words, the model deals with pure information that has no external meaning attached to it. Figure 9.6 shows the conceptual relationship between the model, the view, and the controller. It also shows how the aircraft tracking system fits into this model. Figure 9.6 : The model-view-controller paradigm is a good, object-oriented way of designing applications.

Observables and the Model-View-Controller Paradigm


The Observer interface and the Observable class make it easier for you to create views of data in the model by creating a notification system to let your view know when data in the model has changed. The mechanism is very simple. You create some object in your model that is a subclass of Observable. Anytime this class changes, it calls setChanged to flag itself as having changed, and then calls notifyObservers. The reason for the separation is that you may run through a series of checks in your model, any one of which might change the data. When you have finished, you call notifyObservers one time, rather than notifying the observers after every check. Whether to notify every time or periodically is a design decision. Listing 9.14 shows an example ObservableInt class which is an Observable version of an integer.

Listing 9.14 Source Code for ObservableInt.java import java.util.*; // // // // // // //

ObservableInt - an integer Observable This class implements the Observable mechanism for a simple int variable. You can set the value with setValue(int) and int getValue() returns the current value.

public class ObservableInt extends Observable { int value; // The value everyone wants to observe public ObservableInt() { value = 0; // By default, let value be 0 } public ObservableInt(int newValue) { value = newValue; // Allow value to be set when created
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (17 of 28) [8/14/02 10:53:12 PM]

Chapter 9 -- Creating Reusable Graphics Components

} public synchronized void setValue(int newValue) { // // Check to see that this call is REALLY changing the value // if (newValue != value) { value = newValue; // Mark this class as "changed" setChanged(); // Tell the observers about it, pass the new value as an Integer object // This saves the observers time because they don't have to ask what // the new value is. notifyObservers(new Integer(value)); } } public synchronized int getValue() { return value; } }

On the Observer side of things, you can create components that redisplay themselves when the Observable changes. Listing 9.15 shows an IntLabel class that observes an ObservableInt and displays its current value.

Listing 9.15 Source Code for IntLabel.java import java.awt.*; import java.util.*; // IntLabel - a Label that displays the value of // an ObservableInt. public class IntLabel extends Label implements Observer { private ObservableInt intValue; // The value we're observing public IntLabel(ObservableInt theInt) { intValue = theInt; // Tell intValue we're interested in it

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (18 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

intValue.addObserver(this); // Initialize the label to the current value of intValue setText(""+intValue.getValue()); } // Update will be called whenever intValue is changed, so just update // the label text. public void update(Observable obs, Object arg) { setText(((Integer) arg).toString()); } }

Listing 9.16 shows an IntTextField that allows you to change the value of an ObservableInt. It will also act as an Observer in case another object changes the value.

Listing 9.16 Source Code for IntTextField.java import java.awt.*; import java.util.*; // // // // // IntTextField - a TextField that reads in integer values and updates an Observable int with the new value. This class is both a "view" of the Observable int, since it displays its current value, and a "controller" since it updates the value.

public class IntTextField extends TextField implements Observer { private ObservableInt intValue; public IntTextField(ObservableInt theInt) { // Initialize the field to the current value, allow 3 input columns super(""+theInt.getValue(), 3); intValue = theInt; intValue.addObserver(this); // Express interest in value } // The action for the text field is called whenever someone presses "return" // We'll try to convert the string in the field to an integer, and if // successful, update the observable int.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (19 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

public boolean action(Event evt, Object whatAction) { Integer intStr; // to be converted from a string try { // The conversion can throw an exception intStr = new Integer(getText());

// If we get here, there was no exception, update the observable intValue.setValue(intStr.intValue()); } catch (Exception oops) { // We just ignore the exception } return true; } // The update action is called whenever the observable int's value changes. // We just update the text in the field with the new int value public void update(Observable obs, Object arg) { setText(((Integer)arg).toString()); } }

Putting these objects together in a working applet is trivial. Listing 9.17 shows an applet that demonstrates these objects.

Listing 9.17 Source Code for ObservableApplet.java import java.applet.*; // This class demonstrates the ObservableInt, IntTextField // and IntLabel classes. public class ObservableApplet extends Applet { public void init() { ObservableInt intValue = new ObservableInt(0); add(new IntTextField(intValue)); add(new IntLabel(intValue)); } }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (20 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

Using Observables for Other Classes


The Observer interface is useful in non-graphical applications, too. It allows you to create more modular software by reducing the direct interconnection of components. The Observer interface is sort of a one-way interaction. The Observers usually know what kind of object they're watching, but the Observables usually just know that there are Observers watching. Caution When you use the Observer interface, you gain a lot of flexibility, because objects no longer have to know what objects are watching them. An observed object just sends out a notification that it has changed. You now have a lot of objects that are acting completely on their own, responding to updates and performing their own tasks. You must pay close attention to your object model here. Make sure each object is only performing the tasks it is responsible for. Try to make each object's roles and responsibilities as clear as possible. Otherwise, debugging a system like this will be a nightmare.

Listing 9.18 shows an example Observable class that represents an aircraft for a flight tracking system. Notice that none of its instance variables are public. You need to be able to notice when a variable changes value. If your instance variables are all public, any object can come along and change the value without you being notified. By restricting all the variable manipulation to accessor functions (get/set functions), you maintain the ability to notice when a variable changes. Tip When you call notifyObservers, you may pass it an object that will be passed to the update method in all observers. You can use this object to pass specific information about the update. If you plan to handle multiple types of updates, you should create an event class, similar to the AWT's Event class, which tells the update method what kind of update it is and which objects are involved.

Listing 9.18 Source Code for Aircraft.java import java.util.*; // // // // // // // // // This class demonstrates an observable object with multiple values that can change. If an individual value is changed, it calls notifyObservers. You can also change the values in bulk using setAll. When you create observable classes like this, you can't have public variables if you need to know when those varables change. Otherwise, if altitude was public, anyone could say: aircraft.altitude = 10000;

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (21 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

// and no observers would be notified. public class Aircraft extends Observable { protected String id; protected protected protected protected double double double double latitude; longitude; altitude; speed;

public Aircraft(String id) { this.id = id; } public Aircraft(String id, double lat, double lon, double alt, double speed) { this.id = id; this.latitude = lat; this.longitude = lon; this.altitude = alt; this.speed = speed; } public String getID() { return id; } public double getAltitude() { return altitude; } public void setAltitude(double newAlt) { altitude = newAlt; setChanged(); notifyObservers(this); } public double getLatitude() { return latitude; } public void setLatitude(double newLat) { latitude = newLat; setChanged(); notifyObservers(this); } public double getLongitude() { return longitude; } public void setLongitude(double newLon) { longitude = newLon; setChanged(); notifyObservers(this);
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (22 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

} public double getSpeed() { return speed; } public void setSpeed(double newSpeed) { speed = newSpeed; setChanged(); notifyObservers(this); } public void setAll(double lat, double lon, double alt, double speed) { this.latitude = lat; this.longitude = lon; this.altitude = alt; this.speed = speed; setChanged(); notifyObservers(this); } }

Listing 9.19 shows an example module that watches an Aircraft for changes and prints a warning when the altitude is too high.

Listing 9.19 Source Code for AltitudeMonitor.java import java.util.*; // // // // // // This class demonstrates how you can add new features to an application without rewriting a lot of code. In this case, this is a module that monitors aircraft altitudes and prints out a warning if one gets too high. The aircraft class doesn't know anything about this class, their only interaction is through the Observer-Observable interface.

public class AltitudeMonitor extends Object implements Observer { double maxAltitude; public AltitudeMonitor(double maxAlt) { this.maxAltitude = maxAlt; } // Somewhere in your application you will have to add code

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (23 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

// to tell this object about new aircraft. public void addAircraft(Aircraft newAircraft) { newAircraft.addObserver(this); } public void update(Observable obs, Object arg) { // Make sure this update is for an aircraft if (!(obs instanceof Aircraft)) return; Aircraft ac = (Aircraft) obs; if (ac.getAltitude() > maxAltitude) { System.out.println("Warning! Aircraft too high!"); return; } } }

Figure 9.7 shows the relationship between an altitude monitor and an aircraft. Figure 9.7 : The altitude monitor registers itself as an observer of an aircraft and then watches the aircraft's altitude. One of the advantages of designing things this way is that AltitudeMonitor could be an add-on feature to your tracking system. You could create whole sets of monitors similar to this that your customer could pick from. Notice, however, there's still one little flaw here. When you add a new aircraft, you have to call addAircraft in the AircraftMonitor object. If you wanted to add new types of monitors, you'd have to call a similar method in the new monitor. This is not good. You want to be able to add a new monitor without adding even one line of code. You can do it, too! You can create an AircraftRegistry class that is an Observable. Its job in life is to notify its observers whenever a new aircraft is added. Instead of calling addAircraft for each different monitor you have in your system, you just call addAircraft in the registry, and it notifies its observers of the new aircraft. Listing 9.20 shows an implementation of an AircraftRegistry class. It is implemented as a singleton class, which means there is only one in the entire system. A singleton class is implemented by keeping a protected static pointer to the lone instance of the class. You also hide the constructor so no one can create their own instance. Then, you create a static method that returns the lone instance of the class, creating a new one if there wasn't one already.

Listing 9.20 Source Code for AircraftRegistry.java import java.util.*; // This class provides a way for aircraft monitors to find out
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (24 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

// // // //

about new aircraft. It is implemented as a singleton class, which means there is only one. Its constructor is protected, so you can't create a new AircraftRegistry manually. Any time you need the registry, you access it through: AircraftRegistry.instance()

public class AircraftRegistry extends Observable { // reference to the single instance of AircraftRegistry in the system protected static AircraftRegistry registry; protected AircraftRegistry() { } // Return the lone instance of this class. If there isn't one, create it. public synchronized static AircraftRegistry instance() { if (registry == null) { registry = new AircraftRegistry(); } return registry; } // When an aircraft is added to the system, notify all the interested parties public void addAircraft(Aircraft aircraft) { setChanged(); // Pass the new aircraft to the interested parties notifyObservers(aircraft); } }

Now, the AltitudeMonitor class no longer needs the AddAircraft method. Instead, its update method has to be smart enough to know whether the update came from an Aircraft or from the AircraftRegistry. Listing 9.21 shows the updated AltitudeMonitor class.

Listing 9.21 Source Code for AltitudeMonitor2.java import java.util.*; // This class demonstrates how you can add new features to an // application without rewriting a lot of code. In this case,
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (25 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

// // // // // //

this is a module that monitors aircraft altitudes and prints out a warning if one gets too high. The aircraft class doesn't know anything about this class, their only interaction is through the Observer-Observable interface. This class uses the AircraftRegistry to learn about new aircraft.

public class AltitudeMonitor2 extends Object implements Observer { double maxAltitude; public AltitudeMonitor2() { maxAltitude = 40000.0; AircraftRegistry.instance().addObserver(this); } public AltitudeMonitor2(double maxAlt) { this.maxAltitude = maxAlt; AircraftRegistry.instance().addObserver(this); } public void update(Observable obs, Object arg) { // See if this update is for an aircraft if (obs instanceof Aircraft) { Aircraft ac = (Aircraft) obs; if (ac.getAltitude() > maxAltitude) { System.out.println( "Warning! Aircraft too high!"); return; } // If this update is from the registry, it is telling us about // a new aircraft, so start observing the new aircraft } else if (obs instanceof AircraftRegistry) { Aircraft ac = (Aircraft) arg; ac.addObserver(this); } } }

This may seem like a lot of fuss to you, but it makes your software much more modular. The AircraftRegistry class provides just the extra level of abstraction to really make this system modular. Now you can add new monitors without
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (26 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

changing a line of code anywhere in the program. You can dynamically load new monitors on-the-fly, thanks to Java's class loading interface. Figure 9.8 shows the relationship between an aircraft, the aircraft registry, and the altitude monitor. Figure 9.8 : The aircraft registry sends out updates when a new aircraft is created. Listing 9.22 shows a simple program that tests the interaction between an Aircraft, the AircraftRegistry, and the AltitudeMonitor classes. The monitors array contains a list of the aircraft monitors to be loaded, currently just the AltitudeMonitor2 class. This list could be read in from a file just as easily, so you wouldn't have to recompile even the test program to add new monitors; however, for this demonstration, a static array is sufficient.

Listing 9.22 Source Code for TestMonitor.java // This class demonstrates the highly dynamic nature of the // Aircraft, AircraftRegistry, and AltitudeMonitor classes. public class TestMonitor extends Object { // The list of monitors to dynamically load. static String monitors[] = { "AltitudeMonitor2" }; // Load the monitors dynamically public static void createMonitors() { for (int i=0; i < monitors.length; i++) { try { // Use the class loader. If the load fails, print an error message, but // keep running. Class monClass = Class.forName(monitors[i]); monClass.newInstance(); } catch (Exception e) { System.err.println("Got error creating class "+ monitors[i]); System.err.println(e); } } } public static void main(String[] args) { // Dynamically load the aircraft monitors createMonitors(); // Create a dummy aircraft
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (27 of 28) [8/14/02 10:53:13 PM]

Chapter 9 -- Creating Reusable Graphics Components

Aircraft ac = new Aircraft("MW1234NA", 0.0, 0.0, 10000.0, 400.0); // Add the dummy aircraft to the system AircraftRegistry.instance().addAircraft(ac); // Play with the altitudes and see if the monitor catches it. System.out.println("Setting to 12000"); ac.setAltitude(12000.0); System.out.println("Setting to 48000"); ac.setAltitude(48000.0); } }

The only disadvantage of this program dynamically loading the monitors is that the dynamic loading process can only call the empty constructor for the monitor. You would have to find alternate means of the monitors getting their configuration. Although these examples are fairly specific to a particular application, the concepts apply to a wide range of applications. Use the Observer-Observable interface to separate components as much as possible. You will find that it is much easier to plug in new components.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch9.htm (28 of 28) [8/14/02 10:53:13 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-1.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-1.gif [8/14/02 10:53:14 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-2.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-2.gif [8/14/02 10:53:15 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-3.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-3.gif [8/14/02 10:53:16 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-4.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-4.gif [8/14/02 10:53:17 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-5.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-5.gif [8/14/02 10:53:18 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-6.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-6.gif [8/14/02 10:53:19 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-7.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-7.gif [8/14/02 10:53:20 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-8.gif

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-8.gif [8/14/02 10:53:21 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

Chapter 10 Inter-Applet Communication


by Mark Wutka

CONTENTS
G G G G

Locating Other Applets Exchanging Data Using Piped Streams Creating Multi-Client Pipes Sharing Information with Singleton Objects

This chapter deals with the communication between applets running within the same browser. It does not address the problem of sending information between applets running in separate browsers. The two problems are completely different. When two applets are in the same browser, they can communicate using any form of inter-object communication, including direct method calls. Communication between applets in different browsers requires some form of networking and usually an intermediate server. One common form of network communication is Remote Method Invocation (RMI), which is discussed in Chapter 16, "Creating 3-Tier Distributed Applications with RMI."

Locating Other Applets


You will occasionally need to allow two or more applets on a Web page to communicate with each other. Because the applets all run within the same Java context-that is, they are all in the same virtual machine together-applets can invoke each other's methods. The one tricky part is getting the applets in touch with each other. The AppletContext class has methods for locating another applet by name, or retrieving all the applets in the current runtime environment. Listing 10.1 shows an applet that examines all the applets in the runtime environment and displays them in a scrolling list.

Listing 10.1 Source Code for ListApplets.java import java.applet.*; import java.awt.*; import java.util.*; // This applet demonstrates the use of the getApplets method to // get an enumeration of the current applets. public class ListApplets extends Applet
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (1 of 19) [8/14/02 10:53:25 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

{ public void init() { // Get an enumeration all the applets in the runtime environment Enumeration e = getAppletContext().getApplets(); // Create a scrolling list for the applet names List appList = new List(); while (e.hasMoreElements()) { // Get the next applet Applet app = (Applet) e.nextElement(); // Store the name of the applet's class in the scrolling list appList.addItem(app.getClass().getName()); } add(appList); } }

Figure 10.1 shows the ListApplets applet on a page with a number of other applets. Figure 10.1 : An applet can see what other applets are running on the same page. Note Be prepared to check for an applet again if you can't find it the first time. Your browser may not load applets all at once, or you might dynamically load an applet. You may receive a NullPointerException if you try to get an applet that doesn't exist-be prepared to catch it. You may have difficulty distinguishing when an applet hasn't been loaded yet from error situations in which it can't be loaded. Try picking a maximum amount of time you'll wait for an applet to be loaded, and then assume that there's a problem if the applet you want hasn't been loaded after that time.

If you already know the name of the applet you want to access, you can locate it with the getApplet method. The following code fragment locates an applet named findme: Applet findme = getAppletContext().getApplet("findme"); if (findme != null) { // do something with findme }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (2 of 19) [8/14/02 10:53:25 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

You might think that the applet name you use in getApplet is the class name of the applet. This is not the case. You set the applet's name in your <APPLET> HTML tag. For example, here is the <APPLET> tag for an applet class called FindMe, which has an applet name of findme: <APPLET codebase="." code="FindMe.class" name="findme">]

Tip Only use lowercase names for applets. Some versions of Netscape convert the applet name to lowercase. The name of the applet is separate from the name of the applet's class, so you can still use uppercase letters in the class name.

Exchanging Data Using Piped Streams


Once two applets have located each other, their means of communication is the same as for any two objects in the system. This means they can invoke each other's methods, share common arrays between them, or send data over stream pipes. A stream pipe is a pair of streams: One is an input stream; the other is an output stream. Any data written to the input stream can be read from the output stream it is connected to. You can use this mechanism to pass data between two applets or any two objects in the system. One object creates both a PipedInputStream and a PipedOutputStream class, and then passes one end of the pipe to the other object. Whichever object has the output end of the pipe can then start writing to the pipe, while the object that has the input side can start reading. Tip Since stream pipes are subclasses of FileInputStream and FileOutputStream, you can use the existing stream filters to pass different kinds of data. The DataInputStream and DataOutputStream classes are good for passing simple data types over pipes, while the ObjectInputStream and ObjectOutputStream are excellent for passing whole objects.

Stream pipes are useful for doing sequenced messaging between objects. Sometimes one object needs to tell another object to perform several tasks in sequence. If some of the tasks take a long time, you don't want the requesting object to have to wait for all the tasks to be performed, yet you want to ensure that they are done in the proper sequence. If the requests are made by sending messages over a stream pipe, the sequencing problem is solved, as is the waiting problem. The requesting object can write all its requests to the PipedOutputStream and continue on. The object performing the tasks reads each message from its PipedInputStream, performs the requested task, and then reads the next message from the pipe. The messages are guaranteed to be read in the same sequence they were written. Listing 10.2 shows an applet that creates a stream pipe and passes one end of the pipe to another applet. It demonstrates how to create a pipe and how to wait for an applet to appear. The SenderApplet first looks for its companion appletReaderApplet. Since the sender may be loaded before the reader, it must retry the search if it can't find the reader the first time it checks. It tries once every second for 30 seconds before deciding that the reader isn't going to be loaded.
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (3 of 19) [8/14/02 10:53:25 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

Once the sender finds the reader, it passes one end of the stream pipe to the reader through a simple method call. If you find that you frequently need to pass stream pipes this way, you should define an interface line this: public interface StreamPipeClient { public void setInputStream(InputStream); } This frees the sender from having to know the exact class name of the reader. As you can see in Listing 10.2, the sender knows that the reader is an instance of ReaderApplet.

Listing 10.2 Source Code for SenderApplet.java import java.applet.*; import java.io.*; // // // // This applet creates a stream pipe and uses it to pass data to another applet. It waits for the other applet to be loaded, then invokes a method on that applet to pass it the input side of the pipe.

public class SenderApplet extends Applet implements Runnable { protected PipedInputStream inStream; protected PipedOutputStream outStream; Thread appletThread; public void init() { // Create the pipe. It doesn't matter which end you create first, you just // pass the first end to the constructor of the other end. try { inStream = new PipedInputStream(); outStream = new PipedOutputStream(inStream); } catch (Exception e) { e.printStackTrace(); } } public void run() { Applet app = null; AppletContext context = getAppletContext(); int tries = 0; // how many times we've looked

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (4 of 19) [8/14/02 10:53:25 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

// Start looking for the reader applet while (app == null) { // Try to locate an applet named "reader" try { app = context.getApplet("reader"); // If we get here and app isn't null, we've found it, break out // of this while loop if (app != null) break; } catch (Exception e) { } // We couldn't find the applet. If we've tried 30 times (at once per second) // we assume it isn't coming up. tries++; if (tries > 30) { return; // time out after 30 seconds } // Sleep for a second before looking again try { Thread.sleep(1000); } catch (Exception insomnia) { } } // Now that we found the applet, cast it to a ReaderApplet so // we can invoke setInputStream ReaderApplet reader = (ReaderApplet) app; // Give the ReaderApplet the input end of the stream reader.setInputStream(inStream); while (true) { // Write byte values of 0-9 to the stream pipe over and over for (int i=0; i < 10; i++) { try { outStream.write(i); Thread.sleep(1000); } catch (Exception ignore) { }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (5 of 19) [8/14/02 10:53:25 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

} } } public void start() { appletThread = new Thread(this); appletThread.start(); } public void stop() { appletThread.stop(); appletThread = null; } }

Listing 10.3 shows the reader portion of the pipe demonstration. The sender applet had to perform a loop to wait for the reader to become active. The reader has a similar problem-it has to wait for the sender to give it the input end of the pipe. It looks at the input stream once every second, and once the input stream is no longer null, it starts reading data. Tip Rather than continually polling to see when the input stream is no longer null, the reader could use the wait/notify mechanism. Basically, if the input stream is null, the run method calls wait,which puts its thread to sleep. Then, the setInputStream method could call notify to wake the run method back up so it can start reading again.

Listing 10.3 Source Code for ReaderApplet.java import java.applet.*; import java.awt.*; import java.io.*; // // // // This applet is the companion to the SenderApplet. It receives an input stream from the sender and begins reading one byte at a time, changing a label on the screen to the string representation of each byte read so you can see it in action.

public class ReaderApplet extends Applet implements Runnable { protected InputStream inStream; protected Thread appletThread;

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (6 of 19) [8/14/02 10:53:25 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

protected Label label; public void init() { label = new Label("X"); add(label); } // This method will be called by the SenderApplet when it locates this // applet. public void setInputStream(InputStream inStream) { this.inStream = inStream; } public void run() { // Wait for the input stream while(inStream == null) { try { Thread.sleep(1000); } catch (Exception insomnia) { } } // Start reading bytes while (true) { try { int ch = inStream.read(); // If ch < 0, we hit EOF, indicating some type of shutdown if (ch < 0) return; // Update the label with the byte we just read label.setText(""+ch); } catch (Exception e) { return; } } } public void start() { appletThread = new Thread(this); appletThread.start(); }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (7 of 19) [8/14/02 10:53:25 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

public void stop() { appletThread.stop(); appletThread = null; } }

Creating Multi-Client Pipes


One of the problems you occasionally run into when doing inter-process communication with streams is that sometimes you need to send the same message to a number of clients. It would be nice if you had a stream that would write a message to a set of streams rather than making you do multiple writes. All you need to do is create an output stream that keeps track of the output streams it needs to copy data to, and then create a write method that writes the same byte to every connected stream. Listing 10.4 shows a MultiClientOutputStream class that enables you to connect up multiple output streams to a single output stream. Whenever you write to it, it copies the data you write to every stream that has been connected.

Listing 10.4 Source Code for MultiClientOutputStream import java.io.*; import java.util.*; // // // // This class implements an output stream that sends its output to any number of client output streams. It allows an object to make one write request that gets forwarded to all streams connected to this one.

public class MultiClientOutputStream extends OutputStream { protected Vector clients; // The streams connected to this one public MultiClientOutputStream() { clients = new Vector(); } public synchronized void write(int ch) { Enumeration e = clients.elements(); // It is bad medicine to remove elements from a vector while you are // still enumerating through it, but we need to remove output streams // from the client vector when we get write errors on them. We create
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (8 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

// // // //

a vector of outputstreams that need to be removed, but it only gets created if at least one stream needs to be removed. Once we finish iterating through the output streams, we remove the dead ones. Vector deadElements = null; while (e.hasMoreElements()) { OutputStream out = (OutputStream) e.nextElement(); try { out.write(ch); } catch (IOException deadStream) {

// If we haven't created the deadElements vector yet, do it now if (deadElements == null) { deadElements = new Vector(); } // Flag this stream as needing to be deleted deadElements.addElement(out); } } // If we had any dead elements, remove them from the vector of clients if (deadElements != null) { e = deadElements.elements(); while (e.hasMoreElements()) { clients.removeElement(e.nextElement()); } } } // addOutputStream connects a new stream up to the set of clients public void addOutputStream(OutputStream out) { if (!clients.contains(out)) { clients.addElement(out); } } // removeOutputStream removes a stream from the set of clients (we no // longer send output to it). public void removeOutputStream(OutputStream out) { clients.removeElement(out); }
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (9 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

Listing 10.5 shows a small test program that illustrates the use of the MultiClientOutputStream class.

Listing 10.5 Source Code for TestMulti.java import java.io.*; // // // // // This class demonstrates the use of the multi-client output stream. It hooks both System.out and System.err to the multi-client stream. It then writes information to the multi-client stream, which causes the information to appear twice - once when it is copied to System.out, the other time when it is copied to System.err.

public class TestMulti extends Object { public static void main(String[] args) { try { // Create the multi-client stream MultiClientOutputStream out = new MultiClientOutputStream(); // Connect System.out and System.err to the multi-client stream out.addOutputStream(System.out); out.addOutputStream(System.err); // Use a PrintStream to write so we can use print and println PrintStream printme = new PrintStream(out); // Write out some test data, it should appear twice printme.println("Hello there!"); printme.println("Is there an echo in here?"); // Test out the fact that if you add a duplicate streams, it still // only gets one copy of the data. out.addOutputStream(System.out); printme.println("You should still be seeing double"); // Test the disconnection of an output stream (stop writing to System.err) out.removeOutputStream(System.err);
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (10 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

printme.println("You should only be seeing single"); } catch (Exception e) { e.printStackTrace(); } } }

Figure 10.2 shows the output from the TestMulti application. Figure 10.2 : Using the MultiClientOutputStream, you can write data on multiple streams with a single write call.

Sharing Information with Singleton Objects


If you want to do inter-applet communications, you can take advantage of the fact that because all the applets run in the same instance of the virtual machine, all static class variables are shared between applets. This means you can implement singleton classes that are shared by all the applets. As you recall from Chapter 9, a singleton is a class that has only one unique instance. Its constructor is hidden so classes cannot create new instances. You access the one instance of the class through a static method in the class such as instance(). You can create a singleton class that acts as a registry for all the applets in the runtime environment. If the registry is implemented as an observable, you can watch for new applets to become available. This allows two applets to sync up without having to constantly poll the getApplet method to see when the applet becomes available. Listing 10.6 shows an implementation of the AppletRegistry class. The applet registry uses a hash table to store applets by name. Whenever you want to find an applet, you call findApplet with the name of the applet you're looking for: Applet findit = AppletRegistry.instance().findApplet("FindMeApplet"); When an applet starts up, it calls addApplet: AppletRegistry.instance().addApplet("FindMeApplet", this); The AppletRegistry is observable. Whenever an applet is added or removed, it sends an update to its observers. This allows any object implementing the Observer interface to find out whenever an applet is added to the registry. This is a nice alternative to polling the registry for particular applets. Since the registry sends a notification when applets are added or removed, it uses a simple event mechanism when it sends the notification. The object passed to the update method in the observers is an instance of AppletRegistryEvent. This object, in turn, contains the information specific to the event (the name of the applet, the applet itself, and whether it is being added or removed).

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (11 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

Listing 10.6 Source Code for AppletRegistry.java import java.applet.Applet; import java.util.*; // // // // // // // // This class implements an applet registry where applets can locate each other. It is an observable, so if you want to wait for a particular class, you can be an observer. This is better than the polling you have to do with getApplet. This class is implemented as a singleton, which means there is only one. The single instance is kept in a protected static variable and returned by the instance() method.

public class AppletRegistry extends Observable { // The single copy of the registry protected static AppletRegistry registry; // The table of applets protected Hashtable applets; // Used for generating unique applet names protected int nextUnique; protected AppletRegistry() { applets = new Hashtable(); nextUnique = 0; } // Returns the long instance of the registry. If there isn't a registry // yet, it creates one. public synchronized static AppletRegistry instance() { if (registry == null) { registry = new AppletRegistry(); } return registry; } // Adds a new applet to the registry - stores it in the table and // sends a notification to its observers. public synchronized void addApplet(String name, Applet newApplet) { applets.put(name, newApplet); setChanged(); notifyObservers(new AppletRegistryEvent(
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (12 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

AppletRegistryEvent.ADD_APPLET, name, newApplet)); } // // // // // Adds a new applet to the registry - stores it in the table and sends a notification to its observers. If uniqueName is false, the applet's name is non-unique. Store the applet in a table with a unique version of the name (appends <#> to the name where # is a constantly increasing number). public synchronized void addApplet(String name, Applet newApplet, boolean uniqueName) { if (!uniqueName && (applets.get(name) != null)) { name = name + "<"+nextUnique+">"; nextUnique++; } applets.put(name, newApplet); setChanged(); notifyObservers(new AppletRegistryEvent( AppletRegistryEvent.ADD_APPLET, name, newApplet)); } // removes an applet from the table and notifies the observers public synchronized void removeApplet(Applet applet) { Enumeration e = applets.keys(); while (e.hasMoreElements()) { Object key = e.nextElement(); if (applets.get(key) == applet) { applets.remove(key); setChanged(); notifyObservers(new AppletRegistryEvent( AppletRegistryEvent.REMOVE_APPLET, (String)key, applet)); return; } } } // removes an applet from the table and notifies the observers public synchronized void removeApplet(String name) { Applet applet = (Applet) applets.get(name); if (applet == null) return;
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (13 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

applets.remove(name); setChanged(); notifyObservers(new AppletRegistryEvent( AppletRegistryEvent.REMOVE_APPLET, name, applet)); } // finds an applet by name, or returns null if not found public Applet findApplet(String name) { return (Applet) applets.get(name); } // lets you see all the applets in the registry public Enumeration getApplets() { return applets.elements(); } }

Listing 10.7 shows the implementation of the AppletRegistryEvent object used by the AppletRegistry. This is an example of how to set up an event that is passed to the update method in an observable's observers.

Listing 10.7 Source Code for AppletRegistryEvent.java import java.applet.Applet; public class AppletRegistryEvent extends Object { public final static int ADD_APPLET = 1; public final static int REMOVE_APPLET = 2; public int id; public String appletName; public Applet applet; public AppletRegistryEvent() { } public AppletRegistryEvent(int id, String appletName, Applet applet) { this.id = id;
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (14 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

this.appletName = appletName; this.applet = applet; } }

Figure 10.3 shows the relationship between applets, the applet registry, and the observers of the registry. Figure 10.3 : Applets add themselves to the registry, which tells its observers about the new applets. To take advantage of the AppletRegistry, a new applet must register itself by calling the addApplet method. Listing 10.8 shows a new version of the SenderApplet example from earlier in this chapter. The corresponding ReaderApplet only needs to call addApplet("reader", this) in its init method to support the registry. Because the new sender applet uses the applet registry, it doesn't have to contin-ually check to see when the reader is loaded. Instead, it waits until it receives an AppletRegistryEvent that tells it that the reader applet has been added. Once it learns that the reader has been added, it passes one end of the pipe stream to the reader and starts a thread that writes data to the pipe.

Listing 10.8 Source Code for SenderApplet2.java import java.applet.*; import java.io.*; import java.util.*; // // // // // // // // // This applet creates a stream pipe and uses it to pass data to another applet. It waits for the other applet to be loaded, then invokes a method on that applet to pass it the input side of the pipe. Rather than using the standard getApplet method to check for the other applet being loaded, it uses the AppletRegistry. Also, it doesn't start its thread until the applet has been started and an update has been received stating that the other applet has been added.

public class SenderApplet2 extends Applet implements Runnable, Observer { protected PipedInputStream inStream; protected PipedOutputStream outStream; protected boolean started = false; Thread appletThread = null; public synchronized void init()

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (15 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

{ // Create the pipe. It doesn't matter which end you create first, you just // pass the first end to the constructor of the other end. try { inStream = new PipedInputStream(); outStream = new PipedOutputStream(inStream); } catch (Exception e) { e.printStackTrace(); } // Add this applet to the registry AppletRegistry.instance().addApplet("sender", this); // Start watching the registry AppletRegistry.instance().addObserver(this); // If the reader applet is already in the registry, set it up Applet applet = AppletRegistry.instance().findApplet("reader"); if (applet != null) { initReader(applet); } } // update is called by the registry whenever an applet is added or removed. public synchronized void update(Observable obs, Object ob) { if (!(ob instanceof AppletRegistryEvent)) return; AppletRegistryEvent evt = (AppletRegistryEvent) ob; if (evt.appletName.equals("reader")) { initReader(evt.applet); } } public void initReader(Applet applet) { // Now that we found the applet, cast it to a ReaderApplet so // we can invoke setInputStream ReaderApplet2 reader = (ReaderApplet2) applet; // Give the ReaderApplet the input end of the stream reader.setInputStream(inStream); appletThread = new Thread(this);

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (16 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

if (started) { appletThread.start(); } } public void run() { while (true) { // Write byte values of 0-9 to the stream pipe over and over for (int i=0; i < 10; i++) { try { outStream.write(i); Thread.sleep(1000); } catch (Exception ignore) { } } } } public synchronized void start() { started = true; // // // // // // If the applet thread has already been created, start it. This is done to synchronize with the update method. We don't know if start or update will be called first. This method sets the started flag to true, but only starts the thread if it has been created. The update method creates the thread, but only starts it if the started flag is true. if (appletThread != null) { appletThread.start(); } } public void stop() { appletThread.stop(); appletThread = null; } }

The reader applet that corresponds to SenderApplet2 is not too different from the original reader applet. The main difference is that it now adds itself to the applet registry. It still uses a polling mechanism to wait for its input stream. Again, you could use the wait/notify mechanism to keep from polling. Listing 10.9 shows the ReaderApplet2 applet.
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (17 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

Listing 10.9 Source Code for ReaderApplet2.java import java.applet.*; import java.awt.*; import java.io.*; // // // // This applet is the companion to the SenderApplet. It receives an input stream from the sender and begins reading one byte at a time, changing a label on the screen to the string representation of each byte read so you can see it in action.

public class ReaderApplet2 extends Applet implements Runnable { protected InputStream inStream; protected Thread appletThread; protected Label label; public void init() { label = new Label("X"); add(label); AppletRegistry.instance().addApplet("reader", this); } // This method will be called by the SenderApplet when it locates this // applet. public void setInputStream(InputStream inStream) { this.inStream = inStream; } public void run() { // Wait for the input stream while(inStream == null) { try { Thread.sleep(1000); } catch (Exception insomnia) { } } // Start reading bytes while (true) {

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (18 of 19) [8/14/02 10:53:26 PM]

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm

try { int ch = inStream.read(); // If ch < 0, we hit EOF, indicating some type of shutdown if (ch < 0) return; // Update the label with the byte we just read label.setText(""+ch); } catch (Exception e) { return; } } } public void start() { appletThread = new Thread(this); appletThread.start(); } public void stop() { appletThread.stop(); appletThread = null; } }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (19 of 19) [8/14/02 10:53:26 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Chapter 16 Creating 3-Tier Distributed Applications with RMI


by Mark Wutka

CONTENTS
G G G

G G G

Creating 3-Tier Applications RMI Features Creating an RMI Server H Defining a Remote Interface H Creating the Server Implementation H Creating the Stub Class Creating an RMI Client Creating Peer-to-Peer RMI Applications Garbage Collection, Remote Objects, and Peer-to-Peer

There are many ways that objects can communicate with one another over a network. Traditionally, objects would communicate with one another using sockets and a custom protocol. Remote procedure calls have also been a popular communication mechanism for the past few years. Java provides two additional mechanisms for remote object-toobject communication. Java provides an interface into the CORBA-distributed object architecture, which is discussed in Chapter 17, "Creating CORBA Clients." Remote Method Invocation (RMI) provides a very simple method for one Java object to invoke a method in another Java object across a network with very little extra work. Unlike many remote communication systems that require you to describe the remote methods in a separate file, RMI works right off existing objects, providing seamless integration.

Creating 3-Tier Applications


The 2-tier model for applications is the most common model in use today. Many application designers think only in terms of the database and the application. The availability of 2-tier application builders has helped perpetuate this philosophy. The 2-tier model is not a "bad thing," but there are cases in which the 3-tier model would be a better choice. Just to review, the 2-tier model consists of an application and a database. A 3-tier model consists of an application, a
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (1 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

layer of business logic, and a database. Once you break out of the 2-tier mold, you often start adding multiple tiers. Figure 16.1 illustrates the difference between a 2-tier and 3-tier application design. Figure 16.1 : A 3-tier design adds an extra layer of abstraction to improve reuse. You can also divide your application into an application logic tier and a presentation tier (the user interface). In a 2tier model, the business logic is part of the application. In smaller applications, this is not a problem because there may be only one application implementing a particular business process. In larger systems, however, many applications use the same areas of business logic. In a 2-tier environment, this means that the business logic is replicated across every application. If you change the business logic, you must change every application. Durable software systems are designed from the ground up with change in mind. A good designer creates modular components with well-defined interfaces so that any single component can be changed without affecting the rest of the system. The 3-tier and multi-tier models are simply the results of modular design. Before you go off thinking that creating 3-tier designs is simple, think again. Identifying business processes in a large company and reducing them to a set of methods is not a task for the fainthearted. Many companies do not have their business logic documented as a series of processes. Instead, it is only implied in the code of the applications. The identification of business processes and business logic is a subject for another book, however. In practice, the line between 2-tier and 3-tier is often rather fuzzy. You may have what is essentially a 2-tier application whose user interface is broken out into a separate module. The application logic and the business logic are still intermixed, but a portion of the application is distributed. Just because you can't get a handle on the actual business logic doesn't mean you can't still work at making your software more modular and add the benefits of distributed computing. RMI is very useful for separating an application from its user interface. You define the methods that comprise the interactions between the user interface and the client, and then make these interactions through remote method invocation (RMI). This allows an applet to implement the user interface for an application running on a server somewhere, without developing a custom communications system.

RMI Features
RMI is like a remote procedure call (RPC) mechanism in other languages. One object makes a method call into an object on another machine and gets a result back. Like most RPC systems, RMI requires that the object whose method is being invoked (the server) must already be up and running. Remote methods are defined by remote interfaces. That is, a remote interface defines a set of methods that can be called remotely. Any object that wants some of its methods to be called remotely must use one or more remote interfaces.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (2 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

An object that uses a remote interface is called a server. An object that calls a remote method is called a client. An object can be both a client and a server: These names indicate only who is calling in a particular instance and who is being called. Once you define a remote interface and create an object that uses the interface, you still need a way for the client to invoke methods on the server. Unfortunately, it is not quite as easy as instantiating a server object. You need to create a stub for the client. An object's stub is a remote view of that object in that it contains only the remote methods of the object. The stub runs on the client side and is the representative of the remote object in the client's data space. The client invokes methods on the stub and the stub then invokes the methods on the remote object. This allows any client to invoke remote methods through normal Java method invocation. A stub is also called a proxy. Figure 16.2 shows the relationship between a client, a server, and a stub. Figure 16.2 : A stub invokes remote methods on behalf of a client. RMI adds an extra feature that most RPC systems do not have. Remote objects can be passed as parameters in remote method calls. When you pass a remote object as a parameter, you actually pass a stub for the object. The real object always stays on the machine where it was originally started. The stub that is passed then invokes methods back to the original object. Stubs can also be passed as parameters and work the same way. Figure 16.3 illustrates how a client passes a stub to a server so that the server can invoke methods on the client. Figure 16.3 : A client can pass a stub to a server so the server can invoke methods on the client. In a distributed system, you need a way for clients to find the servers they need. RMI provides a simple name lookup object that allows a client to get a stub for a particular server based on the server's name. The naming service that comes with the RMI system is fairly simplistic but is useful for most cases. Figure 16.4 shows how a client uses the naming service to find a server. Figure 16.4 : The naming service allows a client to locate a server by name.

Creating an RMI Server


To create an object whose methods can be called remotely (a server object), you need to create a remote interface. All remote interfaces must extend the java.rmi.Remote interface.

Defining a Remote Interface


When you define a remote interface, pay special attention to the interaction between the objects. Remote invocations carry much heavier penalties than local invocations. Try to minimize the number of method invocations needed to get the job done. Ideally, you want to do things with a single method call. Every method in a remote interface can throw a
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (3 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

java.rmi.RemoteException. This exception is thrown by the underlying RMI system whenever there is an error in sending or receiving information. Listing 16.1 shows a sample remote interface for a simple banking application.

Listing 16.1 Source Code for Banking.java package banking; // This interface represents a set of remote methods for a // banking service. All money amounts are given in cents, so // one dollar is represented as 100. public interface Banking extends java.rmi.Remote { // getBalance returns the current balance in the account public int getBalance(Account account) throws java.rmi.RemoteException, BankingException; // withdraw subtracts an amount from an account public void withdraw(Account account, int amount) throws java.rmi.RemoteException, BankingException; // deposit adds an amount to the account public void deposit(Account account, int amount) throws java.rmi.RemoteException, BankingException; // transfer subtracts an amount from one account and // adds it to another. public void transfer(Account fromAccount, Account toAccount, int amount) throws java.rmi.RemoteException, BankingException; }

Notice that the account information is encapsulated in an Account object. This allows you to change the way you represent accounts without modifying the interface. Of course, you may have to change the client and server to understand the new account format. Tip

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (4 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Try to encapsulate related parameters into a single object, especially if they are subject to change. If a position is given by an x,y coordinate, encapsulate it in a Position object so you can later change the position to be x,y,z or even polar coordinates. This allows you to keep the remote interface the same.

Listing 16.2 shows the Account object used in the Banking interface.

Listing 16.2 Source Code for Account.java package banking; // This class contains the information that defines // a banking account. public class Account extends { // Flags to indicate whether public static final int public static final int Object the account is savings or checking CHECKING = 1; SAVINGS = 2;

public String id; // Account id, or account number public String password; // password for ATM transactions public int which; // is this checking or savings public Account() { } public Account(String id, String password, int which) { this.id = id; this.password = password; this.which = which; } public String toString() { return "Account { "+id+","+password+","+which+" }"; } // Tests equality between accounts. public boolean equals(Object ob) { if (!(ob instanceof Account)) return false;

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (5 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Account other = (Account) ob; return id.equals(other.id) && password.equals(other.password) && (which == other.which); } // Returns a hash code for this object public int hashCode() { return id.hashCode()+password.hashCode()+which; } }

Tip When encapsulating similar data into an object, always define the equals and hashCode methods. You may occasionally want to store the objects in hash tables and other structures, and without these methods, two objects containing identical data look like two separate objects.

Listing 16.3 shows the BankingException class for the Banking interface.

Listing 16.3 Source Code for BankingException.java package banking; // Defines a generic banking exception for the banking interface. public class BankingException extends Exception { public BankingException() { } public BankingException(String problem) { super(problem); } }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (6 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Tip Don't lump all your exceptions into one big exception, hiding the specific information in a string. Create exceptions specifically for each separate case. You don't want to parse the exception string to find out what kind of exception it was. Instead, you should be using instanceof.

For a simple interface like the Banking interface, there are only two specific exceptions defined: InvalidAccountException and InsufficientFundsException. Listings 16.4 and 16.5 show these exceptions.

Listing 16.4 Source Code for InvalidAccountException.java package banking; // Defines an exception for an invalid account and indicates // which account was invalid. Also allows an error string. public class InvalidAccountException extends BankingException { public Account account; // which account was invalid public InvalidAccountException() { } public InvalidAccountException(String str) { super(str); } public InvalidAccountException(Account account) { this.account = account; } public InvalidAccountException(Account account, String str) { super(str); this.account = account; }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (7 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Listing 16.5 Source Code for InsufficientFundsException.java package banking; // Defines a simple Insufficent Funds exception for the // Banking interface. public class InsufficientFundsException extends BankingException { public InsufficientFundsException() { } public InsufficientFundsException(String problem) { super(problem); } }

Creating the Server Implementation


The remote interface defines only which methods in an object are remote methods. You must still implement the remote object itself. The implementation of the remote methods is straightforward: It is no different from implementing normal methods. Apart from the implementation of the methods, you need some startup code to create a security manager, create the remote objects, and register them with the Naming service. When a server registers itself to the Naming service, it can call either the bind or rebind methods. The bind method throws an AlreadyBoundException if you try to bind an existing name to an object. The rebind method simply forgets about the old name association and binds the name to the new object. Caution It is possible for a client or server to send you malicious objects. You should exercise extreme caution when interacting with clients or servers that you did not write. The RMI system includes a StubSecurityManager that implements some security measures to help protect your applications. If you run RMI servers or clients as stand-alone programs and you don't have your own security manager, make sure you set StubSecurityManager. The default for stand-alone programs is to have no security manager.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (8 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Listing 16.6 shows a sample implementation of a remote banking object.

Listing 16.6 Source Code for BankingImpl.java package banking; import java.rmi.Naming; import java.rmi.server.UnicastRemoteServer; import java.rmi.server.StubSecurityManager; import java.util.*; // // // // // // // // This class implements a remote banking object. It sets up a set of dummy accounts and allows you to manipulate them through the Banking interface. Accounts are the password way to work, the password identified by the combination of the account id, and the account type. This is a quick and dirty and not the way a bank would normally do it, since is not part of the unique identifier of the account.

public class BankingImpl extends UnicastRemoteServer implements Banking { public Hashtable accountTable; // The constructor creates a table of dummy accounts. public BankingImpl() throws java.rmi.RemoteException { accountTable = new Hashtable(); accountTable.put( new Account("AA1234", "1017", Account.CHECKING), new Integer(50000)); // $500.00 balance accountTable.put( new Account("AA1234", "1017", Account.SAVINGS), new Integer(148756)); // $1487.56 balance accountTable.put( new Account("AB5678", "4456", Account.CHECKING), new Integer(7742)); // $77.32 balance accountTable.put( new Account("AB5678", "4456", Account.SAVINGS), new Integer(32201)); // $322.01 balance }

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (9 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

// getBalance returns the amount of money in the account (in cents). // If the account is invalid, it throws an InvalidAccountException public int getBalance(Account account) throws java.rmi.RemoteException, BankingException { // Fetch the account from the table Integer balance = (Integer) accountTable.get(account); // If the account wasn't there, throw an exception if (balance == null) { throw new InvalidAccountException(account); } // Return the account's balance return balance.intValue(); } // // // // withdraw subtracts an amount from the account's balance. If the account is invalid, it throws InvalidAccountException. If the withdrawal amount exceeds the account balance, it throws InsufficientFundsException. public synchronized void withdraw(Account account, int amount) throws java.rmi.RemoteException, BankingException { // Fetch the account Integer balance = (Integer) accountTable.get(account); // If the account wasn't there, throw an exception if (balance == null) { throw new InvalidAccountException(account); } // If we are trying to withdraw more than is in the account, // throw an exception if (balance.intValue() < amount) { throw new InsufficientFundsException(); } // Put the new balance in the account accountTable.put(account, new Integer(balance.intValue() amount)); } // Deposit adds an amount to an account. If the account is invalid
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (10 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

// it throws an InvalidAccountException public synchronized void deposit(Account account, int amount) throws java.rmi.RemoteException, BankingException { // Fetch the account Integer balance = (Integer) accountTable.get(account); // If the account wasn't there, throw an exception if (balance == null) { throw new InvalidAccountException(account); } // Update the account with the new balance accountTable.put(account, new Integer(balance.intValue() + amount)); } // // // // Transfer subtracts an amount from fromAccount and adds it to toAccount. If either account is invalid it throws InvalidAccountException. If there isn't enough money in fromAccount it throws InsufficientFundsException. public synchronized void transfer(Account fromAccount, Account toAccount, int amount) throws java.rmi.RemoteException, BankingException { // Fetch the from account Integer fromBalance = (Integer) accountTable.get(fromAccount); // If the from account doesn't exist, throw an exception if (fromBalance == null) { throw new InvalidAccountException(fromAccount); } // Fetch the to account Integer toBalance = (Integer) accountTable.get(toAccount); // If the to account doesn't exist, throw an exception if (toBalance == null) { throw new InvalidAccountException(toAccount); } // Make sure the from account contains enough money, otherwise throw // an InsufficientFundsException. if (fromBalance.intValue() < amount) { throw new InsufficientFundsException();
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (11 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

} // Subtract the amount from the fromAccount accountTable.put(fromAccount, new Integer(fromBalance.intValue() - amount)); // Add the amount to the toAccount accountTable.put(toAccount, new Integer(toBalance.intValue() + amount)); } public static void main(String args[]) { // Need a security manager to prevent malicious stubs System.setSecurityManager(new StubSecurityManager()); try { // Create the bank BankingImpl bank = new BankingImpl(); // Register the bank with the naming service. Naming.rebind("NetBank", bank); } catch (Exception e) { System.out.println("Got exception: "+e); e.printStackTrace(); } } }

Creating the Stub Class


You don't have to create the stub class for your remote object by hand. The RMI system provides a special utility to automatically generate the stub class for you. To generate the stubs for the BankingImpl class, the BankingImpl.class file should be stored in a directory called banking somewhere on your system. Go to the parent directory of the banking directory and type: rmic -d . banking.BankingImpl This creates the stubs for the BankingImpl class and also puts them in the banking directory.

Creating an RMI Client


file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (12 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Creating an RMI client is a simple task. When you need to access a remote object, you call the lookup method in the Naming service (also called the registry). The lookup method returns a stub for the remote object. The contains all the remote methods defined for that object. If the stub is not on the client system, the RMI system tries to download the stubs from the remote object's host or from wherever the remote object was loaded. Listing 16.7 shows a very simple application that remotely invokes methods in the BankingImpl object.

Listing 16.7 Source Code for BankingClient.java import java.rmi.server.StubSecurityManager; import java.rmi.Naming; import banking.*; // This program tries out some of the methods in the BankingImpl // remote object. public class BankingClient { public static void main(String args[]) { // Always set up a security manager when running RMI System.setSecurityManager(new StubSecurityManager()); // Create an Account object for the account we are going to access. Account myAccount = new Account( "AA1234", "1017", Account.CHECKING); try { // Get a stub for the BankingImpl object (the stub implements the // Banking interface). Banking bank = (Banking)Naming.lookup("NetBank"); // Check the initial balance System.out.println("My balance is: "+ bank.getBalance(myAccount)); // Deposit some money bank.deposit(myAccount, 50000);

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (13 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

// Check the balance again System.out.println("Deposited $500.00, balance is: "+ bank.getBalance(myAccount)); // Withdraw some money bank.withdraw(myAccount, 25000); // Check the balance again System.out.println("Withdrew $250.00, balance is: "+ bank.getBalance(myAccount)); } catch (Exception e) { System.out.println("Got exception: "+e); e.printStackTrace(); } } }

Creating Peer-to-Peer RMI Applications


Distributed systems that support only a pure client/server model sometimes give system designers fits. In many applications, such as banking, the pure client/server model fits quite well, since the client always initiates requests, and the server handles them and passes the data back until the transaction is completed. In other applications, you need the server to be able to invoke methods in the client as well. This is called peer-topeer since both objects take on the role of client and server. The observer-observable model is often needed in distributed systems. The interaction in the model occurs in both directions. Consequently, you need the observer to behave as a client to register itself with the observable and then behave as a server so the observable can invoke the update method in the observer. If an object can only be a client or a server and not both, you can still implement the observer-observable model but the methods are ugly. The observer could periodically poll the observable to see whether it changes. But this would put a tremendous burden on the observable, since it spends a lot of time telling the observers that it hasn't changed. The observable could also set up a waitForChange method that blocks until the observable changes. This could result in a large number of threads on the observable just sitting around waiting for a change. It consumes less network resources than the polling method because there are no "have you changed?" "No." messages flying back and forth. This is still a less-than-optimal solution, however. For one thing, suppose the observable changes in the time that it takes the observer to call waitForChange again. Should it keep track of whether things have changed since the last call? If so, that's extra work. If not, the observer may miss changes.

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (14 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

The RMI system allows an object to be both a client and a server, relieving you of many of these headaches. Typically, one object starts out as the server and one starts out as the client. At some point, the client invokes a method on the server and passes a stub back to the client, and the client also becomes a server. You might, for example, have a server that sends periodic updates of information. A client registers with the server telling it what information it wants and passes the client's stub to the server. Whenever the server has new information, it invokes a method in what was originally the client via the stub. Figure 16.5 shows the relationship between two objects in a peer-to-peer stock-quoting system. Figure 16.5 : The stock-quote server uses RMI to send quotes to its clients. Listing 16.8 shows a remote interface for a stock-quoting system that invokes a method in its clients to deliver stock quotes.

Listing 16.8 Source Code for StockQuoteServer.java package stocks; // Defines a remote interface for a stock quoting system. // Stock quotes are delivered to remote objects through the // StockQuoteClient interface. public interface StockQuoteServer extends java.rmi.Remote { // addWatch tells the server that the client wants quotes for // a certain stock. public void addWatch(StockQuoteClient client, String stock) throws java.rmi.RemoteException, StockQuoteException; // removeWatch tells the server that the client no longer wants // to watch a certain stock. public void removeWatch(StockQuoteClient client, String stock) throws java.rmi.RemoteException, StockQuoteException; // removeClient tells the server that the client no longer wants // to watch any stocks. public void removeClient(StockQuoteClient client) throws java.rmi.RemoteException, StockQuoteException; // getStockList returns an array of all the stocks that can be watched public String[] getStockList() throws java.rmi.RemoteException;
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (15 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

Listing 16.9 shows the StockQuoteClient interface that the StockQuoteServer uses to notify its clients of new quotes

Listing 16.9 Source Code for StockQuoteClient.java package stocks; // Defines a callback interface for the StockQuoteServer so // it can notify its clients of new stock quotes. public interface StockQuoteClient extends java.rmi.Remote { public void quote(StockQuote quote) throws java.rmi.RemoteException; }

Rather than putting the individual elements of a stock quote into the method definition, the stock quotes are passed around in a StockQuote object. If the system expands the information in the stock quote, it still works with the existing clients, as long as it doesn't remove or rename any fields. This lets you build an extensible system without having to change all your existing clients at once. If you change the quote method, however, all the clients have to change. Listing 16.10 shows the StockQuote object.

Listing 16.10 Source Code for StockQuote.java package stocks; // Defines the information contained in a stock quote for the // StockQuoteClient interface. public class StockQuote { public String stock; public double amount; public double change; public StockQuote() { }

// the stock name // the last price // the last change

file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch16.htm (16 of 22) [8/14/02 10:53:32 PM]

Chapter 16 -- Creating 3-Tier Distributed Applications with RMI

public StockQuote(String stock, double amount, double change) { this.stock = stock; this.amount = amount; this.change = change; } }

The stock-quote system defines its own exceptions. You should always do this for your systems if you intend to throw any exceptions outside the standard ones in Java. StockQuoteException serves as the base class for all specific exceptions in the stock-quote system. There is only one specific exception defined: UnknownStockException. Again, if you c