ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

The Java Platform
Pages: 1, 2, 3

The New I/O API

Java 1.4 introduces an entirely new API for high-performance, nonblocking I/O and networking. This API consists primarily of three new packages. java.nio defines Buffer classes that are used to store sequences of bytes or other primitive values. java.nio.channels defines channels through which data can be transferred between a buffer and a data source or sink, such as a file or a network socket. This package also contains important classes used for nonblocking I/O. Finally, the java.nio.charset package contains classes for efficiently converting buffers of bytes into buffers of characters. The subsections that follow contain examples of using all three of these packages, as well as examples of specific I/O tasks with the New I/O API.



Basic Buffer Operations

The java.nio package includes an abstract Buffer class, which defines generic operations on buffers. This package also defines type-specific subclasses such as ByteBuffer, CharBuffer, and IntBuffer. (See Buffer and ByteBuffer in for details on these classes and their various methods.) The following code illustrates typical sequences of buffer operations on a ByteBuffer. The other type-specific buffer classes have similar methods.


import java.nio.*;

// Buffers don't have public constructors. They are allocated instead.
ByteBuffer b = ByteBuffer.allocate(4096);   // Create a buffer for 4,096 bytes
// Or do this to try to get an efficient buffer from the low-level OS
ByteBuffer buf2 = ByteBuffer.allocateDirect(65536); 
// Here's another way to get a buffer: by "wrapping" an array
byte[] data;  // Assume this array is created and initialized elsewhere
ByteBuffer buf3 = ByteBuffer.wrap(data); // Create buffer that uses the array
// It is also possible to create a "view buffer" to view bytes as other types
buf3.order(ByteOrder.BIG_ENDIAN);      // Specify the byte order for the buffer
IntBuffer ib = buf3.asIntBuffer();     // View those bytes as integers

// Now store some data in the buffer
b.put(data);       // Copy bytes from array to buffer at current position
b.put((byte)42);   // Store another byte at the new current position
b.put(0, (byte)9); // Overwrite first byte in buffer. Don't change position.
b.order(ByteOrder.BIG_ENDIAN);  // Set the byte order of the buffer
b.putChar('x');       // Store the two bytes of a Unicode character in buffer
b.putInt(0xcafebabe); // Store four bytes of an int into the buffer

// Here are methods for querying basic numbers about a buffer
int capacity = b.capacity();   // How many bytes can the buffer hold? (4,096)
int position = b.position();   // Where will the next byte be written or read?
// A buffer's limit specifies how many bytes of the buffer can be used.
// When writing into a buffer, this should be the capacity. When reading data
// from a buffer, it should be the number of bytes that were previously
// written.
int limit = b.limit();         // How many should be used? 
int remaining = b.remaining(); // How many left? Return limit-position.
boolean more=b.hasRemaining(); // Test if there is still room in the buffer

// The position and limit can also be set with methods of the same name
// Suppose you want to read the bytes you've written into the buffer
b.limit(b.position());         // Set limit to current position
b.position(0);                 // Set limit to 0; start reading at beginning

// Instead of the two previous calls, you usually use a convenience method
b.flip();   // Set limit to position and position to 0; prepare for reading
b.rewind(); // Set position to 0; don't change limit; prepare for rereading
b.clear();  // Set position to 0 and limit to capacity; prepare for writing

// Assuming you've called flip(), you can start reading bytes from the buffer
buf2.put(b);        // Read all bytes from b and put them into buf2
b.rewind();         // Rewind b for rereading from the beginning
byte b0 = b.get();  // Read first byte; increment buffer position
byte b1 = b.get();  // Read second byte; increment buffer position
byte[] fourbytes = new byte[4];
b.get(fourbytes);   // Read next four bytes, add 4 to buffer position
byte b9 = b.get(9); // Read 10th byte, without changing current position
int i = b.getInt(); // Read next four bytes as an integer; add 4 to position

// Discard bytes you've already read; shift the remaining ones to the beginning
// of the buffer; set position to new limit and limit to capacity, preparing
// the buffer for writing more bytes into it.
b.compact();        

You may notice that many buffer methods return the object on which they operate. This is done so that method calls can be "chained" in code, as follows:


ByteBuffer bb=ByteBuffer.allocate(32).order(ByteOrder.BIG_ENDIAN).putInt(1234);
Many methods throughout java.nio and its subpackages return the current object to enable this kind of method chaining. Note that the use of this kind of chaining is a stylistic choice (which I have avoided in this chapter) and does not have any significant impact on efficiency.

ByteBuffer is the most important of the buffer classes. However, another commonly used class is CharBuffer. CharBuffer objects can be created by wrapping a string and can also be converted to strings. CharBuffer implements the new java.lang.CharSequence interface, which means that it can be used like a String or StringBuffer in certain applications, (e.g., for regular expression pattern matching.


// Create a read-only CharBuffer from a string
CharBuffer cb = CharBuffer.wrap("This string is the data for the CharBuffer");
String s = cb.toString();  // Convert to a String with toString() method
System.out.println(cb);    // or rely on an implicit call to toString().
char c = cb.charAt(0);     // Use CharSequence methods to get characters
char d = cb.get(1);        // or use a CharBuffer absolute read. 
// A relative read that reads the char and increments the current position
// Note that only the characters between the position and limit are used when
// a CharBuffer is converted to a String or used as a CharSequence.
char e = cb.get();         

Bytes in a ByteBuffer are commonly converted to characters in a CharBuffer and vice versa. We'll see how to do this when we consider the java.nio.charset package.

Basic Channel Operations

Buffers are not all that useful on their own -- there isn't much point in storing bytes into a buffer only to read them out again. Instead, buffers are typically used with channels: your program stores bytes into a buffer, then passes the buffer to a channel, which reads the bytes out of the buffer and writes them to a file, network socket, or some other destination. Or, in the reverse, your program passes a buffer to a channel, which reads bytes from a file, socket, or other source, and stores those bytes into the buffer, where they can then be retrieved by your program. The java.nio.channels package defines several channel classes that represent files, sockets, datagrams, and pipes. (We'll see examples of these concrete classes later in this chapter.) The following code, however, is based on the capabilities of the various channel interfaces defined by java.nio.channels and should work with any Channel object:


Channel c;  // Object that implements Channel interface; initialized elsewhere
if (c.isOpen()) c.close();  // These are the only methods defined by Channel

// The read() and write() methods are defined by the 
// ReadableByteChannel and WritableByteChannel interfaces.
ReadableByteChannel source;      // Initialized elsewhere
WritableByteChannel destination; // Initialized elsewhere
ByteBuffer buffer = ByteBuffer.allocateDirect(16384); // Low-level 16 KB buffer

// Here is the basic loop to use when reading bytes from a source channel 
// and writing them to a destination channel until there are no more bytes to 
// read from the source and no more buffered bytes to write to the destination.
while(source.read(buffer) != -1 || buffer.position() > 0) {
  // Flip buffer: set limit to position and position to 0. This prepares
  // the buffer for reading (which is done by a channel *write* operation).
  buffer.flip();              
  // Write some or all of the bytes in the buffer to the destination
  destination.write(buffer);  
  // Discard the bytes that were written, copying the remaining ones to
  // the start of the buffer. Set position to limit and limit to capacity, 
  // preparing the buffer for writing (done by a channel *read* operation).
  buffer.compact();
}

// Don't forget to close the channels
source.close();
destination.close();

In addition to the ReadableByteChannel and WritableByteChannel interfaces illustrated in the preceding code, java.nio.channels defines several other channel interfaces. ByteChannel simply extends the readable and writable interfaces without adding any new methods. It is a useful shorthand for channels that support both reading and writing. GatheringByteChannel is an extension of WritableByteChannel that defines write() methods that gather bytes from more than one buffer and write them out. Similarly, ScatteringByteChannel is an extension of ReadableByteChannel that defines read() methods that read bytes from the channel and scatter or distribute them into more than one buffer. The gathering and scattering write() and read() methods can be useful when working with network protocols that use fixed-size headers that you want to store in a buffer separate from the rest of the transferred data.

One confusing point to be aware of is that a channel read operation involves writing (or putting) bytes into a buffer, and a channel write operation involves reading (or getting) bytes from a buffer. Thus, when I say that the flip() method prepares a buffer for reading, I mean that it prepares a buffer for use in a channel write() operation! The reverse is true for the buffer's compact() method.

Encoding and Decoding Text with Charsets

A java.nio.charset.Charset object represents a character set plus an encoding for that character set. Charset and its associated classes, CharsetEncoder and CharsetDecoder, define methods for encoding strings of characters into sequences of bytes and decoding sequences of bytes into strings of characters. Since these classes are part of the New I/O API, they use the ByteBuffer and CharBuffer classes:


// The simplest case. Use Charset convenience routines to convert.
Charset charset = Charset.forName("ISO-8859-1"); // Get Latin-1 Charset
CharBuffer cb = CharBuffer.wrap("Hello World");  // Characters to encode
// Encode the characters and store the bytes in a newly allocated ByteBuffer
ByteBuffer bb = charset.encode(cb);
// Decode these bytes into a newly allocated CharBuffer and print them out
System.out.println(charset.decode(bb));

Note the use of the ISO-8859-1 (a.k.a. "Latin-1") charset in this example. This 8-bit charset is suitable for most Western European languages, including English. Programmers who work only with English may also use the 7-bit "US-ASCII" charset. The Charset class does not do encoding and decoding itself, and the previous convenience routines create CharsetEncoder and CharsetDecoder classes internally. If you plan to encode or decode multiple times, it is more efficient to create these objects yourself:


Charset charset = Charset.forName("US-ASCII");   // Get the charset
CharsetEncoder encoder = charset.newEncoder();   // Create an encoder from it
CharBuffer cb = CharBuffer.wrap("Hello World!"); // Get a CharBuffer
WritableByteChannel destination;                 // Initialized elsewhere
destination.write(encoder.encode(cb));           // Encode chars and write

The preceding CharsetEncoder.encode() method must allocate a new ByteBuffer each time it is called. For maximum efficiency, there are lower-level methods you can call to do the encoding and decoding into an existing buffer:


ReadableByteChannel source;                      // Initialized elsewhere
Charset charset = Charset.forName("ISO-8859-1"); // Get the charset
CharsetDecoder decoder = charset.newDecoder();   // Create a decoder from it
ByteBuffer bb = ByteBuffer.allocateDirect(2048); // Buffer to hold bytes
CharBuffer cb = CharBuffer.allocate(2048);       // Buffer to hold characters

while(source.read(bb) != -1) {  // Read bytes from the channel until EOF
  bb.flip();                    // Flip byte buffer to prepare for decoding
  decoder.decode(bb, cb, true); // Decode bytes into characters
  cb.flip();                    // Flip char buffer to prepare for printing
  System.out.print(cb);         // Print the characters
  cb.clear();                   // Clear char buffer to prepare for decoding
  bb.clear();                   // Prepare byte buffer for next channel read
}
source.close();                 // Done with the channel, so close it
System.out.flush();             // Make sure all output characters appear

The preceding code relies on the fact that ISO-8895-1 is an 8-bit encoding charset and that there is one-to-one mapping between characters and bytes. For more complex charsets, such as the UTF-8 encoding of Unicode or the EUC-JP charset used with Japanese text, however, this does not hold, and more than one byte is required for some (or all) characters. When this is the case, there is no guarantee that all bytes in a buffer can be decoded at once (the end of the buffer may contain a partial character). Also, since a single character may encode to more than one byte, it can be tricky to know how many bytes a given string will encode into. The following code shows a loop you can use to decode bytes in a more general way:


ReadableByteChannel source;                      // Initialized elsewhere
Charset charset = Charset.forName("UTF-8");      // A Unicode encoding
CharsetDecoder decoder = charset.newDecoder();   // Create a decoder from it
ByteBuffer bb = ByteBuffer.allocateDirect(2048); // Buffer to hold bytes
CharBuffer cb = CharBuffer.allocate(2048);       // Buffer to hold characters

// Tell the decoder to ignore errors that might result from bad bytes
decoder.onMalformedInput(CodingErrorAction.IGNORE);
decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);

decoder.reset();                 // Reset decoder if it has been used before
while(source.read(bb) != -1) {   // Read bytes from the channel until EOF
  bb.flip();                     // Flip byte buffer to prepare for decoding
  decoder.decode(bb, cb, false); // Decode bytes into characters
  cb.flip();                     // Flip char buffer to prepare for printing
  System.out.print(cb);          // Print the characters
  cb.clear();                    // Clear the character buffer
  bb.compact();                  // Discard already decoded bytes
}
source.close();                  // Done with the channel, so close it

// At this point, there may still be some bytes in the buffer to decode
bb.flip();                       // Prepare for decoding
decoder.decode(bb, cb, true);    // Pass true to indicate this is the last call
decoder.flush(cb);               // Output any final characters
cb.flip();                       // Flip char buffer
System.out.print(cb);            // Print the final characters

Working with Files

FileChannel is a concrete Channel class that performs file I/O and implements ReadableByteChannel and WritableByteChannel (although its read() method works only if the underlying file is open for reading, and its write() method works only if the file is open for writing). Obtain a FileChannel object by using the java.io package to create a FileInputStream, a FileOutputStream, or a RandomAccessFile, and then call the getChannel() method (new in Java 1.4) of that object. As an example, you can use two FileChannel objects to copy a file with code such as the following:


String filename = "test";   // The name of the file to copy
// Create streams to read the original and write the copy
FileInputStream fin = new FileInputStream(filename);
FileOutputStream fout = new FileOutputStream(filename + ".copy");
// Use the streams to create corresponding channel objects
FileChannel in = fin.getChannel();
FileChannel out = fout.getChannel();
// Allocate a low-level 8KB buffer for the copy
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
while(in.read(buffer) != -1 || buffer.position() > 0) {
  buffer.flip();      // Prepare to read from the buffer and write to the file
  out.write(buffer);  // Write some or all buffer contents
  buffer.compact();   // Discard all bytes that were written and prepare to
}                     // read more from the file and store them in the buffer.
in.close();           // Always close channels and streams when done with them
out.close();
fin.close();          // Note that closing a FileChannel does not automatically
fout.close();         // close the underlying stream.

FileChannel has special transferTo() and transferFrom() methods that make it particularly easy (and on many operating systems, particularly efficient) to transfer a specified number of bytes from a FileChannel to some other specified channel, or from some other channel to a FileChannel. These methods allow us to simplify the preceding file-copying code to the following:


FileChannel in, out;              // Assume these are initialized as in the 
                                  // preceding example.
long numbytes = in.size();        // Number of bytes in original file
in.transferTo(0, numbytes, out);  // Transfer that amount to output channel

This code could be equally well-written using transferFrom() instead of transferTo() (note that these two methods expect their arguments in different orders):


long numbytes = in.size();
out.transferFrom(in, 0, numbytes);

FileChannel also has other capabilities that are not shared by other channel classes. One of the most important is the ability to "memory map" a file or a portion of a file, i.e., to obtain a MappedByteBuffer (a subclass of ByteBuffer) that represents the contents of the file and allows you to read (and optionally write) file contents simply by reading from and writing to the buffer. Memory mapping a file is a somewhat expensive operation, so this technique is usually efficient only when you are working with a large file to which you need repeated access. Memory mapping offers you yet another way to perform the same file-copy operation shown previously:


long filesize = in.size();
ByteBuffer bb = in.map(FileChannel.MapMode.READ_ONLY, 0, filesize);
while(bb.hasRemaining()) out.write(bb);

The channel interfaces defined by java.nio.channels include ByteChannel but not CharChannel. The channel API is low-level and provides methods for reading bytes only. All of the previous examples have treated files as binary files. It is possible to use the CharsetEncoder and CharsetDecoder classes introduced earlier to convert between bytes and characters, but when you want to work with text files, the Reader and Writer classes of the java.io package are usually much easier to use than CharBuffer. Fortunately, the Channels class defines convenience methods that bridge between the new and old APIs. Here is code that wraps a Reader and a Writer object around input and output channels, reads lines of Latin-1 text from the input channel, and writes them back out to the output channel, with the encoding changed to UTF-8:


ReadableByteChannel in;      // Assume these are initialized elsewhere
WritableByteChannel out; 
// Create a Reader and Writer from a FileChannel and charset name
BufferedReader reader=new BufferedReader(Channels.newReader(in, "ISO-8859-1"));
PrintWriter writer = new PrintWriter(Channels.newWriter(out, "UTF-8"));
String line;
while((line = reader.readLine()) != null) writer.println(line);
reader.close();
writer.close();

Unlike the FileInputStream and FileOutputStream classes, the FileChannel class allows random access to the contents of the file. The zero-argument position() method returns the file pointer (the position of the next byte to be read), and the one-argument position() method allows you to set this pointer to any value you want. This allows you to skip around in a file in the way that the java.io.RandomAccessFile does. Here is an example:


// Suppose you have a file that has data records scattered throughout, and the
// last 1,024 bytes of the file are an index that provides the position of
// those records. Here is code that reads the index of the file, looks up the
// position of the first record within the file, and then reads that record.
FileChannel in = new FileInputStream("test.data").getChannel();  // The channel
ByteBuffer index = ByteBuffer.allocate(1024);   // A buffer to hold the index
long size = in.size();                          // The size of the file
in.position(size - 1024);                       // Position at start of index
in.read(index);                                 // Read the index
int record0Position = index.getInt(0);          // Get first index entry
in.position(record0Position);                   // Position file at that point
ByteBuffer record0 = ByteBuffer.allocate(128);  // Get buffer to hold data
in.read(record0);                               // Finally, read the record

The final feature of FileChannel that we'll consider here is its ability to lock a file or a portion of a file against all concurrent access (an exclusive lock) or against concurrent writes (a shared lock). (Note that some operating systems strictly enforce all locks, while others only provide an advisory locking facility that requires programs to cooperate and to attempt to acquire a lock before reading or writing portions of a shared file.) In the previous random-access example, suppose we wanted to ensure that no other program was modifying the record data while we read it. We could acquire a shared lock on that portion of the file with the following code:


FileLock lock = in.lock(record0Position, // Start of locked region
                        128,        // Length of locked region
                        true);      // Shared lock: prevent concurrent updates
                                    // but allow concurrent reads.
in.position(record0Position);       // Move to start of index
in.read(record0);                   // Read the index data
lock.release();                     // You're done with the lock, so release it

Client-Side Networking

The New I/O API includes networking capabilities as well as file-access capabilities. To communicate over the network, you can use the SocketChannel class. Create a SocketChannel with the static open() method, then read and write bytes from and to it as you would with any other channel object. The following code uses SocketChannel to send an HTTP request to a web server and saves the server's response (including all of the HTTP headers) to a file. Note the use of java.net.InetSocketAddress, a subclass of java.net.SocketAddress, to tell the SocketChannel what to connect to. These classes are also new in Java 1.4 and were introduced as part of the New I/O API.


import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;

// Create a SocketChannel connected to the web server at www.oreilly.com
SocketChannel socket =
  SocketChannel.open(new InetSocketAddress("www.oreilly.com",80));
// A charset for encoding the HTTP request
Charset charset = Charset.forName("ISO-8859-1");
// Send an HTTP request to the server. Start with a string, wrap it to
// a CharBuffer, encode it to a ByteBuffer, then write it to the socket.
socket.write(charset.encode(CharBuffer.wrap("GET / HTTP/1.0\r\n\r\n")));
// Create a FileChannel to save the server's response to
FileOutputStream out = new FileOutputStream("oreilly.html");
FileChannel file = out.getChannel();
// Get a buffer for holding bytes while transferring from socket to file
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
// Now loop until all bytes are read from the socket and written to the file
while(socket.read(buffer) != -1 || buffer.position() > 0) {  // Are we done?
  buffer.flip();       // Prepare to read bytes from buffer and write to file
  file.write(buffer);  // Write some or all bytes to the file
  buffer.compact();    // Discard those that were written
}
socket.close();        // Close the socket channel
file.close();          // Close the file channel
out.close();           // Close the underlying file

Another way to create a SocketChannel is with the no-argument version of open(), which creates an unconnected channel. This allows you to call the socket() method to obtain the underlying socket, configure the socket as desired, and connect to the desired host with the connect method. For example:


SocketChannel sc = SocketChannel.open();  // Open an unconnected socket channel
Socket s = sc.socket();                   // Get underlying java.net.Socket
s.setSOTimeout(3000);                     // Time out after three seconds
// Now connect the socket channel to the desired host and port
sc.connect(new InetSocketAddress("www.davidflanagan.com", 80));

ByteBuffer buffer = ByteBuffer.allocate(8192);  // Create a buffer
try { sc.read(buffer); }                        // Try to read from socket
catch(SocketTimeoutException e) {               // Catch timeouts here
  System.out.println("The remote computer is not responding.");
  sc.close();
}

In addition to the SocketChannel class, the java.nio.channels package defines a DatagramChannel for networking with datagrams instead of sockets. DatagramChannel is not demonstrated here, but you can read about it in .

One of the most powerful features of the New I/O API is that channels such as SocketChannel and DatagramChannel can be used in nonblocking mode. We'll see examples of this in later sections.

Server-Side Networking

The java.net package defines a Socket class for communication between a client and a server and defines a ServerSocket class used by the server to listen for and accept connections from clients. The java.nio.channels package is analogous: it defines a SocketChannel class for data transfer and a ServerSocketChannel class for accepting connections. ServerSocketChannel is an unusual channel because it does not implement ReadableByteChannel or WritableByteChannel. Instead of read() and write() methods, it has an accept() method for accepting client connections and obtaining a SocketChannel through which it communicates with the client. Here is the code for a simple, single-threaded server that listens for connections on port 8000 and reports the current time to any client that connects:


import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;

public class DateServer {
    public static void main(String[] args) throws java.io.IOException {
	// Get a CharsetEncoder for encoding the text sent to the client
	CharsetEncoder encoder = Charset.forName("US-ASCII").newEncoder();

	// Create a new ServerSocketChannel and bind it to port 8000  
	// Note that this must be done using the underlying ServerSocket
	ServerSocketChannel server = ServerSocketChannel.open();
	server.socket().bind(new java.net.InetSocketAddress(8000));

	for(;;) {  // This server runs forever
	    // Wait for a client to connect
	    SocketChannel client = server.accept();  
	    // Get the current date and time as a string
	    String response = new java.util.Date().toString() + "\r\n";
	    // Wrap, encode, and send the string to the client
	    client.write(encoder.encode(CharBuffer.wrap(response)));
	    // Disconnect from the client
	    client.close();
	}
    }
}

Nonblocking I/O

The preceding DateServer class is a simple network server. Because it does not maintain a connection with any client, it never needs to communicate with more than one at a time, and there is never more than one SocketChannel in use. More realistic servers must be able to communicate with more than one client at a time. The java.io and java.net APIs allow only blocking I/O, so servers written using these APIs must use a separate thread for each client. For large-scale servers with many clients, this approach does not scale well. To solve this problem, the New I/O API allows most channels (but not FileChannel) to be used in nonblocking mode and allows a single thread to manage all pending connections. This is done with a Selector object, which keeps track of a set of registered channels and can block until one or more of those channels is ready for I/O, as the following code illustrates. This is a longer example than most in this chapter, but it is a complete working server class that manages a ServerSocketChannel and any number of SocketChannel connections to clients through a single Selector object.


import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*;           // For Set and Iterator

public class NonBlockingServer {
    public static void main(String[] args) throws IOException {
	
	// Get the character encoders and decoders you'll need
	Charset charset = Charset.forName("ISO-8859-1");
	CharsetEncoder encoder = charset.newEncoder();
	CharsetDecoder decoder = charset.newDecoder();

	// Allocate a buffer for communicating with clients
	ByteBuffer buffer = ByteBuffer.allocate(512);

	// All of the channels in this code will be in nonblocking mode. 
	// So create a Selector object that will block while monitoring 
	// all of the channels and stop blocking only when one or more
	// of the channels is ready for I/O of some sort.
	Selector selector = Selector.open();

	// Create a new ServerSocketChannel and bind it to port 8000  
	// Note that this must be done using the underlying ServerSocket
	ServerSocketChannel server = ServerSocketChannel.open();
	server.socket().bind(new java.net.InetSocketAddress(8000));
	// Put the ServerSocketChannel into nonblocking mode
	server.configureBlocking(false);
	// Now register it with the Selector (note that register() is called 
         // on the channel, not on the selector object, however). 
         // The SelectionKey represents the registration of this channel with 
         // this Selector.
	SelectionKey serverkey = server.register(selector,
		                                SelectionKey.OP_ACCEPT);

	for(;;) {  // The main server loop. The server runs forever.
	    // This call blocks until there is activity on one of the 
	    // registered channels. This is the key method in nonblocking 
	    // I/O.
	    selector.select();

	    // Get a java.util.Set containing the SelectionKey objects for
	    // all channels that are ready for I/O.
	    Set keys = selector.selectedKeys();

	    // Use a java.util.Iterator to loop through the selected keys
	    for(Iterator i = keys.iterator(); i.hasNext(); ) {
		// Get the next SelectionKey in the set, and remove it
		// from the set. It must be removed explicitly, or it will
		// be returned again by the next call to select().
		SelectionKey key = (SelectionKey) i.next();
		i.remove();

		// Check whether this key is the SelectionKey obtained when
		// you registered the ServerSocketChannel.
		if (key == serverkey) {
		    // Activity on the ServerSocketChannel means a client
		    // is trying to connect to the server.
		    if (key.isAcceptable()) {
			// Accept the client connection and obtain a 
			// SocketChannel to communicate with the client.
			SocketChannel client = server.accept();
			// Put the client channel in nonblocking mode
			client.configureBlocking(false);
			// Now register it with the Selector object, 
			// telling it that you'd like to know when 
			// there is data to be read from this channel.
			SelectionKey clientkey =
			    client.register(selector, 
			    SelectionKey.OP_READ);
			// Attach some client state to the key. You'll
			// use this state when you talk to the client.
			clientkey.attach(new Integer(0));
		    }
		}
		else {
		    // If the key obtained from the Set of keys is not the
		    // ServerSocketChannel key, then it must be a key 
		    // representing one of the client connections.  
		    // Get the channel from the key.
		    SocketChannel client = (SocketChannel) key.channel();

		    // If you are here, there should be data to read from
		    // the channel, but double-check.
		    if (!key.isReadable()) continue;

		    // Now read bytes from the client. Assume that all the
		    // client's bytes are in one read operation.
		    int bytesread = client.read(buffer);

		    // If read() returns -1, it indicates end-of-stream, 
		    // which means the client has disconnected, so 
		    // deregister the selection key and close the channel.
		    if (bytesread == -1) {  
			key.cancel();
			client.close();
			continue;
		    }

		    // Otherwise, decode the bytes to a request string
		    buffer.flip();
		    String request = decoder.decode(buffer).toString();
		    buffer.clear();
		    // Now reply to the client based on the request string
		    if (request.trim().equals("quit")) {
			// If the request was "quit", send a final message
			// Close the channel and deregister the 
			// SelectionKey
			client.write(encoder.encode(CharBuffer.
			    wrap("Bye.")));
			key.cancel();
			client.close();
		    }
		    else {
			// Otherwise, send a response string comprised of 
			// the sequence number of this request plus an 
			// uppercase version of the request string. Note 
			// that you keep track of the sequence number by 
			// "attaching" an Integer object to the 
			// SelectionKey and incrementing it each time.

			// Get sequence number from SelectionKey
			int num = ((Integer)key.attachment()).intValue();
			// For response string
			
			String response = num + ": " + 
			    request.toUpperCase();
			// Wrap, encode, and write the response string
			client.write(encoder.encode(CharBuffer.
			    wrap(response)));
			// Attach an incremented sequence nubmer to the key
			key.attach(new Integer(num+1));
		    }
		}
	    }
	}
    }
}

Nonblocking I/O is most useful for writing network servers. It is also useful in clients that have more than one network connection pending at the same time. For example, consider a web browser downloading a web page and the images referenced by that page at the same time. One other interesting use of nonblocking I/O is to perform nonblocking socket connection operations. The idea is that you can ask a SocketChannel to establish a connection to a remote host and then go do other stuff (such as build a GUI, for example) while the underlying OS is setting up the connection across the network. Later, you do a select() call to block until the connection has been established, if it hasn't been already. The code for a nonblocking connect looks like this:


// Create a new, unconnected SocketChannel. Put it in nonblocking
// mode, register it with a new Selector, and then tell it to connect.
// The connect call will return instead of waiting for the network
// connect to be fully established.
Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_CONNECT);
channel.connect(new InetSocketAddress(hostname, port));

// Now go do other stuff while the connection is set up
// For example, you can create a GUI here

// Now block if necessary until the SocketChannel is ready to connect.
// Since you've registered only one channel with this selector, you
// don't need to examine the key set; you know which channel is ready.
while(selector.select() == 0) /* empty loop */;

// This call is necessary to finish the nonblocking connections
channel.finishConnect();

// Finally, close the selector, which deregisters the channel from it
selector.close();

XML

JAXP, the Java API for XML Processing, was originally defined as an optional extension to the Java platform and was available as a separate download. In Java 1.4, however, JAXP has been made part of the core platform. It consists of the following packages (and their subpackages):

javax.xml.parsers

This package provides high-level interfaces for instantiating SAX and DOM parsers; it is a "pluggability layer" that allows the end user or system administrator to choose or even replace the default parser implementation with another.

javax.xml.transform

This package and its subpackages define a Java API for transforming XML document content and representation using the XSLT standard. This package also provides a pluggability layer that allows new XSLT engines to be "plugged in" and used in place of the default implementation.

org.xml.sax

This package and its two subpackages define the de facto standard SAX (SAX stands for Simple API for XML) API. SAX is an event-driven, XML-parsing API: a SAX parser invokes methods of a specified ContentHandler object (as well as some other related handler objects) as it parses an XML document. The structure and content of the document are fully described by the method calls. This is a streaming API that does not build any permanent representation of the document. It is up to the ContentHandler implementation to store any state or perform any actions that are appropriate. This package includes classes for the SAX 2 API and deprecated classes for SAX 1.

org.w3c.dom

This package defines interfaces that represent an XML document in tree form. The Document Object Model (DOM) is a recommendation (essentially a standard) of the World Wide Web Consortium (W3C). A DOM parser reads an XML document and converts it into a tree of nodes that represent the full content of the document. Once the tree representation of the document is created, a program can examine and manipulate it however it wants.

Examples of each of these packages are presented in the following subsections.

Parsing XML with SAX

The first step in parsing an XML document with SAX is to obtain a SAX parser. If you have a SAX parser implementation of your own, you can simply instantiate the appropriate parser class. It is usually simpler, however, to use the javax.xml.parsers package to instantiate whatever SAX parser is provided by the Java implementation. The code looks like this:


import javax.xml.parsers.*;

// Obtain a factory object for creating SAX parsers
SAXParserFactory parserFactory = SAXParserFactory.newInstance();

// Configure the factory object to specify attributes of the parsers it creates
parserFactory.setValidating(true);
parserFactory.setNamespaceAware(true);

// Now create a SAXParser object
SAXParser parser = parserFactory.newSAXParser();   //May throw exceptions

The SAXParser class is a simple wrapper around the org.xml.sax.XMLReader class. Once you have obtained one, as shown in the previous code, you can parse a document by simply calling one of the various parse() methods. Some of these methods use the deprecated SAX 1 HandlerBase class, and others use the current SAX 2 org.xml.sax.helpers.DefaultHandler class. The DefaultHandler class provides an empty implementation of all the methods of the ContentHandler, ErrorHandler, DTDHandler, and EntityResolver interfaces. These are all the methods that the SAX parser can call while parsing an XML document. By subclassing DefaultHandler and defining the methods you care about, you can perform whatever actions are necessary in response to the method calls generated by the parser. The following code shows a method that uses SAX to parse an XML file and determine the number of XML elements that appear in a document as well as the number of characters of plain text (possibly excluding "ignorable whitespace") that appear within those elements:


import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

public class SAXCount {
    public static void main(String[] args) 
	throws SAXException,IOException, ParserConfigurationException
    {
	// Create a parser factory and use it to create a parser
	SAXParserFactory parserFactory = SAXParserFactory.newInstance();
	SAXParser parser = parserFactory.newSAXParser();
	// This is the name of the file you're parsing
	String filename = args[0];
	// Instantiate a DefaultHandler subclass to do your counting for you
	CountHandler handler = new CountHandler();
	// Start the parser. It reads the file and calls methods of the 
         // handler.
	parser.parse(new File(filename), handler);
	// When you're done, report the results stored by your handler object
	System.out.println(filename + " contains " + handler.numElements +
			   " elements and " + handler.numChars +
			   " other characters ");
    }

    // This inner class extends DefaultHandler to count elements and text in
    // the XML file and saves the results in public fields. There are many
    // other DefaultHandler methods you could override, but you need only 
    // these.
    public static class CountHandler extends DefaultHandler {
	public int numElements = 0, numChars = 0;  // Save counts here
	// This method is invoked when the parser encounters the opening tag
	// of any XML element. Ignore the arguments but count the element.
	public void startElement(String uri, String localname, String qname,
		                Attributes attributes) {
	    numElements++;
	}

	// This method is called for any plain text within an element
	// Simply count the number of characters in that text
	

	public void characters(char[] text, int start, int length) {
	    numChars += length;
	}
    }
}

Parsing XML with DOM

The DOM API is much different from the SAX API. While SAX is an efficient way to scan an XML document, it is not well-suited for programs that want to modify documents. Instead of converting an XML document into a series of method calls, a DOM parser converts the document into an org.w3c.dom.Document object, which is a tree of org.w3c.dom.Node objects. The conversion of the complete XML document to tree form allows random access to the entire document but can consume substantial amounts of memory.

In the DOM API, each node in the document tree implements the Node interface and a type-specific subinterface. (The most common types of node in a DOM document are Element and Text nodes.) When the parser is done parsing the document, your program can examine and manipulate that tree using the various methods of Node and its subinterfaces. The following code uses JAXP to obtain a DOM parser (which, in JAXP parlance, is called a DocumentBuilder). It then parses an XML file and builds a document tree from it. Next, it examines the Document tree to search for <sect1> elements and prints the contents of the <title> of each.


import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class GetSectionTitles {
    public static void main(String[] args)
	throws IOException, ParserConfigurationException,
	       org.xml.sax.SAXException
    {
	// Create a factory object for creating DOM parsers and configure it
	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	factory.setIgnoringComments(true);  // We want to ignore comments
	factory.setCoalescing(true);        // Convert CDATA to Text nodes
	factory.setNamespaceAware(false);   // No namespaces: this is default
	factory.setValidating(false);       // Don't validate DTD: also default

	// Now use the factory to create a DOM parser, a.k.a. DocumentBuilder
	DocumentBuilder parser = factory.newDocumentBuilder();

	// Parse the file and build a Document tree to represent its content
	Document document = parser.parse(new File(args[0]));

	// Ask the document for a list of all &lt;sect1&gt; elements it contains
	NodeList sections = document.getElementsByTagName("sect1");
	// Loop through those <sect1> elements one at a time
	int numSections = sections.getLength();
	for(int i = 0; i < numSections; i++) {
	    Element section = (Element)sections.item(i);  // A <sect1>
	    // The first Element child of each <sect1> should be a <title>
	    // element, but there may be some whitespace Text nodes first, so 
	    // loop through the children until you find the first element 
	    // child.
	    Node title = section.getFirstChild();
	    while(title != null && title.getNodeType() != Node.ELEMENT_NODE) 
		title = title.getNextSibling();
	    // Print the text contained in the Text node child of this element
	    if (title != null)
		System.out.println(title.getFirstChild().getNodeValue());
	}
    }
}

Transforming XML Documents

The javax.xml.transform package defines a TransformerFactory class for creating Transformer objects. A Transformer can transform a document from its Source representation into a new Result representation and optionally apply an XSLT transformation to the document content in the process. Three subpackages define concrete implementations of the Source and Result interfaces, which allow documents to be transformed among three representations:

javax.xml.transform.stream

Represents documents as streams of XML text

javax.xml.transform.dom

Represents documents as DOM Document trees

javax.xml.transform.sax

Represents documents as sequences of SAX method calls

The following code shows one use of these packages to transform the representation of a document from a DOM Document tree into a stream of XML text. An interesting feature of this code is that it does not create the Document tree by parsing a file; instead, it builds it up from scratch.


import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class DOMToStream {
    public static void main(String[] args) 
        throws ParserConfigurationException,
	       TransformerConfigurationException,
	       TransformerException
    {
	// Create a DocumentBuilderFactory and a DocumentBuilder
	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	DocumentBuilder db = dbf.newDocumentBuilder();
	// Instead of parsing an XML document, however, just create an empty
	// document that you can build up yourself.
	Document document = db.newDocument();

	// Now build a document tree using DOM methods
	Element book = document.createElement("book"); // Create new element
	book.setAttribute("id", "javanut4");           // Give it an attribute
	document.appendChild(book);                    // Add to the document
	for(int i = 1; i <= 3; i++) {                  // Add more elements
	    Element chapter = document.createElement("chapter");
	    Element title = document.createElement("title");
	    title.appendChild(document.createTextNode("Chapter " + i));
	    chapter.appendChild(title);
	    chapter.appendChild(document.createElement("para"));
	    book.appendChild(chapter);
	}

	// Now create a TransformerFactory and use it to create a Transformer
	// object to transform our DOM document into a stream of XML text.
	// No arguments to newTransformer() means no XSLT stylesheet
	TransformerFactory tf = TransformerFactory.newInstance();
	Transformer transformer = tf.newTransformer();

	// Create the Source and Result objects for the transformation
	DOMSource source = new DOMSource(document);          // DOM document
	StreamResult result = new StreamResult(System.out);  // to XML text

	// Finally, do the transformation
	transformer.transform(source, result);
    }
}

The most interesting uses of javax.xml.transform involve XSLT stylesheets. XSLT is a complex but powerful XML grammar that describes how XML document content should be converted to another form (e.g., XML, HTML, or plain text). A tutorial on XSLT stylesheets is beyond the scope of this book, but the following code (which contains only six key lines) shows how you can apply such a stylesheet (which is an XML document itself) to another XML document and write the resulting document to a stream:


import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class Transform {
    public static void main(String[] args)
        throws TransformerConfigurationException,
	       TransformerException
    {
	// Get Source and Result objects for input, stylesheet, and output
	StreamSource input = new StreamSource(new File(args[0]));
	StreamSource stylesheet = new StreamSource(new File(args[1]));
	StreamResult output = new StreamResult(new File(args[2]));

	// Create a transformer and perform the transformation
	TransformerFactory tf = TransformerFactory.newInstance();
	Transformer transformer = tf.newTransformer(stylesheet);
	transformer.transform(input, output);
    }
}

Processes

Earlier in the chapter, we saw how easy it is to create and manipulate multiple threads of execution running within the same Java interpreter. Java also has a java.lang.Process class that represents a program running externally to the interpreter. A Java program can communicate with an external process using streams in the same way that it might communicate with a server running on some other computer on the network. Using a Process is always platform-dependent and is rarely portable, but it is sometimes a useful thing to do:


// Maximize portability by looking up the name of the command to execute
// in a configuration file. 
java.util.Properties config;  
String cmd = config.getProperty("sysloadcmd");
if (cmd != null) {
  // Execute the command; Process p represents the running command
  Process p = Runtime.getRuntime().exec(cmd);         // Start the command
  InputStream pin = p.getInputStream();               // Read bytes from it
  InputStreamReader cin = new InputStreamReader(pin); // Convert them to chars
  BufferedReader in = new BufferedReader(cin);        // Read lines of chars
  String load = in.readLine();                        // Get the command output
  in.close();                                         // Close the stream
}

Security

The java.security package defines quite a few classes related to the Java access-control architecture, which is discussed in more detail in . These classes allow Java programs to run untrusted code in a restricted environment from which it can do no harm. While these are important classes, you rarely need to use them. The more interesting classes are the ones used for authentication; examples of their use are shown below.

Message Digests

A message digest is a value, also known as cryptographic checksum or secure hash, that is computed over a sequence of bytes. The length of the digest is typically much smaller than the length of the data for which it is computed, but any change, no matter how small, in the input bytes, produces a change in the digest. When transmitting data (a message), you can transmit a message digest along with it. Then, the recipient of the message can recompute the message digest on the received data and, by comparing the computed digest to the received digest, determine whether the message or the digest was corrupted or tampered with during transmission. We saw a way to compute a message digest earlier in the chapter when we discussed streams. A similar technique can be used to compute a message digest for nonstreaming binary data:


import java.security.*;

// Obtain an object to compute message digests using the "Secure Hash
// Algorithm"; this method can throw a NoSuchAlgorithmException.
MessageDigest md = MessageDigest.getInstance("SHA");

byte[] data, data1, data2, secret;  // Some byte arrays initialized elsewhere

// Create a digest for a single array of bytes
byte[] digest = md.digest(data);

// Create a digest for several chunks of data
md.reset();            // Optional: automatically called by digest()
md.update(data1);      // Process the first chunk of data
md.update(data2);      // Process the second chunk of data
digest = md.digest();  // Compute the digest

// Create a keyed digest that can be verified if you know the secret bytes
md.update(data);             // The data to be transmitted with the digest
digest = md.digest(secret);  // Add the secret bytes and compute the digest

// Verify a digest like this
byte[] receivedData, receivedDigest;  // The data and the digest we received
byte[] verifyDigest = md.digest(receivedData);  // Digest the received data
// Compare computed digest to the received digest
boolean verified =  java.util.Arrays.equals(receivedDigest, verifyDigest);

Digital Signatures

A digital signature combines a message-digest algorithm with public-key cryptography. The sender of a message, Alice, can compute a digest for a message and then encrypt that digest with her private key. She then sends the message and the encrypted digest to a recipient, Bob. Bob knows Alice's public key (it is public, after all), so he can use it to decrypt the digest and verify that the message has not been tampered with. In performing this verification, Bob also learns that the digest was encrypted with Alice's private key, since he was able to decrypt the digest successfully using Alice's public key. As Alice is the only one who knows her private key, the message must have come from Alice. A digital signature is called such because, like a pen-and-paper signature, it serves to authenticate the origin of a document or message. Unlike a pen-and-paper signature, however, a digital signature is very difficult, if not impossible, to forge, and it cannot simply be cut and pasted onto another document.

Java makes creating digital signatures easy. In order to create a digital signature, however, you need a java.security.PrivateKey object. Assuming that a keystore exists on your system (see the keytool documentation in ), you can get one with code like the following:


// Here is some basic data we need
File homedir = new File(System.getProperty("user.home"));
File keyfile = new File(homedir, ".keystore"); // Or read from config file
String filepass = "KeyStore password"          // Password for entire file
String signer = "david";                       // Read from config file
String password = "No one can guess this!";    // Better to prompt for this
PrivateKey key;  // This is the key we want to look up from the keystore

try {
  // Obtain a KeyStore object and then load data into it
  KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
  keystore.load(new BufferedInputStream(new FileInputStream(keyfile)), 
                filepass.toCharArray());
  // Now ask for the desired key
  key = (PrivateKey) keystore.getKey(signer, password.toCharArray());
}
catch (Exception e) { /* Handle various exception types here */ }

Once you have a PrivateKey object, you create a digital signature with a java.security.Signature object:


PrivateKey key;              // Initialized as shown previously
byte[] data;                 // The data to be signed
Signature s =                // Obtain object to create and verify signatures
  Signature.getInstance("SHA1withDSA");  // Can throw a 
                                         // NoSuchAlgorithmException
s.initSign(key);             // Initialize it; can throw an InvalidKeyException
s.update(data);              // Data to sign; can throw a SignatureException
/* s.update(data2); */       // Call multiple times to specify all data
byte[] signature = s.sign(); // Compute signature

A Signature object can verify a digital signature:


byte[] data;        // The signed data; initialized elsewhere
byte[] signature;   // The signature to be verified; initialized elsewhere
String signername;  // Who created the signature; initialized elsewhere
KeyStore keystore;  // Where certificates stored; initialize as shown earlier

// Look for a public-key certificate for the signer
java.security.cert.Certificate cert = keystore.getCertificate(signername);
PublicKey publickey = cert.getPublicKey();  // Get the public key from it

Signature s = Signature.getInstance("SHA1withDSA"); // Or some other algorithm
s.initVerify(publickey);                            // Setup for verification
s.update(data);                                     // Specify signed data
boolean verified = s.verify(signature);             // Verify signature data

Signed Objects

The java.security.SignedObject class is a convenient utility for wrapping a digital signature around an object. The SignedObject can then be serialized and transmitted to a recipient, who can deserialize it and use the verify() method to verify the signature:


Serializable o;  // The object to be signed; must be Serializable
PrivateKey k;    // The key to sign with; initialized elsewhere
Signature s = Signature.getInstance("SHA1withDSA"); // Signature "engine"
SignedObject so = new SignedObject(o, k, s);        // Create the SignedObject 

// The SignedObject encapsulates the object o; it can now be serialized
// and transmitted to a recipient. 

// Here's how the recipient verifies the SignedObject
SignedObject so;           // The deserialized SignedObject
Object o;                  // The original object to extract from it
PublicKey pk;              // The key to verify with
Signature s = Signature.getInstance("SHA1withDSA"); // Verification "engine"






if (so.verify(pk,s))       // If the signature is valid,
  o = so.getObject();      // retrieve the encapsulated object. 

Cryptography

The java.security package includes cryptography-based classes, but it does not contain classes for actual encryption and decryption. That is the job of the javax.crypto package. This package supports symmetric-key cryptography, in which the same key is used for both encryption and decryption and must be known by both the sender and the receiver of encrypted data.

Secret Keys

The SecretKey interface represents an encryption key; the first step of any cryptographic operation is to obtain an appropriate SecretKey. Unfortunately, the keytool program supplied with the Java SDK cannot generate and store secret keys, so a program must handle these tasks itself. Here is some code that shows various ways to work with SecretKey objects:


import javax.crypto.*;       
import javax.crypto.spec.*;  

// Generate encryption keys with a KeyGenerator object
KeyGenerator desGen = KeyGenerator.getInstance("DES");       // DES algorithm
SecretKey desKey = desGen.generateKey();                     // Generate a key
KeyGenerator desEdeGen = KeyGenerator.getInstance("DESede"); // Triple DES
SecretKey desEdeKey = desEdeGen.generateKey();               // Generate a key

// SecretKey is an opaque representation of a key. Use SecretKeyFactory to
// convert to a transparent representation that can be manipulated: saved
// to a file, securely transmitted to a receiving party, etc. 
SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");
DESKeySpec desSpec = (DESKeySpec)
  desFactory.getKeySpec(desKey, javax.crypto.spec.DESKeySpec.class);
byte[] rawDesKey = desSpec.getKey();  
// Do the same for a DESede key
SecretKeyFactory desEdeFactory = SecretKeyFactory.getInstance("DESede");
DESedeKeySpec desEdeSpec = (DESedeKeySpec)
  desEdeFactory.getKeySpec(desEdeKey, javax.crypto.spec.DESedeKeySpec.class);
byte[] rawDesEdeKey = desEdeSpec.getKey();

// Convert the raw bytes of a key back to a SecretKey object
DESedeKeySpec keyspec = new DESedeKeySpec(rawDesEdeKey);
SecretKey k = desEdeFactory.generateSecret(keyspec);

// For DES and DESede keys, there is an even easier way to create keys
// SecretKeySpec implements SecretKey, so use it to represent these keys
byte[] desKeyData = new byte[8];        // Read 8 bytes of data from a file
byte[] tripleDesKeyData = new byte[24]; // Read 24 bytes of data from a file
SecretKey myDesKey = new SecretKeySpec(desKeyData, "DES");
SecretKey myTripleDesKey = new SecretKeySpec(tripleDesKeyData, "DESede");

Encryption and Decryption with Cipher

Once you have obtained an appropriate SecretKey object, the central class for encryption and decryption is Cipher. Use it like this:


SecretKey key;     // Obtain a SecretKey as shown earlier
byte[] plaintext;  // The data to encrypt; initialized elsewhere

// Obtain an object to perform encryption or decryption
Cipher cipher = Cipher.getInstance("DESede");  // Triple-DES encryption
// Initialize the cipher object for encryption
cipher.init(Cipher.ENCRYPT_MODE, key);
// Now encrypt data
byte[] ciphertext = cipher.doFinal(plaintext);

// If we had multiple chunks of data to encrypt, we can do this
cipher.update(message1);
cipher.update(message2);
byte[] ciphertext = cipher.doFinal();

// We simply reverse things to decrypt
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedMessage = cipher.doFinal(ciphertext);

// To decrypt multiple chunks of data
byte[] decrypted1 = cipher.update(ciphertext1);
byte[] decrypted2 = cipher.update(ciphertext2);
byte[] decrypted3 = cipher.doFinal(ciphertext3);

Encrypting and Decrypting Streams

The Cipher class can also be used with CipherInputStream or CipherOutputStream to encrypt or decrypt while reading or writing streaming data:


byte[] data;                              // The data to encrypt
SecretKey key;                            // Initialize as shown earlier
Cipher c = Cipher.getInstance("DESede");  // The object to perform encryption
c.init(Cipher.ENCRYPT_MODE, key);         // Initialize it

// Create a stream to write bytes to a file
FileOutputStream fos = new FileOutputStream("encrypted.data");

// Create a stream that encrypts bytes before sending them to that stream
// See also CipherInputStream to encrypt or decrypt while reading bytes
CipherOutputStream cos = new CipherOutputStream(fos, c);

cos.write(data);                      // Encrypt and write the data to the file
cos.close();                          // Always remember to close streams
java.util.Arrays.fill(data, (byte)0); // Erase the unencrypted data

Encrypted Objects

Finally, the javax.crypto.SealedObject class provides an especially easy way to perform encryption. This class serializes a specified object and encrypts the resulting stream of bytes. The SealedObject can then be serialized itself and transmitted to a recipient. The recipient is only able to retrieve the original object if she knows the required SecretKey:


Serializable o;           // The object to be encrypted; must be Serializable
SecretKey key;                              // The key to encrypt it with
Cipher c = Cipher.getInstance("Blowfish");  // Object to perform encryption
c.init(Cipher.ENCRYPT_MODE, key);           // Initialize it with the key
SealedObject so = new SealedObject(o, c);   // Create the sealed object

// Object so is a wrapper around an encrypted form of the original object o;
// it can now be serialized and transmitted to another party. 
// Here's how the recipient decrypts the original object
Object original = so.getObject(key);        // Must use the same SecretKey


View catalog information for Java in a Nutshell, 4th Edition.

Return to ONJava.com.