Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Technical Tips

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
Print Button

Members Only Requires login

Early Access Members Only

Downloads

Bug Database Members Only
Submit a Bug
View Database

Newsletters
Back Issues
Subscribe

Learning Centers
Articles
Bookshelf
Code Samples
New to Java
Question of the Week
Quizzes
Tech Tips
Tutorials

Forums

Technology Centers
Tech Tips archive

J2ME Tech Tips

February 20, 2001

WELCOME to the Java Developer ConnectionSM (JDC) J2ME Tech Tips, February 20, 2001. This issue covers:

This issue of the JDC J2ME Tech Tips is written by Eric Giguere (http://www.ericgiguere.com), an engineer at iAnywhere Solutions, inc, and author of the book "Java 2 Micro Edition: Professional Developer's Guide."


MAKING CONNECTIONS WITH THE CLDC

As you can tell from its name, the Connected Limited Device Configuration (CLDC) is designed for connected devices. A connected device has the ability to connect to the outside world, whether the connection is serial, infrared, wireless, or something else entirely. How then do you take advantage of this connectivity in your JavaTM applications? The answer is: through a set of classes called the Generic Connection framework, or GCF for short.

The GCF consists of a single factory class, an exception class, and eight interfaces, all part of the javax.microedition.io package. The interfaces model six different types of connections, ranging from simple one-way connections to stream and datagram connections. These abstractions are central to the GCF, and are very similar to the way in which the JDBC abstracts access to a database.

To obtain any kind of connection -- HTTP, socket, infrared, serial -- you use the static methods of the Connector class. For example, you call one of the open methods, which takes a URI (uniform resource indicator -- a more general form of URL) and returns a Connection, the base interface for all six connection types. Here is how to open an HTTP connection:

import javax.microedition.io.*;

ContentConnection conn = 
   (ContentConnection) Connector.open( 
     "http://www.yahoo.com" );

Opening a read-only file requires a slightly different syntax:

import javax.microedition.io.*;

InputConnection conn = 
    (InputConnection) Connector.open( 
      "file:/autoexec.bat", Connector.READ );

There is also a third form of the open method that indicates whether or not the connection should throw exceptions on timeouts.

If all you're interested in is an input or output stream, you can also use the convenience methods openInputStream, openDataInputStream, openOutputStream and openDataOutputStream.

The URI you use to obtain a connection is of the general form

    <protocol>:<address>;<parameters>

where protocol specifies how to connect, address specifies what to connect to, and parameters are connection parameters. While the protocol is a simple string like "http", "mailto", or "ftp", the syntax of the rest of the URI depends on the protocol being used. The Connector class uses the protocol part of the URI to dynamically find and load a class that parses the URI, makes the actual connection, and returns the appropriate Connection object.

It's important to note that the CLDC only defines the classes and interfaces that make up the GCF -- it doesn't require an implementation to support any particular protocol. Profiles that are built on top of the CLDC define which protocols an implementation must support. For example, the Mobile Information Device Profile (MIDP), requires support for the HTTP protocol. Implementations are free to add support for other protocols if they want, however, as long as they fit within the framework. Sun's reference implementation of the CLDC includes support for a number of protocols, including "datagram", "socket", and "file", but they are all meant to be examples for CLDC implementors. For an application to be portable, it must use only the protocols defined by the profile, or else restrict itself to running on a specific J2ME implementation.

The six connection types supported by the GCF are defined by the six extensions of the Connection interface. The Connection interface itself is very simple: it defines a single close method.

The InputConnection interface is used for raw input streams, such as those to read from a socket or a file. The interface adds two methods to the basic Connection, one for opening a raw InputStream and another for opening a DataInputStream. Here is a simple example:

import java.io.*;
import javax.microedition.io.*;

public class TestInput {

    public static void main( String args[] ) {
        try {
            String uri = "file:c:/autoexec.bat";
            InputConnection conn = (InputConnection) 
                     Connector.open( uri, 
                        Connector.READ );
            InputStream in = conn.openInputStream();
            int ch;
   
            conn.close(); // doesn't close input stream!
   
            System.out.println( "Contents of [" + uri + 
                                                 "]" );
   
            while( ( ch = in.read() ) != -1 ){
                System.out.print( (char) ch );
            }
   
            in.close();
        }
        catch( ConnectionNotFoundException e ){
            System.out.println( "File could not be 
                                         found!" );
        }
        catch( IOException e ){
        System.out.println( e.toString() );
        }

        System.exit( 0 );
    }
}

You should run this example using the CLDC reference implementation on your desktop computer because it assumes the existence of the "file" protocol. As mentioned earlier in this tip, the reference implementation supports this protocol, but in general, CLDC does not require an implementation to support any particular connection protocol. Also note that the URI format shown, "file:drive:path" is specific to the Windows platform. Unix/Linux users should use a URI of the form "file:path", as in "file:/usr/lib/foo.txt".

Notice that the program can safely close the connection after it obtains the input stream. In fact, you need to close all streams obtained from before the Connection enters the closed state.

One other note about the program: if it were a MIDlet (that is, an MIDP application), it could not use a System.exit call to terminate. A MIDlet indicates that it is complete by calling MIDlet.notify.Destroyed.

Of course, the opposite of InputConnection is OutputConnection, which you can use to write to a stream:

import java.io.*;
import javax.microedition.io.*;

public class TestOutput {

    public static void main( String[] args ){
        try {
            String uri = "file:c:/hah.txt;append=true";
            OutputConnection conn = (OutputConnection)
                     Connector.open( uri, Connector.WRITE );
            OutputStream out = conn.openOutputStream();
            PrintStream print = new PrintStream( out );
   
            conn.close(); // doesn't close output stream!
 
            print.println( "Hah hah hah!" );
  
            out.close();
        }
        catch( ConnectionNotFoundException e ){
            System.out.println( "File could not be 
                                      created!" );
        }
        catch( IOException e ){
            System.out.println( e.toString() );
        }
 
        System.exit( 0 );
    }
}

Again, run this example with the CLDC reference implementation. Remove the ";append=true" parameter from the URI to reset the file to empty before the writing starts.

What if a particular protocol, such as the "socket" protocol, supports both input and output streams? In this case, use the StreamConnection interface, which extends both InputConnection and OutputConnection. Here's an example:

import java.io.*;
import javax.microedition.io.*;

public class TestInputOutput {

    public static void main( String[] args ){
        try {
            String uri = 
            "socket://www.ericgiguere.com:80";
            StreamConnection conn = (StreamConnection)
                     Connector.open( uri );
     
            // Send HTTP request...

            PrintStream out = new PrintStream( 
                                conn.openOutputStream() );
            out.println( "GET /index.html HTTP/0.9\r\n" );
            out.flush();
   
            // Get raw HTTP reply...

            InputStream in = conn.openInputStream();
            int ch;

            while( ( ch = in.read() ) != -1 ){
                System.out.print( (char) ch );
            }

            in.close();
            out.close();
            conn.close();
        }
        catch( ConnectionNotFoundException e ){
            System.out.println( "Socket could not be 
					  opened" );
        }
        catch( IOException e ){
            System.out.println( e.toString() );
        }

        System.exit( 0 );
    }
}

The TestInputOutput example opens a socket connection to a web server and sends an HTTP GET request. It then waits for the server to respond. A simpler way to do this is to use the ContentConnection interface and the "testhttp" protocol. That protocol is a simple (and incomplete) implementation of the HTTP protocol. Here's an example of the ContentConnection and testhttp approach;

import java.io.*;
import javax.microedition.io.*;

public class TestHTTP {

    public static void main( String[] args ){
        try {
            String uri = 
              "testhttp://www.ericgiguere.com/index.html";
            ContentConnection conn = (ContentConnection)
               Connector.open( uri );

            InputStream in = conn.openInputStream();
            int ch;
   
            System.out.println( "Content type is " 
                               + conn.getType() );

            while( ( ch = in.read() ) != -1 ){
                System.out.print( (char) ch );
            }

            in.close();
            conn.close();
        }
        catch( ConnectionNotFoundException e ){
            System.out.println( 
                   "URI could not be opened" );
        }
        catch( IOException e ){
            System.out.println( e.toString() );
        }

        System.exit( 0 );
    }
}

The ContentConnection interface extends StreamConnection and adds three new methods for accessing some of the basic HTTP response data: getType, getEncoding and getLength. By itself, ContentConnection isn't particularly useful. The Mobile Information Device Profile (MIDP), for example, subclasses ContentConnection to define a new connection interface called HttpConnection that includes full support for the HTTP protocol. This is covered in the December 18, 2000 J2ME Tech Tip "Making HTTP Connections with MIDP".

The final two Connection interfaces are very specialized and only worth a brief mention. The StreamConnectionNotifier interface is for writing server applications and has a single method called acceptAndOpen. An application calls this method to wait for a client to connect to it. The method then returns a StreamConnection that can be used to communicate with the client. The CLDC reference implementation supports the "serversocket" protocol for opening server sockets using this mechanism. There is also a DatagramConnection interface for sending and receiving datagrams, using the related Datagram interface to pack and unpack the data. A DatagramConnection is modeled after the java.net.DatagramSocket class in the J2SETM platform.


RECORD MANAGEMENT SYSTEM BASICS

The Mobile Information Device Profile (MIDP) defines a set of classes for storing and retrieving data. These classes are called the Record Management System (RMS). With the RMS, it's possible to make data persist across invocations of a MIDlet (an MIDP application). Different MIDlets in the same MIDlet suite can also use the RMS to share data. (A MIDlet suite is a set of MIDlets packaged together into a single JAR file.)

The basic storage mechanism used by the RMS is referred to as a record store. A record store is a collection of records, and a record is basically a byte array of arbitrary data. The size of the byte array can vary for each record. The RMS doesn't understand the contents of a record and doesn't place any restrictions on what goes into the record. The RMS does do some rudimentary bookkeeping, however, such as assigning each record a unique identifier that is valid for the lifetime of the record store.

A record store is represented by a javax.microedition.rms.RecordStore object. The only way to obtain a RecordStore object is to use the openRecordStore method, as in:

import javax.microedition.rms.*;

RecordStore rs = null;

try {
    rs = RecordStore.openRecordStore( "mydata", false );
}
catch( RecordStoreNotFoundException e ){
    // doesn't exist
}
catch( RecordStoreException e ){
    // some other error
}

The first parameter of the openRecordStore method is the name of the record store, which must be no longer than 32 characters. The name must only be unique within the MIDlet suite (MIDlets outside the suite cannot open them). The second parameter indicates whether to create the record store if it doesn't already exist, or to throw a RecordStoreNotFoundException instead. RecordStoreNotFoundException extends RecordStoreException, the root class for all RMS exceptions. A RecordStoreException is thrown if a record store can't be created because there is not enough space or because of some other internal error.

At any given time, a single instance of a RecordStore object exists for each record store. If two or more MIDlets in the same suite open the same record store, they both get a reference to the same object instance.

After a record store has been opened, you can close it by calling the closeRecordStore method:

try {
    rs.closeRecordStore();
}
catch( RecordStoreNotOpenException e ){
    // already closed
}
catch( RecordStoreException e ){
    // some other error
}

The record store is not actually closed until you call the closeRecordStore method as many times as you called the openRecordStore method to open it.

You can add records to an open record store using the addRecord method:

byte[] data = new byte[2];

data[0] = 0;
data[1] = 1;

try {
    int id = rs.addRecord( data, 0, data.length );
}
catch( RecordStoreFullException e ){
    // no room left for more data
}
catch( RecordStoreNotOpenException e ){
    // store has been closed
}
catch( RecordStoreException e ){
    // general error
}

The value returned by addRecord is the unique identifier for the new record. The record identifiers (IDs) start at 1 and increase each time a record is added. Use the getNextRecordID method to obtain the record ID for the next record to be added.

You can delete a record by calling the deleteRecord method, which takes the record ID as its only parameter.

The getRecord method reads the contents of a particular record. There are two forms of method. One form takes the record ID as its only parameter and returns a byte array with the record data. The second form fills an existing byte array with the record contents, as in:

byte[] data = new byte[100];
int    id = ....; // get the ID from somewhere

try {
    int numBytes = rs.getRecord( id, data, 0 );
}
catch( ArrayIndexOutOfBoundsException e ){
    // record too big for the array
}
catch( InvalidRecordIDException e ){
    // record doesn't exist
}
catch( RecordStoreNotOpenException e ){
    // store has been closed
} 
catch( RecordStoreException e ){
    // general error
}

The first parameter is the record ID, the second parameter is the byte array to copy the contents into, and the third parameter is the offset at which to start the copying. The byte array is assumed to be long enough to hold the record contents (you can use the getRecordSize method to obtain the size of a particular record). Use the second form of getRecord whenever possible, this form uses less memory than the first form, and does not overextend the garbage collector -- something particularly important when enumerating through an entire record store.

You can replace a particular record with the setRecord method:

byte[] data = new byte[3];
int    id = ....; // get the ID from somewhere

data[0] = 0;
data[1] = 1;
data[2] = 2;

try {
    rs.setRecord( id, data, 0, data.length );
}
catch( RecordStoreFullException e ){
    // no room left for more data
}
catch( InvalidRecordIDException e ){
    // record doesn't exist
}
catch( RecordStoreNotOpenException e ){
    // store has been closed
}
catch( RecordStoreException e ){
    // general error;
}

Note that there is no way to modify part of the data in a record. You can only replace it with a new byte array.

Now that you've learned the basics of how to create and use a record store, let's look at a fuller example. Here's a MIDlet that prints the contents of an arbitrary record store to an output stream.

import java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.rms.*;

public class TestStore extends MIDlet {

    static final String DBNAME = "mydata";

    public TestStore() {

        RecordStore rs = null;

        // Data is persistent across MIDlet invocations.
        // So, first clear out the old record store...

        try {
            RecordStore.deleteRecordStore( DBNAME );
        }
        catch( Exception e ){
            // ignore any errors...
        }

        // Now create a new one and dump 
	// each element out....

        try {
            rs = RecordStore.openRecordStore( DBNAME, 
					      true );

            byte[] data1 = "Here is the first
			    record".getBytes();
            byte[] data2 = "And here is the 
                            second".getBytes();
            byte[] data3 = "And then the 
			    third".getBytes();

            data3[0] = 0;
            data3[data3.length-1] = (byte) -1;

            rs.addRecord( data1, 0, data1.length );
            rs.addRecord( data2, 0, data2.length );
            rs.addRecord( data3, 0, data3.length );

            dumpRecordStore( rs, System.out );

            rs.closeRecordStore();
        }
        catch( RecordStoreException e ){
            System.out.println( e );
        }

        notifyDestroyed();
    }

    public void dumpRecordStore( RecordStore rs, 
				 PrintStream out )
    {
        if( rs == null ) return;

        StringBuffer hexLine = new StringBuffer();
        StringBuffer charLine = new StringBuffer();

        try {
            int    lastID = rs.getNextRecordID();
            byte[] data = new byte[100];
            int    size;

            for( int i = 1; i < lastID; ++i ){
                try {
                    size = rs.getRecordSize( i );
                    if( size > data.length ){
                        data = new byte[ size * 2 ];
                    }

                    out.println( "Record " + i + 
			      " of size " + size );

                    rs.getRecord( i, data, 0 );
                    dumpRecord( data, size, out, 
			   hexLine, charLine, 16 );

                    out.println( "" );
                }
                catch( InvalidRecordIDException e ){
                    continue;
                }
            }
        }
        catch( RecordStoreException e ){
            out.println( "Exception reading record 
 				      store: " + e );
        }
    }
 
    private void dumpRecord( byte[] data, int size,
                             PrintStream out,
                             StringBuffer hexLine,
                             StringBuffer charLine,
                             int maxLen )
    {
        if( size == 0 ) return;

        hexLine.setLength( 0 );
        charLine.setLength( 0 );

        int count = 0;

        for( int i = 0; i < size; ++i ){
            char b = (char) ( data[i] & 0xFF );

            if( b < 0x10 ){
                hexLine.append( '0' );
            }

            hexLine.append( Integer.toHexString( b ) );
            hexLine.append( ' ' );

            if( ( b >= 32 && b <= 127 ) ||
                Character.isDigit( b ) ||
                Character.isLowerCase( b ) ||
                Character.isUpperCase( b ) ){
                charLine.append( (char) b );
            } else {
                charLine.append( '.' );
            }

            if( ++count >= maxLen || i == size-1 ){
                while( count++ < maxLen ){
                hexLine.append( "   " );
                }

                hexLine.append( ' ' );
                hexLine.append( charLine.toString() );

                out.println( hexLine.toString() );
  
                hexLine.setLength( 0 );
                charLine.setLength( 0 );
                count = 0;
            }
        }
    }

    public void destroyApp( boolean unconditional ) {
    }

    public void startApp() {
    }

    public void pauseApp() {
    }
}

When you run TestStore using the MIDP reference implementation, you should see the following printed in your console window:

Record 1 of size 24
48 65 72 65 20 69 73 20 74 68 65 20 66 69 72 73  Here is the firs
74 20 72 65 63 6f 72 64                          t record 
						
Record 2 of size 22
41 6e 64 20 68 65 72 65 20 69 73 20 74 68 65 20  And here is the 
73 65 63 6f 6e 64                                second
						 
Record 3 of size 18
00 6e 64 20 74 68 65 6e 20 74 68 65 20 74 68 69  And then the thi
72 ff                                            rd

— Note —

Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun MicrosystemsTM purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click the Update button.

— Subscribe —

To subscribe to a JDC newsletter mailing list, go to the Subscriptions page (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to, and click Update.

— Feedback —

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

— Archives —

You'll find the J2ME Tech Tips archives at:

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

— Copyright —

Copyright 2001 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

J2ME Tech Tips February 20, 2001


Print Button
[ This page was updated: 4-Jun-2001 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2001 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.