
 J 2 M E    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC)
Java(tm) 2 Platform, Micro Edition (J2ME(tm)) 
Tech Tips, for March 25, 2002. This issue covers:

     * Simple Store-and-Forward Messaging for MIDP Applications
     * Compressing XML For Faster Wireless Networking
      
The J2ME Tech Tips are written by Eric Giguere
(http://www.ericgiguere.com), an engineer at iAnywhere 
Solutions, inc. Eric is the author of the book "Java 2 Micro
Edition: Professional Developer's Guide" and co-author of the 
book "Mobile Information Device Profile for Java 2 Micro 
Edition," both books in John Wiley & Sons' Professional 
Developer's Guide series.

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
SIMPLE STORE-AND-FORWARD MESSAGING FOR MIDP APPLICATIONS

Applications written for the Mobile Information Device Profile 
(MIDP) are rarely stand-alone applications. Normally, there's 
some part of the application that involves calling server-side 
code for processing. In MIDP 1.0, the only way to portably invoke 
server-side code is by making HTTP requests to a servlet running
in an external Web server. On a wireless device, this can be 
quite slow, and so the communication is best done on a background 
thread. In fact, it might make more sense to move to a truly 
asynchronous communication model, where your application 
interacts with other devices by sending and receiving messages. 
This Tech Tip shows you how to implement this kind of simple 
store-and-forward messaging system using MIDP's persistent record 
stores.  To run the code in this tip, you need Tomcat or another 
servlet-enabled Web server. You can download Tomcat from the 
Jakarta Project (http://jakarta.apache.org/tomcat/). 

Let's first define the term "message".  A message is simply 
a class that holds some data, as in this example:

import java.io.*;

public class SimpleMessage implements Persistent {
    private String dest;
    private Object data;

    public SimpleMessage(){
    }

    public SimpleMessage( String dest, String data ){
        setDestination( dest );
        setData( data );
    }

    public SimpleMessage( String dest, byte[] data ){
        setDestination( dest );
        setData( data );
    }

    public String getDestination(){
        return dest != null ? dest : "";
    }

    public Object getData(){ return data; }

    public void setDestination( String dest ){
        this.dest = dest;
    }

    public void setData( String data ){
        this.data = data;
    }

    public void setData( byte[] data ){
        this.data = data;
    }

    private static final int IS_NULL = 0;
    private static final int IS_STRING = 1;
    private static final int IS_BINARY = 2;

    public byte[] persist() throws IOException {
        ByteArrayOutputStream bout = 
                           new ByteArrayOutputStream();
        DataOutputStream dout = 
                          new DataOutputStream( bout );

        dout.writeUTF( getDestination() );

        Object obj = getData();
        if( obj instanceof String ){
            dout.writeInt( IS_STRING );
            dout.writeUTF( (String) obj );
        } else if( obj instanceof byte[] ){
            dout.writeInt( IS_BINARY );
            byte[] arr = (byte []) obj;
            dout.writeInt( arr.length );
            dout.write( arr );
        } else {
            dout.writeInt( IS_NULL );
        }
        dout.flush();

        return bout.toByteArray();
    }

    public void resurrect( byte[] indata ) 
                                   throws IOException {
        ByteArrayInputStream bin =
                    new ByteArrayInputStream( indata );
        DataInputStream din = 
                            new DataInputStream( bin );

        dest = din.readUTF();

        int type = din.readInt();
        if( type == IS_STRING ){
            data = din.readUTF();
        } else if( type == IS_BINARY ){
            int len = din.readInt();
            byte[] arr = new byte[ len ];
            if( len > 0 ){
                din.readFully( arr );
            }
            data = arr;
        } else {
            data = null;
        }
    }

    public String toString(){
        StringBuffer buf = new StringBuffer();
        buf.append( "{destination=\"" );
        if( dest != null ) buf.append( dest );
        buf.append( "\",data=" );
        if( data instanceof byte[] ){
            buf.append( "byte array of length " +
              ((byte[]) data).length );
        } else if( data instanceof String ){
            buf.append( '"' );
            buf.append( (String) data );
            buf.append( '"' );
        } else {
            buf.append( "null" );
        }
        buf.append( '}' );
        return buf.toString();
    }
}

The SimpleMessage class holds two things: a destination name
(a string) and some data, which is either a string or a byte 
array. The format of the destination name is arbitrary. It could, 
for example, be the name of a Java Message Service (JMS) queue. 
Or it could be an email address. Later on in this tip you'll see
that a servlet is responsible for interpreting the destination
name.

Notice that the class implements the Persistent interface. This
interface was defined in a previous Tech Tip, "Object 
Serialization in CLDC-Based Profiles" (see
http://java.sun.com/jdc/developer/J2METechTips/2002/tt0226.html). 
You need this capability to serialize the message for storage and 
transport. 

The first step in building a simple store-and-forward messaging 
system is to build the message hub. The hub is the class that the 
application uses to send and receive messages. The hub uses two 
record stores, one to track incoming messages, and the other to 
track outgoing messages. The messages themselves are sent and 
received by a background thread. Using the hub is fairly simple. 
The constructor takes two parameters: the URL of a servlet and 
a timeout value (in milliseconds) for polling:

    String url = 
          "http://localhost:8080/MessagingServlet";
    int timeout = 60000; // every minute
    MessageHub hub = new MessageHub( url, timeout );

You then start the hub's background thread:

    hub.start();

Now the application can send and receive messages. It sends 
messages using the send method:

    SimpleMessage msg = new SimpleMessage( "eric",
                          "Hello!" );
    hub.send( msg );

This send method does not block, it serializes the message and 
adds it to a record store. The hub's background thread is then 
responsible for delivering it. 

Receiving a message, however, is a blocking operation:

    int timeout = 5000; // wait at most 5 seconds
    SimpleMessage msg = hub.receive( timeout );

If the hub receives a message, it returns it immediately. 
Otherwise the calling thread is blocked until a message arrives 
or the given timeout expires.

To suspend message processing, you can call the hub's stop 
method:

    hub.stop();
    
Finally, when the application is about to terminate, it should 
destroy to hub in order to free the hub's resources:

    hub.destroy();

Let's look at the code for the message hub:

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

/**
 * Defines a class that can send and receive
 * messages asynchronously. Messages are stored
 * in record stores and processed by a background
 * thread, which posts them via HTTP to a servlet
 * running on an external web server.
 */

public class MessageHub implements Runnable {

    // Constructor creates two record stores: one for
    // outgoing messages and one for incoming messages.
    // Pass in the URL to the servlet and a timeout 
    // value in milliseconds for how often to poll the 
    // server if no messages are being sent by the 
    // client.

    public MessageHub( String url, int pullTimeout )
                       throws RecordStoreException {
        inrs = RecordStore.openRecordStore( "mhubin",
                                            true );
        outrs = RecordStore.openRecordStore( "mhubout",
                                             true );
        this.url = url;
        this.pullTimeout = pullTimeout;
    }

    // Convenience method.

    private HttpConnection closeConnection(
                                 HttpConnection conn ){
        try {
            if( conn != null ){
                conn.close();
            }
        }
        catch( IOException e ){
        }

        return null;
    }

    // Client calls this to receive a message, passing
    // in an appropriate timeout value in milliseconds.
    // If the timeout expires, null is returned. 
    // Otherwise the message is returned.

    public SimpleMessage receive( int timeout )
             throws IOException, RecordStoreException {
        SimpleMessage msg = null;
        RecordEnumeration enum = null;

        synchronized( inrs ){
            try {
                if( inrs.getNumRecords() == 0 ){
                    inrs.wait( timeout );
                }

                enum = inrs.enumerateRecords( null,
                               null, false );
                if( enum.hasNextElement() ){
                    int id = enum.nextRecordId();
                    byte[] rawdata =
                               inrs.getRecord( id );
                    SimpleMessage tmp =
                               new SimpleMessage();
                    tmp.resurrect( rawdata );
                    msg = tmp;
                    inrs.deleteRecord( id );
                }
            }
            catch( InterruptedException e ){
            }
            finally {
                if( enum != null ){
                    enum.destroy();
                }
            }
        }

        return msg;
    }

    // Client calls this to send a message. The
    // message is queued in the outgoing queue and
    // the background thread is woken up if necessary.

    public int send( SimpleMessage msg ) 
             throws IOException, RecordStoreException {
        int id = 0;

        synchronized( outrs ){
            byte[] rawdata = msg.persist();
            id = outrs.addRecord( 
                          rawdata, 0, rawdata.length );
            outrs.notify();
        }

        return id;
    }

    // The background thread that reads messages
    // from the outgoing queue, POSTs them to the
    // server and places incoming messages into
    // the incoming queue.

    public void run(){
        HttpConnection conn = null;
        boolean        checkForMore = true;

        while( thread == Thread.currentThread() ){
            byte[] record = null;
            int    recordID = 0;

            // First stage: read a record from the
            // outgoing store.  If there is no record,
            // wait for a specific time.
            
            synchronized( outrs ){
                try {
                    if( outrs.getNumRecords() == 0
                        && !checkForMore ){
                        outrs.wait( pullTimeout );
                    }

                    RecordEnumeration e =
                        outrs.enumerateRecords( null,
                                       null, false );
                    while( e.hasNextElement() ){
                        recordID = e.nextRecordId();
                        record =
                           outrs.getRecord( recordID );
                        if( record != null ){
                            outrs.setRecord( recordID,
                                         null, 0, 0 );
                            break;
                        } else {
                            recordID = 0;
                            record = null;
                        }
                    }
                    e.destroy();
                }
                catch( RecordStoreException e ){
                    break;
                }
                catch( InterruptedException e ){
                }
            }

            // Second stage: POST the record, if any,
            // to the web server.  If successful,
            // delete the record, otherwise restore it.

            try {
                conn = (HttpConnection)
                           Connector.open( url );
                conn.setRequestMethod( conn.POST );
                conn.setRequestProperty( 
                          "Content-Type", 
                          "application/octet-stream" );
                conn.setRequestProperty( "User-Agent",
           "Profile/MIDP-1.0 Configuration/CLDC-1.0" );
                conn.setRequestProperty( 
                     "Content-Length",
                     Integer.toString( record != null ?
                                record.length : 0 ) );

                OutputStream os =
                                conn.openOutputStream();
                if( record != null ){
                    os.write( record );
                }
                os.close();

                int rc = conn.getResponseCode();
                if( rc == HttpConnection.HTTP_OK ){
                    if( recordID != 0 ){
                        try {
                            outrs.deleteRecord( 
                                           recordID );
                        }
                        catch( 
                             RecordStoreException e ){
                        }
                    }
                }
            }
            catch( IOException e ){
                if( record != null ){
                    try {
                        outrs.setRecord( recordID, 
                            record, 0, record.length );
                    }
                    catch( RecordStoreException re ){
                    }
                }

                conn = closeConnection( conn );
            }

            recordID = 0;
            record = null;
            checkForMore = false;

            // Third stage: if the POST was successful,
            // read in an incoming record, if any.

            if( conn != null ){
                DataInputStream is = null;

                try {
                    int len = (int) conn.getLength();
                    if( len > 0 ){
                        record = new byte[ len ];
                        is = conn.openDataInputStream();
                        is.readFully( record );
                        checkForMore = true;
                    }
                }
                catch( IOException e ){
                    record = null;
                }
                finally {
                    if( is != null ){
                        try {
                            is.close();
                        }
                        catch( IOException e ){
                        }
                    }
                }
            }

            conn = closeConnection( conn );

            // Fourth stage: write out the new message
            // and notify a waiting thread.

            if( record != null ){
                synchronized( inrs ){
                    try {
                        inrs.addRecord( record, 0,
                                    record.length );
                        inrs.notify();
                    }
                    catch( RecordStoreException re ){
                    }
                }
            }
        }

        // Some cleanup code

        synchronized( this ){
            if( thread == Thread.currentThread() ){
                thread = null;
            }
        }
    }

    // Starts the background thread.

    public synchronized void start() {
        if( thread == null ){
            thread = new Thread( this );
            thread.start();
        }
    }

    // Stops the background thread.

    public synchronized void stop(){
        if( thread != null ){
            Thread tmp = thread;
            thread = null;

            try {
                tmp.join();
            }
            catch( InterruptedException e ){
            }
        }
    }

    // Cleanup by waiting for the thread to
    // stop and then closing the record stores.

    public synchronized void destroy(){
        stop();

        synchronized( outrs ){
            try {
                outrs.closeRecordStore();
            }
            catch( RecordStoreException e ){
            }

            outrs.notifyAll();
        }

        synchronized( inrs ){
            try {
                inrs.closeRecordStore();
            }
            catch( RecordStoreException e ){
            }

            inrs.notifyAll();
        }
    }

    private RecordStore inrs;
    private RecordStore outrs;
    private Thread      thread;
    private String      url;
    private int         pullTimeout;
}

As mentioned earlier, two MIDP record stores are associated with 
the hub. One holds the outgoing messages, and the other holds 
incoming messages. These provide the persistent storage required 
to build a reliable store-and-forward messaging system. The send 
method in the hub serializes a message and adds it to the 
outgoing record store. The receive method removes a message from
the incoming record store and deserializes it.

The real work is done by the hub's background thread in the run 
method. Whenever a message is added to the outgoing record store, 
or a polling timeout expires, the background thread wakes up and 
sends a POST request to the servlet, sending a message (if 
available) as part of the request body. The servlet can respond 
with a simple status or with a message to deliver to the client.
The background thread then adds the message to the incoming 
record store and wakes a waiting thread.

The servlet code itself is quite simple when compared to the hub:

import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

/**
 * A simple servlet that receives a message
 * from a client and immediately sends one back. 
 */

public class MessagingServlet extends HttpServlet {

    private static int counter = 0;

    public void doPost( HttpServletRequest request,
                        HttpServletResponse response )
            throws IOException, ServletException {

        SimpleMessage msg = null;
        byte[]        rawdata = null;
        Object        received = null;

    // Read the incoming message, if any...

        int len = request.getContentLength();
        if( len > 0 ){
            ServletInputStream in = 
                     request.getInputStream();
            DataInputStream    din = 
                     new DataInputStream( in );

            rawdata = new byte[ len ];
            din.readFully( rawdata );

            try {
                msg = new SimpleMessage();
                msg.resurrect( rawdata );
                received = msg.getData();
            }
            catch( IOException e ){
            }

            din.close();
        }

        rawdata = null;

        // Send a canned response for any 
        // incoming message

        if( received != null ){
            ++counter;
    
            msg = new SimpleMessage();
            msg.setDestination( "client" );
            msg.setData( "Counter: " + counter + 
               " Date: " + new Date().toString() + 
               " Received: " + received );
    
            try {
                rawdata = msg.persist();
            }
            catch( IOException e ){
                rawdata = null;
            }
        }

        // Send it out!

        response.setContentType( 
             "application/octect-stream" );
        response.setContentLength( 
             rawdata != null ? rawdata.length : 0 );
        response.setStatus( response.SC_OK );

        if( rawdata != null ){
            OutputStream out = 
                     response.getOutputStream();
            out.write( rawdata );
            out.close();
        }
    }
}

This servlet is just a test servlet. All it does is generate 
a response for each message it receives. Normally, you'd have the
servlet convert the messages to and from JMS, or something more 
meaningful.

Finally, here is a very simple MIDlet to test the message hub's 
interaction with the test servlet:

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

// A very simple MIDlet to test out the message hub.
// Doesn't create any UI, just prints messages out
// to the console.

public class MessagingTest extends MIDlet
                 implements CommandListener {

    private Display    display;
    private MessageHub hub;

    public static final Command exitCommand =
                         new Command( "Exit",
                                     Command.EXIT, 1 ); 

    public MessagingTest(){
    }

    public void commandAction( Command c,
                               Displayable d ){
        if( c == exitCommand ){
            exitMIDlet();
        }
    }

    protected void destroyApp( boolean unconditional )
                    throws MIDletStateChangeException {
        exitMIDlet();
    }

    public void exitMIDlet(){
        if( hub != null ){
            hub.destroy();
        }

        notifyDestroyed();
    }

    public Display getDisplay(){ return display; }

    protected void initMIDlet(){
        try {
            testHub();
        }
        catch( Exception e ){
            System.err.println( "Exception " + e );
        }

        exitMIDlet();
    }

    protected void pauseApp(){
    }

    protected void startApp()
                    throws MIDletStateChangeException {
        if( display == null ){ 
            display = Display.getDisplay( this );
            initMIDlet();
        }
    }

    private void log( String str ){
        System.out.println( str );
    }

    private static final String TEST_URL =
             "http://localhost:8080/MessagingServlet";

    private static final int PULL_TIMEOUT = 5000;

    // The code to test the hub. Sends ten messages
    // and then reads the replies.

    private void testHub() throws IOException,
                                 RecordStoreException {
        log( "Creating hub to " + TEST_URL );

        hub = new MessageHub( 
                              TEST_URL, PULL_TIMEOUT );

        log( "Starting hub..." );
        hub.start();

        log( "Sending ten messages to server..." );
        for( int i = 1; i <= 10; ++i ){
            SimpleMessage msg =
               new SimpleMessage( "serverUserID",
                     "Client message " + i );

            log( "Sending " + msg + "..." );
            hub.send( msg );
            log( "...sent" );
        }

        log( "Receiving messages from server..." );
        while( true ){
            log( "Waiting..." );
            SimpleMessage msg = hub.receive( 
                                        PULL_TIMEOUT );
            if( msg == null ){
                log( "Receive timed out, quitting" );
                break;
            }

            log( "Received " + msg );
        }

        hub.stop();
        hub.destroy();
    }
}

This MIDlet is meant to be run from an emulator, not from a real 
device. That's because it doesn't define a user interface. 
Instead, it simply prints messages to the console.

There are many improvements you can make to this example. For
example, you can batch multiple messages together into a single 
HTTP request/response cycle. Or you can add the ability to 
register listeners which are asynchronously notified when there
are waiting messages. There are also commercial messaging 
solutions available which are probably worth exploring. As you 
can see, though, it doesn't really take that much code to build
a basic messaging framework.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
COMPRESSING XML FOR FASTER WIRELESS NETWORKING

An earlier J2ME Tech Tip showed how to parse XML documents in 
CLDC-based profiles using XML parsers like kXML or NanoXML. (See
Parsing XML in CLDC-Based Profiles at 
http://java.sun.com/jdc/J2METechTips/2001/tt0725.html#tip2.)
While XML is a useful, portable format for exchanging data 
between different applications, it has some disadvantages. One of 
these disadvantages is that it's very verbose. Remember, an XML 
document is a text-based, human-readable document, so it's 
verbose by design. Because current wireless networks are slow 
(data throughput as low as 4KB per second is not unheard of), 
passing XML documents between a server and a J2ME-enabled device 
might be too slow to be workable. Also, parsing large documents 
can easily cause out-of-memory errors on very constrained devices.
Encoding or transforming XML into a binary format is usually 
a better solution.

Binary encodings of XML already exist. Perhaps the most relevant 
encoding is WBXML, which is used in WAP to encode the decks of 
Wireless Markup Language (WML) browser markup sent to cellphones. 
The open-source kXML parser (see http://www.kxml.org) has 
built-in support for generating and parsing WBXML.

WBXML works by replacing common tag and attribute names and/or 
values with tokens. The exact set of tokens is configurable. 
Using WBXML with the kXML parser is simply a matter of:

o Replacing org.kxml.parser.XmlParser with 
  org.kxml.wap.WbxmlParser  
  
o Replacing org.kxml.io.XmlWriter with org.kxml.wap.WbxmlWriter 

o Configuring the WbxmlParser and WbxmlWriter classes 
  appropriately before using them.

Here's a simple example. Say your J2ME application uses an XML 
document to represent employee information. A typical employee 
might be represented as:

<employee>
  <firstname>Eric</firstname>
  <lastname>Giguere</lastname>
  <sex type="male"/>
  <email valid="true" primary="true">
     ericgiguere@ericgiguere.com
  </email>
</employee>

This document is about 180 bytes long, depending on how many 
whitespace characters you use. By replacing the tag and attribute
names and values with tokens you can reduce its length to less 
than 70 bytes. All you do is define three tables. The first is
for tags:

public static final String tagTable[] = {
    "employee",
    "firstname",
    "lastname",
    "sex",
    "email"
};

The second is for attribute names:

public static final String attrStartTable[] = {
    "type",
    "valid",
    "primary"
};

The third is for attribute values:

public static final String attrValueTable[] = {
    "true",
    "false",
    "male",
    "female"
};

You get the best compression by making sure that all the tags and 
attribute names (and as many values as possible) defined by your 
XML Document Type Definition (DTD) are in the tables. Any values 
not listed in the tables will still be correctly written out.

First create a WbxmlWriter object, setting the tables 
appropriately (Note that the first argument to the "set" methods 
must currently be hardcoded to 0.)

    OutputStream out = ...; // a destination
    WbxmlWriter writer = new WbxmlWriter( out );
    writer.setTagTable( 0, tagTable );
    writer.setAttrStartTable( 0, attrStartTable );
    writer.setAttrValueTable( 0, attrValueTable ); 

Then write tags and attribute values as you normally would:

    writer.startTag( "email" );
    writer.attribute( "primary", "true" );
    writer.attribute( "valid", "true" );
    writer.write( "ericgiguere@ericgiguere.com" );
    writer.endTag();
    .... // etc. etc.
  
The WbxmlWriter class automatically encodes the tags, attributes,
and values for you, making for a much smaller XML document. 

Parsing a WBXML document is just as simple. All you do is 
initialize the WbxmlParser object with the same tables as the 
WbxmlWriter:

    InputStream in = ....; // an input source
    WbxmlParser parser = new WbxmlParser( in );
    parser.setTagTable( 0, tagTable );
    parser.setAttrStartTable( 0, attrStartTable );
    parser.setAttrValueTable( 0, attrValueTable );
    
    ParseEvent event = parser.read();
    ..... // etc. etc.

As long as the writer and the parser are using the same set of 
tables, you should have no problems shrinking your document 
sizes.

Here's the code for a simple MIDlet (it has no user interface) 
that writes out the same document using an XmlWriter and 
a WbxmlWriter:

import java.io.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import org.kxml.*;
import org.kxml.io.*;
import org.kxml.wap.*;

// A simple MIDlet that shows how to use WBXML
// encoding to "shrink" or "compress" XML documents.

public class XMLCompressTest extends MIDlet
                 implements CommandListener {

    private Display    display;

    public static final Command exitCommand =
                         new Command( "Exit",
                                     Command.EXIT, 1 ); 

    public XMLCompressTest(){
    }

    public void commandAction( Command c,
                               Displayable d ){
        if( c == exitCommand ){
            exitMIDlet();
        }
    }

    protected void destroyApp( boolean unconditional )
                    throws MIDletStateChangeException {
        exitMIDlet();
    }

    public void exitMIDlet(){
        notifyDestroyed();
    }

    public Display getDisplay(){ return display; }

    protected void initMIDlet(){
        try {
            testXML();
        }
        catch( Exception e ){
            System.err.println( "Exception " + e );
        }

        exitMIDlet();
    }

    protected void pauseApp(){
    }

    protected void startApp()
                    throws MIDletStateChangeException {
        if( display == null ){ 
            display = Display.getDisplay( this );
            initMIDlet();
        }
    }

    private void log( String str ){
        System.out.println( str );
    }

    // Write an XML document to the given writer

    private void writeXML( AbstractXmlWriter writer )
                                   throws IOException {
        writer.startTag( "employee" );
        writer.startTag( "firstname" );
        writer.write( "Eric" );
        writer.endTag();
        writer.startTag( "lastname" );
        writer.write( "Giguere" );
        writer.endTag();
        writer.startTag( "sex" );
        writer.attribute( "type", "male" );
        writer.endTag();
        writer.startTag( "email" );
        writer.attribute( "valid", "true" );
        writer.attribute( "primary", "true" );
        writer.write( "ericgiguere@ericgiguere.com" );
        writer.endTag();
        writer.endTag();
        writer.close();
    }

    // Table for the tags in our DTD

    private static final String tagTable[] = {
        "employee",
        "firstname",
        "lastname",
        "sex",
        "email"
    };

    // Table for the attribute names in our DTD

    private static final String attrStartTable[] = {
        "type",
        "valid",
        "primary",
    };

    // Table for the attribute values in our DTD

    private static final String attrValueTable[] = {
        "true",
        "false",
        "male",
        "female"
    };

    // Show the difference in writing out the same
    // document using plain XML and then WBXML.

    private void testXML() throws IOException {
        log( 
           "Writing uncompressed XML to a string..." );

        ByteArrayOutputStream out = 
                           new ByteArrayOutputStream();
        XmlWriter xw = new XmlWriter( 
                       new OutputStreamWriter( out ) );
        writeXML( xw );

        String str = new String( out.toByteArray() );
        log( "String length is " + str.length() );
        log( "String value is" );
        log( str );

        log( "Writing compressed XML to a string..." );

        out = new ByteArrayOutputStream();
        WbxmlWriter bxw = new WbxmlWriter( out );
        bxw.setTagTable( 0, tagTable );
        bxw.setAttrStartTable( 0, attrStartTable );
        bxw.setAttrValueTable( 0, attrValueTable );
        writeXML( bxw );

        str = new String( out.toByteArray() );
        log( "String length is " + str.length() );
        log( "String value is" );
        log( str );

        log( "Done." );
    }
}

Of course, you can't use a binary encoding like WBXML without 
support from the server. The server must generate and accept XML 
documents in binary form, not the usual text form. This appears 
to limit who the client can talk to. But realistically, the 
client only talks to a single server, so it's not such a 
limitation. If access to other servers is required, the primary 
server can act (with a bit of coding) as a proxy on the client's 
behalf.

For really simple formats you can probably even eliminate using 
the WBXML format. If the client talks to a Java-based server 
application, it's very easy to encode all kinds of information in 
a portable and efficient manner using the DataInputStream and 
DataOutputStream classes. See the Tech Tip "Client-Server 
Communication over HTTP using MIDP and Servlets" for details
(http://java.sun.com/jdc/J2METechTips/2001/tt0820.html#tip2).

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

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 J2ME 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 J2ME Tech Tips archives at:

http://java.sun.com/jdc/J2METechTips/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

J2ME Tech Tips 
March 25, 2002

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




