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

May 29, 2001

WELCOME to the Java Developer ConnectionSM (JDC) JavaTM 2 Platform, Micro Edition (J2METM) Tech Tips, for May 29, 2001. This issue covers:

The J2ME Tech Tips are 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."


THE CONNECTED DEVICE CONFIGURATION AND THE FOUNDATION PROFILE

Much of what's been written about Java 2 Platform, Micro Edition (J2ME) talks about the Connected Limited Device Configuration, or CLDC for short. The CLDC defines an extremely small subset of the Java Platform for very limited devices -- devices, like cellular telephones, that do not have enough memory or enough processing power to handle a full-blown Java implementation. There is, however, another configuration available that is midway between the CLDC and a full Java 2 Platform, Standard Edition (J2SETM) implementation: the Connected Device Profile, or CDC for short.

The CDC defines a much less radical subset of J2SE. Unlike the CLDC, which is aimed at devices with less (often much less) than 512K bytes of total available memory, the CDC requires devices to have at least 2 megabytes of total memory available for Java. This can be a combination of read-only and read-write memory. The Java virtual machine* (VM) and core class libraries are likely to be stored in read-only or flash memory.

The CDC really targets the next generation of wireless, handheld consumer devices. These devices pack large amounts of memory and faster processors into the same or smaller form factors than today's most prevalent devices. The CDC can also be used for larger consumer devices such as set-top boxes that deliver content through televisions, that is, devices that don't need a general purpose Java implementation such as J2SE.

The CDC is also a superset of the CLDC. All the classes of the CLDC, including the new javax.microedition.io classes defined by the Generic Connection Framework, are part of the CDC. This means that CLDC-based applications can run unchanged in a CDC-based environment. That's true provided that any required CLDC-based profiles the applications use (such as the Mobile Information Device Profile) are available on the device.

Perhaps the biggest difference between the CDC and the CLDC is that the CDC includes the CVM virtual machine, a full J2SE-compliant virtual machine. The CLDC made changes to the virtual machine specification. It disallowed all floating-point operations, changed the way classes are verified, and did not support low-level standards such as the Java Native Interface (JNI) or the Java Virtual Machine Debug Interface (JVMDI). By comparison, all of the features of a full J2SE virtual machine must be supported by the CVM virtual machine. The J2SE VM itself is not a requirement of the CDC --- all the CDC requires is a VM that implements the full set of VM features. Vendors can license it and adapt it for their devices quickly. The CVM virtual machine can even be used with real-time operating systems.

The CDC includes classes from the java.lang, java.util, java.net, java.io, java.text and java.security packages. Not every class from each package is supported. However those classes that are supported behave identically to and have the same public interfaces as those in J2SE; the one exception is that most deprecated methods have been removed. No changes are usually required to port existing J2SE code to the CDC.

Just as there are profiles for the CLDC, there are also profiles being developed for the CDC. The first profile, the Foundation Profile, was released at the same time as the CDC. Generally, the two are meant to go hand-in-hand. The Foundation Profile extends the CDC by adding most of the missing J2SE core libraries, except for those related to creating user interfaces. These additions include socket classes, the complete internationalization and localization classes, and the full set of classes in the java.lang and java.iopackages. There are still gaps, for example, the java.beans, java.rmi and java.sql packages are not part of the CDC or the Foundation Profile.

As you might guess, the Foundation Profile is meant to serve as a foundation for other profiles. The PersonalJavaTM API is being redefined as a Personal Profile that extends the Foundation Profile. An RMI Profile, in the final stages of development, adds RMI support to the Foundation Profile. Other profiles are sure to be developed.

Reference implementations of the CDC and the Foundation Profile are available for Linux and VxWorks platforms under the terms and conditions of the Sun Community Source License (SCSL) agreement. You can download them by following the links from the main CDC page.

Here's a CDC-Foundation Profile application that illustrates some of the things described in this tip. In particular, the application uses CDC java.net classes to create device connections (Socket and ServerSocket). The application is comprised of two programs. One program, PeerFinder, acts as a client; the other program, PeerListener, acts as a server.

Here's the source code for PeerFinder:

import java.io.*;
import java.net.Socket;
import java.net.InetAddress;
import java.util.Vector;

public class PeerFinder {

    static final int    PORT_NUM = 4321;
    static final int    DATA_BUFFER_MAX = 256;

    static final String PING_MSG = new String("ping");
    static final String PONG_MSG = new String("pong");

    static String       command = System.getProperty("command");
    static String       portprop = System.getProperty("port");
    static String       hostname = System.getProperty("hostname");

    static Socket       socket;
    static int          port = PORT_NUM;

    static BufferedOutputStream outputStream;
    static BufferedInputStream  inputStream;


    public static void main(String args[]) {

        byte[]  dataBuffer = new byte[DATA_BUFFER_MAX];
        String  dataString = null;

        // Set default port if not set by user
        if ((portprop != null) && !portprop.equals("")) {
            port = Integer.parseInt(portprop);
        }

        // Set default command if not set by user
        if ((command == null) || (command.equals(""))) {
            command = PING_MSG;
        }

        // Set default security manager
        try {
            if (System.getSecurityManager()==null) {
                System.setSecurityManager(new SecurityManager());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Open a socket to the given peer listener host port
       try {

            if ((hostname != null) && hostname.equals("")) {
                System.out.println("Opening to local 
                                           host port # "+port);
                socket = new Socket(
                             InetAddress.getLocalHost(), port);
            } else {
                System.out.println("Opening peer listener host "+
                                     hostname+
                                   " port # "+port);
                socket = new Socket(hostname, port);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        try {

            // Get the input and output streams of socket to peer
            outputStream = new BufferedOutputStream(
                                        socket.getOutputStream());
            inputStream = new BufferedInputStream(
                                        socket.getInputStream() );

            // Check to send a ping
            if (command.equals(PING_MSG)) {
               sendPing();
            }

            // Listen for peer listener reply
            System.out.println("Listening for return message...");

            // Reading from peer listener
            while (inputStream.read(dataBuffer, 0, DATA_BUFFER_MAX)
                   != -1) {
                dataString = new String(dataBuffer);

            // Print received message
                System.out.println("Received peer listener reply: 
                                   "+  dataString);

            // Check if pong was received
                if (dataString.indexOf(PONG_MSG) != -1) {
                    break;
                }
            }

            // Cleanup and close
            inputStream.close();
            outputStream.close();
            socket.close();

        } catch (IOException ioe) {
            ioe.printStackTrace();
        }

    }

    static void sendPing() {
        byte[] dataBuffer = PING_MSG.getBytes();

       try {
            outputStream.write(dataBuffer, 0, dataBuffer.length);
            outputStream.flush();
            System.out.println("Sent: "+PING_MSG);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Here's the source code for PeerListener:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class PeerListener {

    static final int    PORT_NUM = 4321;
    static final int    DATA_BUFFER_MAX = 256;

    static final String PING_MSG = new String("ping");
    static final String PONG_MSG = new String("pong");

    static String       command = System.getProperty("command");
    static String       portprop = System.getProperty("port");

    static ServerSocket peerSocket;
    static Socket       socket;
    static int          port = PORT_NUM;

    static BufferedOutputStream outputStream;
    static BufferedInputStream  inputStream;

    public static void main(String args[]) {

        byte[]  dataBuffer = new byte[DATA_BUFFER_MAX];
        String  dataString = null;

        // Set default property if not set by user
        if ((portprop != null) && !portprop.equals("")) {
            port = Integer.parseInt(portprop);
        }

        // Set default security manager
        try {
            if (System.getSecurityManager()==null) {
                System.setSecurityManager(new SecurityManager());
            }
        } catch (Exception e) {
           e.printStackTrace();
        }

        // Open a peer socket to the given port
        try {
            peerSocket = new ServerSocket(port);
        } catch (Exception e) {
           e.printStackTrace();
        }

        // Start listening for peers
        System.out.println("Listening for peer finder 
                                                connection...");

        while (true) {

        try {

                // Block here for peer finder requests to connect
                socket = peerSocket.accept();

                // Get the input and output streams from peer socket
                outputStream = new 
                    BufferedOutputStream(socket.getOutputStream());
                inputStream = new 
                    BufferedInputStream(socket.getInputStream() );

                // Read loop
                while (inputStream.read(dataBuffer, 0, 
                                                  DATA_BUFFER_MAX-1)
                       != -1) {
                    dataString = new String(dataBuffer);
                    System.out.println("Received: "+ dataString+"");

                    // Check if this is a ping request message
                    if ((dataString.indexOf(PING_MSG)) != -1) {
                        // Send pong message to the peer
                           sendPong();
                    } else {
                        // Don't know what type of message this was
                        System.out.println("     Unknown message");
                    }
                }

                inputStream.close();
                outputStream.close();
                socket.close();

            } catch (IOException ioe) {
                ioe.printStackTrace();
                break;
            }
        }

    }

    static void sendPong() {
        byte[]  dataBuffer = PONG_MSG.getBytes();

        try {
            outputStream.write(dataBuffer, 0, dataBuffer.length);
            outputStream.flush();
            System.out.println("Sent: "+ PONG_MSG);
        } catch (Exception e) {
          e.printStackTrace();
        }
    }

}

You will also need a policy file and a security properties file to run the application. Use the following policy file (java.policy):

grant {
        permission java.net.SocketPermission "*:1024-65535", 
                "connect,accept,listen,resolve";
};

// Standard extensions get all permissions by default

grant codeBase "file:${java.home}/lib/ext/*" {
        permission java.security.AllPermission;
};

// default permissions granted to all domains

grant { 
        // Allows any thread to stop itself using the 
        // java.lang.Thread.stop() method that takes no argument.
        // Note that this permission is granted by default only 
        // to remain backwards compatible.
        // It is strongly recommended that you either remove this 
        // permission from this policy file or further restrict 
        // it to code sources that you specify, because 
        // Thread.stop() is potentially unsafe.
        // See "http://java.sun.com/notes" for more information.
        permission java.lang.RuntimePermission "stopThread";

        // allows anyone to listen on un-privileged ports
        permission java.net.SocketPermission "localhost:1024-", 
                                                        "listen";

        // "standard" properies that can be read by anyone

        permission java.util.PropertyPermission "java.version", 
                                                          "read";
        permission java.util.PropertyPermission "java.vendor", 
                                                          "read";
        permission java.util.PropertyPermission 
                                       "java.vendor.url", "read";
        permission java.util.PropertyPermission 
                                    "java.class.version", "read";
        permission java.util.PropertyPermission 
                                               "os.name", "read";
        permission java.util.PropertyPermission 
                                            "os.version", "read";
        permission java.util.PropertyPermission "os.arch", 
                                                          "read";
        permission java.util.PropertyPermission "file.separator", 
                                                          "read";
        permission java.util.PropertyPermission "path.separator", 
                                                          "read";
        permission java.util.PropertyPermission "line.separator", 
                                                          "read";

        permission java.util.PropertyPermission 
                            "java.specification.version", "read";
        permission java.util.PropertyPermission 
                             "java.specification.vendor", "read";
        permission java.util.PropertyPermission 
                               "java.specification.name", "read";

        permission java.util.PropertyPermission 
                         "java.vm.specification.version", "read";
        permission java.util.PropertyPermission 
                          "java.vm.specification.vendor", "read";
        permission java.util.PropertyPermission 
                            "java.vm.specification.name", "read";
        permission java.util.PropertyPermission 
                                       "java.vm.version", "read";
        permission java.util.PropertyPermission "
                                         java.vm.vendor", "read";
        permission java.util.PropertyPermission "java.vm.name", 
                                                          "read";
};

Use the following master security properties file (java.security):

#
# This is the "master security properties file".
#
# In this file, various security properties are set for use by
# java.security classes. This is where users can statically 
# register Cryptography Package Providers ("providers" for short). 
# The term "provider" refers to a package or set of packages that 
# supply a concrete implementation of a subset of the cryptography 
# aspects of the Java Security API. A provider may, for example, 
# implement one or more digital signature algorithms or message 
# digest algorithms.
#
# Each provider must implement a subclass of the Provider class.
# To register a provider in this master security properties file, 
# specify the Provider subclass name and priority in the format
#
#    security.provider.<n>=<className> 
#
# This declares a provider, and specifies its preference 
# order n. The preference order is the order in which providers are 
# searched for requested algorithms (when no specific provider is 
# requested). The order is 1-based; 1 is the most preferred, 
# followed by 2, and so on.
#
# <className> must specify the subclass of the Provider class whose 
# constructor sets the values of various properties that are 
# required for the Java Security API to look up the algorithms or 
# other facilities implemented by the provider.
# 
# There must be at least one provider specification in 
# java.security. There is a default provider that comes standard 
# with the JDK. It is called the "SUN" provider, and its Provider 
# subclass named Sun appears in the sun.security.provider package. 
# Thus, the "SUN" provider is registered via the following:
#
#    security.provider.1=sun.security.provider.Sun 
#
# (The number 1 is used for the default provider.) 
#
# Note: Statically registered Provider subclasses are 
# instantiated when the system is initialized. Providers can be 
# dynamically registered instead by calls to either the addProvider 
# or insertProviderAt method in the Security class.

#
# List of providers and their preference orders (see above):
#
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.rsajca.Provider

#
# Class to instantiate as the system Policy. This is the name of 
# the class that will be used as the Policy object.
#
policy.provider=sun.security.provider.PolicyFile

# The default is to have a single system-wide policy file, 
# and a policy file in the user's home directory.
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy

# whether or not we expand properties in the policy file
# if this is set to false, properties (${...}) will not be expanded 
# in policy files.
policy.expandProperties=true

# whether or not we allow an extra policy to be passed on the 
# command line with -Djava.security.policy=somefile. Comment out 
# this line to disable this feature.
policy.allowSystemProperty=true

# whether or not we look into the IdentityScope for trusted 
# Identities when encountering a 1.1 signed JAR file. If the 
# identity is found and is trusted, we grant it AllPermission.
policy.ignoreIdentityScope=false

#
# Default keystore type.
#
keystore.type=jks

#
# Class to instantiate as the system scope:
#
system.scope=sun.security.provider.IdentityDatabase

#
# List of comma-separated packages that start with or equal this 
# string will cause a security exception to be thrown when
# passed to checkPackageAccess unless the corresponding 
# RuntimePermission ("accessClassInPackage."+package) has
# been granted.
package.access=sun.

#
# List of comma-separated packages that start with or equal this 
# string will cause a security exception to be thrown when
# passed to checkPackageDefinition unless the corresponding
# RuntimePermission ("defineClassInPackage."+package) has
# been granted.
# 
# by default, no packages are restricted for definition, and none 
# of the class loaders supplied with the JDK call 
# checkPackageDefinition.
#
#package.definition=

To build the application:

  1. Download Foundation Profile version 1.0. (The CDC is bundled with the Foundation Profile in the download.)
  2. Compile the Foundation Profile. Follow the instructions in the download. (This builds the CDC as well as the Foundation Profile.)
  3. Make a security subdirectory in the build/<platform>/lib directory. For example:

    mkdir build/linux/lib/security

  4. Copy java.policy and java.security to the security subdirectory. For example:

    cp java.policy build/linux/lib/security
    cp java.security build/linux/lib/security

  5. Compile the application source code using Java 2 SDK version 1.3. Run javac as usual, but use the -bootclasspath option instead of the -classpath option to include the Foundation Profile classes. The -bootclasspath option excludes the standard J2SE classes from the compilation, which makes it possible to detect your application's use of classes and methods not defined in the CDC or the Foundation Profile.

       cd <your_test_dir>
       javac -bootclasspath \ 
        <cdcfoundation_dir>/build/linux/lib/foundation.jar:\
            <cdcfoundation_dir>/build/linux/btclasses.zip \
            PeerListener.java \
            PeerFinder.java
    
  6. Run PeerListener:

    cd <cdcfoundation_dir>/build/linux/bin
    cvm -Djava.class.path=<your_test_dir> PeerListener

    You'll see the following line displayed in the window:

    Listening for peer finder connection...

In another window, run PeerFinder:

   cd <cdcfoundation_dir>/build/linux/bin
   cvm -Djava.class.path=<your_test_dir> PeerFinder
You'll see the following lines displayed in the window:
     Sent: ping
     Listening for return message...
     Received peer listener reply: pong   

Some things to note about this example:

  • It uses J2SE-style APIs (java.net.Socket and java.net.ServerSocket). Unlike the case for CLDC, there is no need to learn the Generic Connection Framework API.
  • It uses buffered input and output streams. This makes for faster I/O.
  • It complies with J2SE-style security (including use of a policy file and the default Security Manager).
  • It can be compiled and run on both J2SE and CDC-Foundation Profile.

HANDLING MULTIPLE SIMULTANEOUS MIDP ALERTS

The April 16, 2001 issue of the J2ME Tech Tips introduced you to timers and alerts. If you write a multithreaded MIDP application -- whether you spawn the threads yourself or use timers to schedule background tasks -- it's easy to trigger two or more alerts simultaneously. In other words, while one alert is being displayed, a second alert is triggered. Dealing with this situation is not as simple as you might think. Alerts are displayed using the two-argument form of the Display object's setCurrent method, as in the following:

Display     display = ...;
Alert       alert = ...;
Displayable next = ...;

display.setCurrent( alert, next );

The first argument is the alert to display and the second is the screen object to display after the alert is dismissed or times out. The second argument is referred to as the "alert destination."

The problem with calling setCurrent as soon as the second alert triggers, is that the new alert immediately replaces the previous alert. Sometimes this is desirable, but in most cases, you want the user to read each alert in the order that they occurred. An alternative approach is to try chaining the alerts, for example:

Display     display = ...;
Alert       alert = ...;
Displayable next = ...;
Displayable current = display.getCurrent();

if( current instanceof Alert ){
    display.setCurrent( (Alert)current, alert );
} else {
    display.setCurrent( alert, next );
}

This doesn't work, however, because only one alert can be active at any time. The final destination, that is, the destination of the last alert, is also lost with this scheme.

What you really need is a way to keep a list of pending alerts and display each alert in turn. This would be simple to do if the alert had a dismissal event you could trap. Unfortunately, there's no direct way to know when an alert is dismissed.

There is an indirect route you can take, however. A Canvas object calls its showNotify and hideNotify methods whenever it is shown or hidden. These calls are normally used to start and stop screen-related operations that only make sense when the canvas is visible. For example:

public class MyCanvas extends Canvas {
    protected void paint( Graphics g ){
        // do the painting here
    }

    protected void showNotify(){
        // canvas is being displayed, start
        // any timers, etc.
    }

    protected void hideNotify(){
        // canvas is being hidden, stop any
        // timers, etc.
    }
}

You can take advantage of this capability to solve the alert dilemma. Whenever you display an alert, make a Canvas object the alert destination. When the alert times out or is dismissed, the canvas is displayed. You then place logic in the canvas's showNotify method to display either a pending alert or another screen. The canvas doesn't even have to paint itself because it will only be briefly displayed.

Armed with the overall design, you can now build the Canvas subclass; let's call it AlertRouter. Here is the source code for AlertRouter:

package com.j2medeveloper.util;

import java.util.*;
import javax.microedition.lcdui.*;

public class AlertRouter extends Canvas {

    private Display     display;
    private Vector      pending = new Vector();
    private Displayable destination;
    private Alert       current;

    public AlertRouter( Display display ){
        this.display = display;
    }

    protected void paint( Graphics g ){
       // no painting
    }

    protected synchronized void showNotify() {
        if( pending.size() > 0 ){
            current = (Alert) pending.elementAt( 0 );
            pending.removeElementAt( 0 );
            display.setCurrent( current, this );
        } else {
            current = null;
            display.setCurrent( destination );
        }
    }

    public void showAlert( Alert alert ) {
        showAlert( alert, null );
    }

    public synchronized void showAlert( Alert alert, 
                                              Displayable next ){
        if( next != null ){
            destination = next;
        } else if( destination == null ){
            destination = display.getCurrent();
            if( destination == null ){
                destination = this;
            }
        }

        pending.addElement( alert );

        if( current == null ){
            display.setCurrent( this );
        }
    }
}

To display alerts, create an instance of AlertRouter when the MIDlet starts. You'll need to pass the MIDlet's Display object to the AlertRouter instance. Then whenever you need to display an alert, call the alert router's showAlert method. Here's a simple example:

import com.j2medeveloper.util.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class MyMIDlet extends MIDlet {

    private Display     display;
    private AlertRouter router;

    public MyMIDlet(){
        display = Display.getDisplay( this );
        router = new AlertRouter( display );
    }

    protected void startApp(){
        Alert alert = new Alert( "Started", "App started", null, 
                                                          null );
        router.showAlert( alert );
    }

    // other methods omitted....
}

Whenever showAlert is called, the given alert is added to a list of pending alerts. The first alert in the list is displayed if no alert is showing. Otherwise, each alert waits its turn. The final alert destination is the screen that was active just before the first alert in the alert chain was shown. A two-argument version of showAlert lets you override that value with your own destination. The most recent call to showAlert determines the ultimate destination of the alert chain, which is normally what you want.

Here's a more complicated MIDlet that tests the alert router by using several timers to create alerts at different time intervals:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
import com.j2medeveloper.util.*;

public class MultiAlert extends MIDlet {

    Display      display;
    Command      exitCommand = new Command( "Exit", Command.EXIT,
                                                             1 );
    Timer        timer1 = new Timer();
    Timer        timer2 = new Timer();
    Timer        timer3 = new Timer();
    MainForm     form = new MainForm();
    AlertRouter  router;

    public MultiAlert() {
        display = Display.getDisplay( this );
        router = new AlertRouter( display );

        timer1.schedule( new AlertTrigger( "Alert 1", 
                             "This is alert #1" ), 5000, 10000 );
        timer2.schedule( new AlertTrigger( "Alert 2", 
                              "This is alert #2" ), 5000, 7000 );
        timer3.schedule( new AlertTrigger( "Alert 3", 
                              "This is alert #3" ), 5000, 9000 );
    }

    protected void destroyApp( boolean unconditional ) {
        timer1.cancel();
        timer2.cancel();
        timer3.cancel();
    }

    protected void startApp() {
        display.setCurrent( form );
    }

    protected void pauseApp() {
    }

    public void exit(){
        destroyApp( true );
        notifyDestroyed();
    }
 
    class AlertTrigger extends TimerTask {

        public AlertTrigger( String title, String message )
        {
            this.title = title;
            this.message = message;
        }

          public void run(){
            Alert alert = new Alert( title, message, null, null );
            alert.setTimeout( Alert.FOREVER );
            router.showAlert( alert );
       }

        private String title;
        private String message;
    }

    class MainForm extends Form implements CommandListener {
        public MainForm(){
            super( "MultiAlert Demo" );
            addCommand( exitCommand );
            setCommandListener( this );
        }

        public void commandAction( Command c, Displayable d ){
            exit();
        }
    }
}

Run this MIDlet and wait a few seconds for the first alert to appear, then wait a few more seconds before dismissing it. You'll see another alert appear immediately after the first. Dismiss this second alert and any other alerts. Eventually, you will be brought back to the main screen, where the whole process starts again.


— 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, uncheck the appropriate checkbox, and click the Update button.

As of May 22, 2001, Sun Microsystems updated its Privacy Policy to give you a better understanding of Sun's Privacy Policy and Practice. If you have any questions, contact privacy@sun.com.

— Subscribe —

To subscribe to a JDC newsletter mailing list, go to the Subscriptions page, 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

- LINKS TO NON-SUN SITES

The J2ME Tech Tips may provide, or third parties may provide, links to other Internet sites or resources. Because Sun has no control over such sites and resources, You acknowledge and agree that Sun is not responsible for the availability of such external sites or resources, and does not endorse and is not responsible or liable for any Content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Content, goods or services available on or through any such site or resource.

J2ME Tech Tips May 29, 2001

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


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.