Return-Path: <env_15019588-1203339468@hermes.sun.com>
Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id OAA02516; Tue, 7 May 2002 14:32:55 -0400 (EDT)
Received: from hermes.sun.com (hermes.sun.com [64.124.140.169])
	by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id OAA03994
	for <alexp@mit.edu>; Tue, 7 May 2002 14:29:47 -0400 (EDT)
Date: Tue, 7 May 2002 10:29:47 GMT-08:00
From: "JDC Tech Tips" <body_15019588-1203339468@hermes.sun.com>
To: alexp@mit.edu
Message-Id: <15019588-1203339468@hermes.sun.com>
Subject: JDC Tech Tips, May 7, 2002 (File Channels, Stack Trace Elements) 
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
X-Mailer: Beyond Email 


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
May 7, 2002. This issue covers:

         * File Channels
         * Stack Trace Elements
                 
These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.4.

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2002/tt0507.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
FILE CHANNELS

Channels are a new Java library feature, part of the new I/O 
package (java.nio). The documentation for channels
(http://java.sun.com/j2se/1.4/docs/api/java/nio/channels/Channel.html)
defines a channel like this:

    A channel represents an open connection to an entity such 
    as a hardware device, a file, a network socket, or a program
    component that is capable of performing one or more distinct
    I/O operations, for example reading or writing.

The java.nio package has many important features in it, including
buffers, file channels, other kinds of channels such as socket
channels, and extensible interfaces. In this tip, the focus is on
file channels only. An important benefit of using file channels 
is additional I/O functionality in your applications. This tip 
presents several examples of what this benefit means in practice.

Suppose that you have some legacy data in binary files, and this 
data includes some 16-bit numbers written by the following 
C program:

    /* cd1.c */
    
    #include <stdio.h>
    #include <assert.h>
    
    short data[] = {1234, 2345, 3456, 4567, 5678};
    
    #define SIZESHORT 2
    
    int main()
    {
        int i, j;
    
        /* open data file for writing */
    
        FILE* fp = fopen("data", "wb");
        assert(fp);
   
        /* write out each short value, low byte first */
    
        for (i = 0; i < sizeof(data) / SIZESHORT; i++) 
            {
            short item = data[i];
            for (j = 0; j < SIZESHORT; j++) {
                char c = (char)(item & 0xff);
                fputc(c, fp);
                item >>= 8;
            }
        }
    
        fclose(fp);
    
        return 0;
    }
    
This program writes five 16-bit values to a file. Each value is
written as two bytes, with the low byte written first
(this ordering of low byte first is referred to as 
"little-endian"). Compile, link, and run the C program to produce
the data file.

How do you read this data using a Java program? Here's one way:
    
    import java.io.*;
    
    public class ChannelDemo0 {
        public static void main(String args[]) 
                                   throws IOException {
            FileInputStream fis = 
                           new FileInputStream("data");
            DataInputStream dis = 
                              new DataInputStream(fis);
            short s = dis.readShort();
            System.out.println(s);
            dis.close();
        }
    }

This program uses the DataInputStream class and the readShort 
method of that class. Unfortunately, however, if you run the
ChannelDemo0 program, the result is:

    -11772

This does not correspond to the first value (1234) written into 
the data file. The problem is that the C program writes the short 
values as low byte / high byte, and the readShort method expects 
high byte / low byte.

How do you solve this problem? Here's another approach, one that
reads the values from the data file and computes their sum:

    import java.nio.*;
    import java.nio.channels.*;
    import java.io.*;
    
    public class ChannelDemo1 {
    
        // sum the values of short data items in a file
    
        static short sumFileContents(String fn) 
                                   throws IOException {
    
            // open input stream and get channel
    
            FileInputStream fis = 
                               new FileInputStream(fn);
            FileChannel fc = fis.getChannel();
    
            // map the file into a byte buffer
    
            MappedByteBuffer mbb = fc.map(
                      FileChannel.MapMode.READ_ONLY, 0, 
                      fc.size());
    
            // set byte order to be little-endian
    
            mbb.order(ByteOrder.LITTLE_ENDIAN);
    
            // get short view buffer of byte buffer
    
            ShortBuffer sb = mbb.asShortBuffer();
    
            // sum up the values
    
            short sum = 0;
            while (sb.hasRemaining()) {
                sum += sb.get();
            }
    
            // finish up
    
            fc.close();
            fis.close();
    
            return sum;
        }
    
        public static void main(String args[]) 
                                   throws IOException {
            short sum = sumFileContents("data");
            System.out.println(sum);
        }
    }

The sumFileContents method first creates a FileInputStream, and 
then gets a file channel based on this stream. A similar approach 
is used for output streams (FileOutputStream) or for files that 
are open for both reading and writing (RandomAccessFile).

After the method gets the channel, it maps the data file into a
MappedByteBuffer. This means that the buffer's content is exactly 
the file's content, so reading the buffer fetches bytes from the 
file and writing the buffer stores bytes into the file.

The byte buffer is then specified to be little-endian, changing 
the default order from big-endian. The method then creates a 
short "view buffer" on the byte buffer. The view buffer presents 
a view of the mapped byte buffer as a sequence of short (16-bit) 
values. The short values are each composed of two bytes, and the 
low-order byte is assumed to come first, so as to match what the 
C program writes. The view buffer is backed by the byte buffer. 
So what the method is doing is creating a view of the data file 
as a sequence of little-endian short values. The final step in 
the method is to sum up the values.

Run the ChannelDemo1 program. It should produce the result: 

    17280
   
Let's go on and look at another example of mapping files. Suppose
you want to reverse the bytes in a file. How can you do this? One
simple way uses the following approach:

    import java.nio.*;
    import java.nio.channels.*;
    import java.io.*;
    
    public class ChannelDemo2 {
        public static void main(String args[]) 
                                   throws IOException {
    
            // check command-line argument
    
            if (args.length != 1) {
                System.err.println(
                              "missing file argument");
                System.exit(1);
            }
    
            // get channel
    
            RandomAccessFile raf =
                new RandomAccessFile(args[0], "rw");
            FileChannel fc = raf.getChannel();
    
            // map file to buffer
    
            MappedByteBuffer mbb = fc.map(
                     FileChannel.MapMode.READ_WRITE, 0, 
                     fc.size());
    
            // reverse bytes of file
     
            int len = (int)fc.size();
            for (
               int i = 0, j = len - 1; i < j; i++, j--) 
               {
                byte b = mbb.get(i);
                mbb.put(i, mbb.get(j));
                mbb.put(j, b);
               }
    
            // finish up
    
            fc.close();
            raf.close();
        }
    }

This program opens a channel based on a RandomAccessFile object, 
and maps the file for reading and writing. It then sets up a loop
that exchanges bytes starting at the two ends of the buffer, and 
works toward the middle. Because the MappedByteBuffer is mapped 
to the file on disk, changes to the buffer are reflected in the 
file. If you enter the commands:

    javac ChannelDemo2.java

    java ChannelDemo2 ChannelDemo2.java
 
and then look at the text of ChannelDemo2.java, you will find 
that all of the bytes in ChannelDemo2.java are reversed. In other
words, the first line of the file will be:

}  

and the last line of the file will be:

;*.oin.avaj tropmi 

To restore the ChannelDemo2.java file to its original byte order,
you need to enter the command:

    java ChannelDemo2 ChannelDemo2.java

Mapping a file can be very useful, but it doesn't necessarily 
free you from doing additional work. For example, suppose that 
you map a file to get convenient access to the last few bytes of 
the file. There is a certain amount of intrinsic work that you
need to do in this case, such as doing a disk seek to the end of 
the file, and reading a file block into memory. There's no 
getting around this work. But mapping is certainly convenient, 
and sometimes it can be faster than the alternatives. For example, 
it can help you avoid system call overhead or buffer copies.

Let's look at several ways of copying one file to another using 
file channel features. The first approach is another example of 
mapped files:

    import java.nio.*;
    import java.nio.channels.*;
    import java.io.*;
    
    public class ChannelDemo3 {
        public static void main(String args[]) 
                                   throws IOException {
    
            // check command-line arguments
    
            if (args.length != 2) {
                System.err.println("missing filenames");
                System.exit(1);
            }
    
            // get channels
  
            FileInputStream fis = 
                          new FileInputStream(args[0]);
            FileOutputStream fos = 
                         new FileOutputStream(args[1]);
            FileChannel fcin = fis.getChannel();
            FileChannel fcout = fos.getChannel();
    
            // map input file
    
            MappedByteBuffer mbb = fcin.map(
                     FileChannel.MapMode.READ_ONLY, 0, 
                     fcin.size());
    
            // do the file copy
    
            fcout.write(mbb);
    
            // finish up
    
            fcin.close();
            fcout.close();
            fis.close();
            fos.close();
        }
    }

In this example, the input file channel is mapped to a buffer, 
and then that buffer is written to the output channel. Because 
the buffer represents the whole file, writing the buffer is 
equivalent to copying the file. So if you enter the commands:

    javac ChannelDemo3.java

    java ChannelDemo3 ChannelDemo3.java ChannelCopy3.java
    
It copies the ChannelDemo3 file to ChannelCopy3.java. 

Another way of copying a file looks like this:

    import java.nio.*;
    import java.nio.channels.*;
    import java.io.*;
    
    public class ChannelDemo4 {
        public static void main(String args[]) 
                                   throws IOException {
    
            // check command-line arguments
    
            if (args.length != 2) {
                System.err.println("missing filenames");
                System.exit(1);
            }
    
            // get channels
    
            FileInputStream fis = 
                          new FileInputStream(args[0]);
            FileOutputStream fos = 
                         new FileOutputStream(args[1]);
            FileChannel fcin = fis.getChannel();
            FileChannel fcout = fos.getChannel();
    
            // do the file copy
    
            fcin.transferTo(0, fcin.size(), fcout);
    
            // finish up
    
            fcin.close();
            fcout.close();
            fis.close();
            fos.close();
        }
    }
    
The transferTo method transfers bytes from the source channel 
(fcin) to the specified target channel (fcout). The transfer is 
typically done without explicit user-level reads and writes of 
the channel. The documentation for the transferTo method
(http://java.sun.com/j2se/1.4/docs/api/java/nio/channels/
FileChannel.html#transferTo(long, long, 
java.nio.channels.WritableByteChannel)) says:

    This method is potentially much more efficient than a simple
    loop that reads from this channel and writes to the target
    channel. Many operating systems can transfer bytes directly
    from the filesystem cache to the target channel without
    actually copying them.

In other words, transferTo may rely on special operating system
features that support very fast file transfers.

What does a "regular" file copy look like using file channels? 
Here's an example:

    import java.io.*;
    import java.nio.*;
    import java.nio.channels.*;
   
    public class ChannelDemo5 {
        public static void main(String args[]) 
                                   throws IOException {
    
            // check command-line arguments
    
            if (args.length != 2) {
                System.err.println("missing filenames");
                System.exit(1);
            }
    
            // get channels
    
            FileInputStream fis = 
                           new FileInputStream(args[0]);
            FileOutputStream fos = 
                          new FileOutputStream(args[1]);
            FileChannel fcin = fis.getChannel();
            FileChannel fcout = fos.getChannel();
    
            // allocate buffer
    
            ByteBuffer buf = 
                        ByteBuffer.allocateDirect(8192);
    
            // do copy
    
            long size = fcin.size();
            long n = 0;
            while (n < size) {
                buf.clear();
                if (fcin.read(buf) < 0) {
                    break;
                }
                buf.flip();
                n += fcout.write(buf);
            }
    
            // finish up
    
            fcin.close();
            fcout.close();
            fis.close();
            fos.close();
        }
    }

This program copies one file to another, one buffer at a time. 
Before it reads each chunk of the input file into the buffer, the
program "clears" the buffer. This makes the buffer ready for 
reading by setting the position to 0 and the limit to the 
capacity. Then, after each read, the program "flips" the buffer. 
This makes the buffer ready for writing by setting the limit to 
the current position and the current position to 0.

At this point, you've seen some of the new features offered by 
file channels. There are other features, such as locking, that 
are also important to learn about. Beyond the new features, file 
channels offer significant I/O performance gains. For more 
information about file channels, see New I/O APIs
(http://java.sun.com/j2se/1.4/docs/guide/nio/index.html).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
STACK TRACE ELEMENTS

The standard Java library has long had a mechanism for displaying
stack tracebacks using the Throwable.printStackTrace method. This
method is used to dump the program context for uncaught exceptions. 
The trace is printed on the System.err stream or on a specified
PrintStream or PrintWriter.

A new library feature gives you programmatic access to stack
tracebacks. You can retrieve an array of StackTraceElement 
objects, with each object representing a single stack frame in 
a trace. Let's look at an example to see how this works:

    class A {
        B bref;
    
        void f() {
            bref.g();
        }
    }
    
    class B {
        void g() {}
    }
    
    class C {
        String str;
        int len = str.length();
    }
   
    public class TraceDemo1 {
    
        // dump a single stack trace element
    
        static void dumpTraceElement(
                               StackTraceElement ste) {
            System.err.println("filename = " +
                ste.getFileName());
            System.err.println("line number = " +
                ste.getLineNumber());
            System.err.println("class name = " +
                ste.getClassName());
            System.err.println("method name = " +
                ste.getMethodName());
            System.err.println("is native method = " +
                ste.isNativeMethod());
        }
    
        // dump an array of stack trace elements, 
        // most recent first
    
        static void dumpTrace(Throwable e) {

            // display exception

            System.err.println("Exception: " + e);
            System.err.println();
    
            // display traceback

            StackTraceElement ste[] = e.getStackTrace();
            for (int i = 0; i < ste.length; i++) {
                dumpTraceElement(ste[i]);
                System.err.println();
            }
        }
    
        public static void main(String args[]) {
    
            // call A.f() and trigger an exception
    
            try {
                A aref = new A();
                aref.f();
            }
            catch (Throwable e) {
    
                // display regular stack trace
    
                e.printStackTrace();
                System.err.println();
    
                // dump stack trace in custom format
    
                dumpTrace(e);
            }
    
            System.err.println();
            System.err.println(
                     "==============================");
            System.err.println();
    
            // trigger an exception in initialization
    
            try {
                new C();
            }
            catch (Throwable e) {
                dumpTrace(e);
            }
        }
    }

In this example, the TraceDemo1 program first calls the A.f 
method. That method, in turn, calls B.g. Unfortunately, when B.g 
is called, the B object reference is null. This triggers an 
exception.

The first thing the program prints is the regular stack trace, 
which looks like this:

    java.lang.NullPointerException
        at A.f(TraceDemo1.java:5)
        at TraceDemo1.main(TraceDemo1.java:61)

Then it displays a customized stack traceback, with the exception
name and a sequence of StackTraceElements:

    Exception: java.lang.NullPointerException
    
    filename = TraceDemo1.java
    line number = 5
    class name = A
    method name = f
    is native method = false
    
    filename = TraceDemo1.java
    line number = 61
    class name = TraceDemo1
    method name = main
    is native method = false

Notice that the filename, class name, and method name of the 
first StackTraceElement refer to the point of the exception. The
exception occurred at line 5 of TraceDemo1.java, within method 
A.f.

The second part of the example shows what happens when an 
exception is thrown during instance initialization. In this 
example, the TraceDemo1 program creates a C object. When the C
object is instantiated, it attempts to take the length of the str
string. However, because str is never explicitely initialized, 
the reference to str is a null reference. Here's the output for 
that part of the example:

    Exception: java.lang.NullPointerException
    
    filename = TraceDemo1.java
    line number = 15
    class name = C
    method name = <init>
    is native method = false
    
    filename = TraceDemo1.java
    line number = 83 
    class name = TraceDemo1
    method name = main
    is native method = false

The point of the exception is line 15 of TraceDemo1.java, in the 
C class, in a method called <init>, which is a special name for 
instance initialization methods within the Java virtual machine*. 

You can use the StackTraceElement feature to implement customized 
exception reporting and logging formats. An example of 
a customized logging format is one that limits the stack trace to 
a manageable depth. For example, if there are 500 stack frames, 
you might want to preserve the first ten and the last ten. The 
StackTraceElement information is part of the serialized 
representation of Throwable class instances, so it's available 
for deserialized objects.

For more information about stack trace elements, see the
description of the StackTraceElement class at
http://java.sun.com/j2se/1.4/docs/api/java/lang/StackTraceElement.html.
Also, see section 10.12, Threads and Exceptions, in "The Java(tm) 
Programming Language Third Edition" by Arnold, Gosling, and Holmes 
http://java.sun.com/docs/books/javaprog/thirdedition/.

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing 
policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/ 
http://developer.java.sun.com/berkeley_license.html

* FEEDBACK
  Comments? Send your feedback on the JDC Tech Tips to: 
  jdc-webmaster@sun.com

* SUBSCRIBE/UNSUBSCRIBE
  - To subscribe, go to the subscriptions page,
    (http://developer.java.sun.com/subscription/), choose
    the newsletters you want to subscribe to and click "Update".
  - To unsubscribe, go to the subscriptions page,
    (http://developer.java.sun.com/subscription/), uncheck the
    appropriate checkbox, and click "Update".
  - To use our one-click unsubscribe facility, see the link at 
    the end of this email:
    
- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html


- COPYRIGHT
Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html

This issue of the JDC Tech Tips is written by Glen McCluskey.

JDC Tech Tips 
May 7, 2002

Sun, Sun Microsystems, Java, and Java Developer Connection are 
trademarks or registered trademarks of Sun Microsystems, Inc. 
in the United States and other countries.

* As used in this document, the terms "Java virtual machine" 
  or "JVM" mean a virtual machine for the Java platform.

To use our one-click unsubscribe facility, select the following URL:
http://bulkmail.sun.com/unsubscribe?15019588-1203339468
