Received: from PACIFIC-CARRIER-ANNEX.MIT.EDU by po10 (5.61/4.7) id AA09325; Tue, 9 May 00 16:11:28 EDT
Received: from hermes.javasoft.com by MIT.EDU with SMTP
	id AA26578; Tue, 9 May 00 16:15:27 EDT
Received: (from nobody@localhost)
	by hermes.java.sun.com (8.9.3+Sun/8.9.1) id UAA09688;
	Tue, 9 May 2000 20:14:37 GMT
Date: Tue, 9 May 2000 20:14:37 GMT
Message-Id: <200005092014.UAA09688@hermes.java.sun.com>
X-Authentication-Warning: hermes.java.sun.com: Processed from queue /bulkmail/data/ed_97/mqueue2
X-Mailing: 203
From: JDCTechTips@sun.com
Subject: JDC Tech Tips  May 9, 2000
To: JDCMember@sun.com
Reply-To: JDCTechTips@sun.com
Errors-To: bounced_mail@hermes.java.sun.com
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
X-Mailer: Beyond Email 2.2


 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 9, 2000. This issue covers:

         * Random Access for Files
         * Using Adapters
         
These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.2.2.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

RANDOM ACCESS FOR FILES

If you've used the standard Java package java.io, you're probably
familiar with I/O classes such as InputStream and BufferedReader. 
These classes support sequential I/O on files. That is, you read 
a file starting at the beginning, or write a file from its 
beginning or by appending to it.

The class java.io.RandomAccessFile operates a little differently. 
It supports random access, that is, access where you can set 
a file pointer to an arbitrary offset (represented as a 64-bit 
long value), and then perform I/O from that position.

Unlike classes such as FileInputStream, RandomAccessFile is not 
part of the InputStream/OutputStream hierarchy; you can't say:

    InputStream istr = new RandomAccessFile(...);
    
RandomAccessFile identifies file locations according to byte
offsets. In the Java(tm) language, bytes and characters are 
distinct. A character is made up of two bytes, so a particular 
byte offset in a file of characters doesn't necessarily represent 
a location of a character.

The RandomAccessFile class implements the DataInput and DataOutput
interfaces. These support methods like readInt and writeUTF to read
and write standard data types in a uniform way. For example, if you
use writeInt to write an integer to a file, you can then use readInt
to read the integer back from the file, with byte-ordering issues
automatically handled for you.

To see how RandomAccessFile might be useful, consider an application
with a large legacy database of fixed-length records. These records
are accessed in random order. For example, the records might
represent a hash table on disk, or some type of complex linked data
structure. The application needs to read a specific record in the
database without having to read all the records before it.

Here is an illustration of this application using two programs, the 
first is a C program that writes a database. The database represents 
the legacy part of the application. The format of records in the 
database is:

    number of records as a two-byte short

    name of a person, up to 25 bytes, unused bytes 0 filled
    birthdate month as a two-byte short
    birthdate day as a two-byte short
    birthdate year as a two-byte short
    ...

The C program looks like this:

    #include <stdio.h>
    #include <string.h>
    
    /* structure of a record */
    
    struct Rec {
        char* name;
        short month;
        short day;
        short year;
    };
    
    /* birthdate/name record */
    
    struct Rec data[] = {
        {"Jane Jones", 3, 17, 1959},
        {"Bill Smith", 2, 27, 1947},
        {"Maria Thomas", 12, 23, 1954},
        {"Mortimer Smedley Williams", 9, 24, 1957},
        {"Jennifer Garcia Throckmorton", 11, 9, 1963} 
    };
    short NUMVALUES = sizeof(data) / sizeof(struct Rec);
    
    /* write a short to a file */
    
    void writeShort(FILE* fp, short s) {
        fputc((s >> 8) & 0xff, fp);
        fputc(s & 0xff, fp);
    }
    
    /* write a sequence of bytes to a file */
    
    void writeBytes(FILE* fp, char* buf, size_t n) {
        fwrite(buf, 1, n, fp);
    }
    
    int main() {
        int i;
    
        /* open the output file */
    
        FILE* fpout = fopen("out.data", "wb");
        if (fpout == NULL) {
            fprintf(stderr, "Cannot open output file\n");
            return 1;
        }
    
        /* write out the number of values */
    
        writeShort(fpout, NUMVALUES);
    
        /* write the data to the file */
    
        for (i = 0; i < NUMVALUES; i++) {
            struct Rec* p = &data[i];
            char outbuf[25];
    
            /* write the name, truncating if necessary */
    
            strncpy(outbuf, p->name, sizeof outbuf);
            writeBytes(fpout, outbuf, sizeof outbuf);
    
            /* write month/day/year */
    
            writeShort(fpout, p->month);
            writeShort(fpout, p->day);
            writeShort(fpout, p->year);
        }
    
        fclose(fpout);
    
        return 0;
    }

You need to be careful about byte ordering when you write data to 
a file for later reading by another application. For example, when 
the above program writes out short values to the file, it must 
ensure that the two bytes of the short are written in the order 
that RandomAccessFile.readShort expects them -- high byte first, 
then low byte. You can't use readShort to read a legacy database 
that has values whose bytes are reversed -- low byte first, then 
high byte. You'd need to read raw bytes and assemble the short 
value yourself.

The Java program that reads the file looks like this:

    import java.io.RandomAccessFile;
    import java.io.IOException;
    
    public class RAFDemo {
    
        // starting offset in data file, past the count of records
    
        static final int STARTING_OFFSET = 2;
    
        // length of a name
    
        static final int NAME_LENGTH = 25;
    
        // bytes in a record (name length + three shorts)
    
        static final int BYTES_IN_RECORD = NAME_LENGTH + 2 + 2 + 2;
    
        public static void main(String args[]) throws IOException {
            RandomAccessFile raf =
                new RandomAccessFile("out.data", "r");
    
            // read the number of data records
    
            short numvalues = raf.readShort();
    
            byte namebuf[] = new byte[NAME_LENGTH];
    
            // read each record, going backwards through the file
    
            for (int i = numvalues - 1; i >= 0; i--) {
    
                // seek to the record
    
                raf.seek(STARTING_OFFSET + i * BYTES_IN_RECORD);
    
                // read the name as a vector of bytes
    
                raf.read(namebuf);
    
                // convert the name to a string
    
                StringBuffer namesb = new StringBuffer();
                for (int j = 0; j < NAME_LENGTH; j++) {
                    if (namebuf[j] == 0) {
                        break;
                    }
                    else {
                        namesb.append((char)namebuf[j]);
                    }
                }
    
                // read month/day/year
    
                short month = raf.readShort();
                short day = raf.readShort();
                short year = raf.readShort();
    
                // display the results
    
                System.out.println(namesb.toString() + " " +
                    month + " " + day + " " + year);
            }
        }
    }

To demonstrate that it can access parts of the database at
random, the program reads the file records in backwards order
and then displays the records. If you run the program, output is:

    Jennifer Garcia Throckmor 11 9 1963
    Mortimer Smedley Williams 9 24 1957
    Maria Thomas 12 23 1954
    Bill Smith 2 27 1947
    Jane Jones 3 17 1959

The first name has been truncated to 25 characters, to fit the
requirements of record layout stated above.

One issue related to RandomAccessFile concerns strings. The
RAFDemo example just presented reads a legacy database. Strings 
in a database record, such as "Jane Jones", are represented 
in RAFDemo as a sequence of bytes. To convert the bytes to 
a string, the program converts each byte to a character and then 
appends it to a StringBuffer.

Suppose, however, that you want to use RandomAccessFile with full
16-bit Unicode characters. How can you do this? One way is to use
the readUTF and writeUTF methods found in DataInput and 
DataOutput. These represent strings as UTF sequences; 7-bit ASCII 
characters are represented as themselves, and other characters as 
two or three bytes. The methods read or write two bytes of length 
information, followed by the UTF representation as a stream of 
bytes. Because the length is stored as two bytes, you cannot write 
extremely long strings this way.

Another approach to writing strings is to use the writeChars 
method. This method writes a sequence of characters (there's no 
readChars method). If you use writeChars, you need to write out 
the string length first using writeInt.

If you use RandomAccessFile to access fixed-length records
containing strings, you need to determine how you're going to
represent the records. The complication is that strings are 
typically variable in length. You need to either truncate strings, 
so that all are a fixed length (as in the example above), or 
represent the strings in a separate file and record string numbers 
in the actual records. For example, you might use an integer field 
in a fixed-length record to store a value "37", where 37 represents 
the 37th string in a separate file.

For further information about java.io.RandomAccessFile, see "The 
Java(tm) Language Specification" section 22.23. Also see 
"Java 2(tm) Platform, Standard Edition, v 1.2.2 API: Specification: 
Class RandomAccessFile"
(http://java.sun.com/products//jdk/1.2/docs/api/java/io/RandomAccessFile.html). 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

USING ADAPTERS

Suppose that you're doing some programming in the Java language, 
and you have a class that implements an interface. Let's call the 
interface A:

    interface A {
        public void f1();
        public void f2();
        public void f3();
        public void f4();
        public void f5();
    }

The interface has five methods, but you're only interested in the
first one, f1. So you say:

    class B implements A {
        public void f1() {/*...*/}
    }

Unfortunately, this usage is invalid, and results in a compile
error. For a class to implement an interface, it must define all
the interface's methods. If you make B abstract, the error goes
away, but you can't create objects of an abstract class. If you
extend B to a non-abstract class C, you're presented with the 
error again.

To deal with this problem, use "adapter" classes. An adapter
class for A looks like this:

    abstract class A_ADAPTER implements A {
        public void f1() {}
        public void f2() {}
        public void f3() {}
        public void f4() {}
        public void f5() {}
    }

Notice that the adapter class implements all the methods of the
interface as dummy methods that do nothing. If you want to extend 
f1 to provide your own functionality, you then say:

    class B extends A_ADAPTER {
        public void f1() {/*...*/}
    }

In this case, a user of class B gets the overriding version of the 
f1 method, and the dummy version of the f2-f5 methods found in the 
abstract class A_ADAPTER.

One place where adapters are used is AWT event handling. The
WindowListener interface specifies seven events that relate to
window handling, and WindowAdapter defines dummy methods for these
events:

    public void windowOpened(WindowEvent e) {}
    public void windowClosing(WindowEvent e) {}
    public void windowClosed(WindowEvent e) {}
    public void windowIconified(WindowEvent e) {}
    public void windowDeiconified(WindowEvent e) {}
    public void windowActivated(WindowEvent e) {}
    public void windowDeactivated(WindowEvent e) {}

WindowAdapter provides dummy implementations of all the methods 
that are specified by WindowListener. So if you extend the 
WindowAdapter class, you only need to provide implementations
of methods whose default functionality you want to override.

Here's an example that extends WindowAdapter:

    import java.awt.event.*;
    import javax.swing.*;
    
    public class AdapterDemo {
        public static void main(String args[]) {
    
            // set up a frame
    
            JFrame frame = new JFrame("AdapterDemo");
    
            // set up listeners for window iconification
            // and for window closing
    
            frame.addWindowListener(new WindowAdapter() {
                public void windowIconified(WindowEvent e) {
                    System.out.println(e);
                }
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
    
            // set up panels and labels
    
            JPanel panel = new JPanel();
            JLabel label = new JLabel("This is a test");
            panel.add(label);
    
            // position and display frame
    
            frame.getContentPane().add(panel);
            frame.setSize(300, 200);
            frame.setLocation(200, 200);
            frame.setVisible(true);
        }
    }
    
This particular example sets up listeners for the WindowClosing 
and WindowIconified (minimized) events, and ignores the others. 
Notation like:

    frame.addWindowListener(new WindowAdapter() {...});

defines an anonymous inner class that extends WindowAdapter.

Similar adapter classes are used for keyboard and mouse event
handling.

For further information about adapters, see the section "AWT 
Adapters" in Chapter 9 of the book "Graphic Java - Mastering the
JFC, 3rd Edition, Volume 1, AWT", by David Geary. Also see 
"Java 2(tm) Platform, Standard Edition, v 1.2.2 API: Specification: 
Class WindowAdapter"
(http://java.sun.com/products//jdk/1.2/docs/api/java/awt/event/WindowAdapter.html).

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

- NOTE
The names on the JDC mailing list are used for internal Sun
Microsystems(tm) purposes only. To remove your name from the list,
see Subscribe/Unsubscribe below.


- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com


- SUBSCRIBE/UNSUBSCRIBE
The JDC Tech Tips are sent to you because you elected to subscribe
when you registered as a JDC member. To unsubscribe from JDC email,
go to the following address and enter the email address you wish to
remove from the mailing list:

http://developer.java.sun.com/unsubscribe.html


To become a JDC member and subscribe to this newsletter go to:

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


- ARCHIVES
You'll find the JDC Tech Tips archives at:

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


- COPYRIGHT
Copyright 2000 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://developer.java.sun.com/developer/copyright.html


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

JDC Tech Tips 
May 9, 2000














