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

January 29, 2001

WELCOME to the Java Developer ConnectionSM (JDC) J2ME Tech Tips, January 29, 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."


WRITING WORLD-AWARE J2ME APPLICATIONS

Well-written applications are both internationalized and localized. An application is internationalized, if it can correctly handle different encodings of character data. An application is localized, if it formats and interprets data (dates, times, timezones, currencies, messages and so on) according to rules specific to the user's locale (country and language). An application that is both internationalized and localized, can be referred to as "world-aware."

Writing a world-aware application in the JavaTM programming language is easy enough with JavaTM 2 Standard Edition (J2SETM). That's because of J2SE's extensive set of core Java classes devoted to internationalization and localization. Refer to the java.util.Calendar, java.util.Locale, java.text.Format, and java.io.Reader classes for examples of this support.

However, writing a world-aware application that runs in the stripped-down environment of J2ME can be a real challenge because most of the J2SE support classes simply aren't in J2ME. This is especially true for any J2ME implementation based on the Connected Limited Device Configuration (CLDC) and its associated profiles. CLDC includes a very small subset of J2SE core classes. Still, it's not an impossible to write a world-aware J2ME application, once you're aware of what's available and what's not.

It's important to remember that with J2ME, memory is always at a premium -- the device might have very little space available for storing and running Java applications. Instead of bundling your applications as a single JAR file that can run in multiple locales, plan to create separate JARs for each locale. Include in each JAR, only the classes and resources required by that locale. The user can then download and install the version of the application optimized for their own locale. Sometimes the downloading can be automated. If a device can download application JAR files using HTTP, a servlet on the web server can be written to automatically send the appropriate JAR file based on the locale information encoded in the HTTP headers.

In terms of support classes, the CLDC includes the following J2SE classes:

java.io.DataInputStream
java.io.DataOutputStream
java.io.InputStreamReader
java.io.OutputStreamWriter
java.io.Reader
java.io.Writer
java.util.Calendar
java.util.Date
java.util.TimeZone

The InputStreamReader and OutputStreamReader classes are particularly important because they convert raw byte streams into Unicode-based character streams and back. The conversion is done according to a particular character encoding system. The default encoding for the platform is obtained from a system property:

String encoding = System.getProperty( 
            "microedition.encoding" );

At the very least, a CLDC implementation must support the ISO8859_1 (Latin 1) encoding, but can optionally support other encodings. For example, a Japanese implementation is likely to support the SJIS (Shift-JIS) encoding.

The Calendar, Date and TimeZone classes are subsets of the J2SE classes of the same name. An implementation is only required to support a single time zone, which ideally is the user's local time zone.

Apart from the classes listed above, the CLDC provides no support for any formatting of strings, numbers, currencies or other locale-specific operations. Profiles based on the CLDC are allowed to add more internationalization and localization features if appropriate or necessary for the target set of applications. For example, the Mobile Information Device Profile (MIDP) requires the implementation to define a microedition.locale system property that returns the device's locale in language-country format (as in "en-US" or "fr-FR"). Also, any HTTP requests that a MIDlet (a Java program that follows the MIDP specifications) makes must set a Content-Encoding header so that web servers can return correctly encoded text content. Other profiles can include more support for writing world-aware applications.

With only a bare minimum of support available, you can write your own code to handle application localization, much as you might have done if you were still programming with the original Java 1.02 classes. Resource bundles, for example, are just a set of classes with a particular interface that are searched in a particular order based on the desired locale information. Here is some code that simulates a resource bundle:

import java.util.Hashtable;

public class ResourceBundle {

  private static Hashtable groups = new Hashtable(); 

  public static Object getObject( String group, String 
                                               key ) {
    ResourceBundle bundle;

    synchronized( groups ){
      bundle = (ResourceBundle) groups.get( group );
      if( bundle == null ){
        bundle = loadBundle( group );
      }
    }

    return bundle.getResource( key );
  }

  public static String getString( String group, String 
                                               key ) {
        return (String) getObject( group, key );
  }

  public static ResourceBundle loadBundle( String 
                                            name ) {
    ResourceBundle bundle = null;
    Locale         locale = Locale.getDefaultLocale();
    String         language = locale.getLanguage();
    String         country = locale.getCountry();

    try {
        bundle = (ResourceBundle) 
                   Class.forName( name ).newInstance();
    }
    catch( Exception e ){
    }

    if( language != null ){
      ResourceBundle child;

      try {
        child = (ResourceBundle) Class.forName( 
                              name + '_' + language 
                                    ).newInstance();
        child.setParent( bundle );
        bundle = child;
      }
      catch( Exception e ){
      }

      if( country != null ){
        try {
          child = (ResourceBundle) Class.forName( 
                           name + '_' + language + 
                           '_' + country 
                           ).newInstance();
          child.setParent( bundle );
          bundle = child;
        }
         catch( Exception e ){
         }
       }
     }

     if( bundle == null ){
       bundle = new ResourceBundle();
     }

     groups.put( name, bundle );
     return bundle;
  }

  protected Hashtable resources = new Hashtable();
  private   ResourceBundle parent;

  protected ResourceBundle() {
  }

  protected void setParent( ResourceBundle parent ) {
    this.parent = parent;
  }

  protected Object getResource( String key ) {
    Object obj = null;

    if( resources != null ){
      obj = resources.get( key );
    }

    if( obj == null && parent != null ){
      obj = parent.getResource( key );
    }

    return obj;
  }

  public static class Locale {
    private String language = "en";
    private String country = "US";
    
    public Locale( String language, String country ) {
      this.language = language;
      this.country = country;
    }

    public Locale( String locale ) {
      if( locale != null ){
        int pos = locale.indexOf( '-' );
        if( pos != -1 ){
          language = locale.substring( 0, pos );
          locale = locale.substring( pos+1 );
    
          pos = locale.indexOf( '-' );
          if( pos == -1 ){
            country = locale;
          } else {
            country = locale.substring( 0, pos );
          }
        }
      }
    }

    public String getLanguage() {
      return language;
    }

    public String getCountry() {
      return country;
    }

    private static Locale defaultLocale = new 
                  Locale( System.getProperty( 
                   "microedition.locale" ) );

    public static Locale getDefaultLocale() {
      return defaultLocale;
    }

    public static void setDefaultLocale( Locale 
                                     locale ) {
      defaultLocale = locale;
    }
  }
}

Define the resources using a set of classes sharing a common base name, as in the following:

// Default locale (base name)

public class Test extends ResourceBundle {
  public Test() {
    resources.put( "hello", "Hello!" );
    resources.put( "goodbye", "Goodbye!" );
    resources.put( "stop", "Stop!" );
    resources.put( "notranslation", "This is 
                                 English." );
  }
}

// French (fr) language, no specific country

public class Test_fr extends ResourceBundle {
  public Test_fr() {
    resources.put( "hello", "Bonjour!" );
    resources.put( "goodbye", "Aurevoir!" );
  }
}

// French (fr) language, Canada (CA) country

public class Test_fr_CA extends ResourceBundle {
  public Test_fr_CA() {
    resources.put( "stop", "Arretez!" );
  }
}

After the classes are defined, you can obtain a localized resource using the base name and the name of the resource:

String msg = ResourceBundle.getString( "Test",
                                     "hello" );

This returns "Bounjour!" for French locales and "Hello!" for all other locales.

The resources are loaded based on the default locale, as defined by the microedition.locale system property. You can change this at runtime. You do this by calling the following before loading any resources:

ResourceBundle.Locale.setDefaultLocale( new 
         ResourceBundle.Locale( "de-DE" ) );

Don't forget to share resources across applications wherever possible. For example, MIDlets in the same MIDlet suite (that is, the same JAR file) could easily share resource bundles.

For an in-depth look at writing world-aware applications with J2SE, see the Internationalization and Localization page on the Java Developer Connection: http://java.sun.com/jdc/technicalArticles/Intl/index.html


DEALING WITH MISSING J2SE CLASSES

A problem you might run into when writing J2ME applications is how to deal with missing classes, that is, classes that are available in J2SE but not in J2ME. Note that J2ME configurations and profiles include subsets of the J2SE core classes. This means that specific methods or classes that you would normally use when writing a J2SE application are not always available. Ideally, you want to maximize the amount of code that is portable between your J2ME and J2SE applications. This tip summarizes techniques you can use to maximize this kind of portability.

A seemingly obvious technique -- taking the source code of the missing J2SE classes and adding it to your J2ME application -- is not a valid approach. There are three reasons why you can't use this approach. First, it violates Sun's licensing agreements for the Java SDKs. Second, certain J2SE classes call native methods that are not available on J2ME platforms. In particular, there is no way for CLDC-based applications to load new native methods. And finally, it's possible that a J2ME implementation might refuse to load any core classes (those whose package name starts with "java") that are not shipped as part of the implementation. So other techniques should be used to port missing J2SE classes to J2ME.

The first valid technique is to write your code so that it only uses the J2ME subset of the core classes. Use the -bootclasspath option of the javac command to replace the J2SE core classes with the J2ME core classes. For example, to compile against the CLDC classes:

javac -bootclasspath %CLDC_HOME%\bin\api\classes *.java

The compiler catches and prints any use of unsupported classes or methods.

However it's not often feasible to use only the J2ME subset, and the compiler does not always catch potential problems. For example, the CLDC forbids the use of any floating-point types, but the compiler will happily compile code that includes floating-point types.

To avoid these problems, split the code into separate classes. Split it so that one class contains the J2ME-compliant functionality and the second class contains the J2SE-only "extensions." For example, consider this Utilities class:

package com.mycompany.support;

public class Utilities {
    public static int zeroInt() { return 0; }
    
    public static double zeroDouble() { return 0.0; }
}

You can split this into two classes, both named Utilities but in different packages:

// J2ME-compliant version

package com.mycompany.support.core;

public class Utilities {
    public static int zeroInt() { return 0; }
}

// J2SE version (in a separate file)

package com.mycompany.support;

public class Utilities extends 
               com.mycompany.support.core.Utilities {
    public static int zeroDouble() { return 0.0; }
}

Notice how the J2SE version of the class extends the J2ME version. All the J2SE-only behavior is relegated to the derived class. J2ME and J2SE applications that need to use the class simply use different import statements to reference the appropriate version.

A related technique is to create facade classes for missing J2SE classes. A facade class trivially reimplements a J2SE class, often just by extending the existing class and defining matching constructors. An application uses the facade class instead of the J2SE class. You then develop a J2ME version of the facade class. The J2ME version doesn't extend the J2SE class, but implements all the required methods in a J2ME-compliant way. Finally, you adjust the application's class path to include the appropriate version of the facade class.

Take, for example, the java.io.Properties class, a commonly-used J2SE class missing from the CLDC. The Properties class is a Hashtable extension for storing name-value pairs of strings, with the ability to specify default values and to persist and reload the properties. Say the only methods you call that are unique to Properties are the getProperty methods. Then you can define the J2SE facade class like this:

public class MyProperties extends 
  java.util.Properties {
  public MyProperties() {
  }
    
  // inherits getProperty methods from parent
}

The J2ME version is a bit more complicated:

public class MyProperties extends java.util.Hashtable {
  public MyProperties() {
  }
    
  public String getProperty( String key ) {
    return (String) super.get( key );
  }

  public String getProperty( String key, String 
				     defstr ) {
    String val = getProperty( key );
    return( ( val != null ) ? val : defstr );
  }
}

In your code you just replace all references to Properties with MyProperties and ensure that the correct class is in the class path.

Extending an existing J2SE class doesn't always work. For example, the J2SE class might be final, or methods in the class might return references to other classes that must also be accessed through a facade. In these cases, it might be simpler for the facade class to implement its own similar but slightly different methods.

The final technique for dealing with missing J2SE classes is to determine whether the application is running on a J2ME platform (or not), and if it is a J2ME platform, which configuration or profile it's running with. Depending on the results, your application uses J2SE classes or J2ME classes. A simple way for an application to determine the platform is to get the value of the "microedition.configuration" system property. A CLDC-based system returns the value "CLDC-1.0". An application can check the value of the "microedition.profiles" property to determine the profile. For efficiency, you can define a class to do the platform check:

public class Platform {

  public static final boolean isCLDC;

  public static final boolean isMIDP;
    
  // Initialize the constants at runtime
    
  static {
    String config = System.getProperty( 
                        "microedition.configuration" );
    if( config != null ){
      isCLDC = ( config.indexOf( "CLDC-" ) != -1 );
    } else {
      isCLDC = false;
    }

    String profiles = System.getProperty( 
                              "microedition.profiles" );
    if( profiles != null ){
         isMIDP = ( profiles.indexOf( "MIDP-" ) != -1 );
    } else {
      isMIDP = false;
    }
  }
}

Then at any point in your application you can simply refer to the static members of the Platform class:

if( Platform.isCLDC ){
    // do CLDC-only processing here
} else {
    // do J2SE-processing here
}

Yet another way to determine which classes are available is to load classes dynamically and catch ClassNotFoundException. The application can then make decisions based on which classes are actually available. Note however, that it's not generally possible to determine if a particular method exists. On CLDC-based implementations, the NoSuchMethodError is not available; if a CLDC application attempts to invoke a missing method, the application will unceremoniously halt.

As you can see, with a bit of work you can indeed write code that is portable across both J2SE and J2ME platforms.


— 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 January 29, 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.