» Publishers, Monetize your RSS feeds with FeedShow: More infos (Show/Hide Ads)
Here are some alternatives to J2ME that can be used for rich mobile applications:
- Flash Lite is the Macromedia Flash profile specifically developed for mobile phones.
- OPL (open source!) is an interpreted BASIC-like language purpose-built for Symbian OS.
- Nokia's Amaretto (Python for Series 60) is not public yet and is available for Nokia PRO members.
- AppForge's tools work with .NET and VB. They are targeted NOT only to Windows-based mobile devices but also to Palm OS and Symbian ones.
Why to seek for J2ME alternatives at all? Because it is impossible to write, say, zip utility using MIDP (even 2.0) since there in no file API available and all the interaction with device storage is limited to RecordStore API. Some vendors include proprietary API to access the device file system. It is not always the case (as I have observed with Nokia 6600).
JSR 75 specifies optional packages for features that are commonly found on PDAs and other mobile devices in the J2ME space: one package for Personal Information Management (PIM) access, and one package for accessing file systems through the Generic Connection Framework (GCF).
Michael blogs: on Nokia 9500, the J2ME File I/O and PIM optional package (JSR 75) was implemented but is only accessible from the CDC/Personal Profile runtime... The Nokia 6630 phone is the first consumer phone from Nokia that supports JSR 75. The only caveat is that according to the MIDP spec, in order to access PIM data from JSR 75 APIs, you have to digitally sign your MIDlet...
I have not included Mobile Basic to the above list because it is implemented inside of boundaries of MIDP and is not a way out for the goals in question.
Here are some alternatives to J2ME that can be used for rich mobile applications:
- Flash Lite is the Macromedia Flash profile specifically developed for mobile phones.
- OPL (open source!) is an interpreted BASIC-like language purpose-built for Symbian OS.
- Nokia's Amaretto (Python for Series 60) is not public yet and is available for Nokia PRO members.
- AppForge's tools work with .NET and VB. They are targeted NOT only to Windows-based mobile devices but also to Palm OS and Symbian ones.
Why to seek for J2ME alternatives at all? Because it is impossible to write, say, zip utility using MIDP (even 2.0) since there in no file API available and all the interaction with device storage is limited to RecordStore API. Some vendors include proprietary API to access the device file system. It is not always the case (as I have observed with Nokia 6600).
JSR 75 specifies optional packages for features that are commonly found on PDAs and other mobile devices in the J2ME space: one package for Personal Information Management (PIM) access, and one package for accessing file systems through the Generic Connection Framework (GCF).
Michael blogs: on Nokia 9500, the J2ME File I/O and PIM optional package (JSR 75) was implemented but is only accessible from the CDC/Personal Profile runtime... The Nokia 6630 phone is the first consumer phone from Nokia that supports JSR 75. The only caveat is that according to the MIDP spec, in order to access PIM data from JSR 75 APIs, you have to digitally sign your MIDlet...
I have not included Mobile Basic to the above list because it is implemented inside of boundaries of MIDP and is not a way out for the goals in question.
Esmertec will be demonstrating its Jbed Advanced mobile JVM solution with Jcap Brand Manager during JavaOne 2004, as TMCnet.com reports.
The purpose of Jcap Brand Manager is to provide mobile device vendors with possibilities of flexible customization of J2ME software look and feel on branded devices.
The L&F; flexibility is interesting, but the new question is whether mobile Java developers are ready to the resulting variety of what their applications can look like on customized phones and other devices.
Esmertec will be demonstrating its Jbed Advanced mobile JVM solution with Jcap Brand Manager during JavaOne 2004, as TMCnet.com reports.
The purpose of Jcap Brand Manager is to provide mobile device vendors with possibilities of flexible customization of J2ME software look and feel on branded devices.
The L&F; flexibility is interesting, but the new question is whether mobile Java developers are ready to the resulting variety of what their applications can look like on customized phones and other devices.
The javax.microedition.rms package provides the Record Management System (RMS) implementation. It is a kind of DBMS, but its API doesnot resemble JDBC or any of other J2SE APIs. The primary concept of RMS is the RecordStore. The RecordStore element is the Record, and Record's essence is a byte array.
I have created a special adaptor to the RMS. It doesnot cover all the RMS capabilities but allows us to use RMS as a backend for a Map-like object structure. There is no java.util.Map interface in MIDP, so our "RecordMap" adapter uses java.util.Hashtable as a pattern. Even more, it extends java.util.Hashtable. It is a pure interface inheritance, all the methods are overridden in our RecordMap.
Our RecordMap is configurable in that it can be parameterized with different implementations of our own EntryMapper interface to provide the concrete functionality of the data mapping between Hashtable key-value pairs and RecordStore byte arrays.
package j2mefancier;
public interface EntryMapper {
byte[] bytes(Object key, Object value);
Object key(byte[] bytes);
Object value(byte[] bytes);
}
Then, here is a couple of small utility classes used by our RecordMap:
package j2mefancier;
import java.util.*;
import javax.microedition.rms.*;
public class EnumAdapter implements Enumeration {
private RecordEnumeration re;
private EntryMapper mapper;
private boolean isKey;
public EnumAdapter(RecordStore rs, EntryMapper mapper, boolean isKey) throws RecordStoreException {
this.re = rs.enumerateRecords(null, null, false);
this.mapper = mapper;
this.isKey = isKey;
}
public boolean hasMoreElements() {
return re.hasNextElement();
}
public Object nextElement() {
try {
byte[] bytes = re.nextRecord();
return isKey ? mapper.key(bytes) : mapper.value(bytes);
} catch (RecordStoreException e) {
return null;
}
}
}
package j2mefancier;
import javax.microedition.rms.*;
public class Filter implements RecordFilter {
private Object obj;
private EntryMapper mapper;
private boolean isKey;
public Filter(Object obj, EntryMapper mapper, boolean isKey) {
if (obj == null) throw new NullPointerException();
this.obj = obj;
this.mapper = mapper;
this.isKey = isKey;
}
public boolean matches(byte[] candidate) {
return obj.equals(isKey ? mapper.key(candidate) : mapper.value(candidate));
}
}
Now, look at the implementation of the EntryMapper interface I use for own purposes. Note that it allows only String keys and values, so the best use case of this implementation is for RMS backend of Properties-like data. Again, there is no java.util.Properties in MIDP, otherwise I'd prefer to use Properties API instead of Hashtable one. Other use cases require more intelligent entry mappers.
package j2mefancier;
import java.io.*;
public class EntryMapperImpl implements EntryMapper {
public byte[] bytes(Object key, Object value) {
if (! (key instanceof String) || ! (value instanceof String))
throw new IllegalArgumentException();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
dos.writeUTF(key.toString());
dos.writeUTF(value.toString());
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
return baos.toByteArray();
}
public Object key(byte[] bytes) {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
try {
return dis.readUTF();
} catch (IOException e) {
return null;
}
}
public Object value(byte[] bytes) {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
try {
dis.readUTF();
return dis.readUTF();
} catch (IOException e) {
return null;
}
}
}
And here is the heart of the thing: the RecordMap class. I don't insist on its implementation optimality etc., the main purpose is to demonstrate the idea. Probably you can develop more efficient implementations for some use cases and also add more clever exception handling. The usual Hashtable toString() functionality is omitted to keep the code more compact.
package j2mefancier;
import java.util.*;
import javax.microedition.rms.*;
public class RecordMap extends Hashtable {
private RecordStore rs;
private EntryMapper mapper;
public RecordMap(RecordStore rs, EntryMapper mapper) {
this.rs = rs;
this.mapper = mapper;
}
public void clear() {
try {
RecordEnumeration re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement()) rs.deleteRecord(re.nextRecordId());
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public boolean contains(Object value) {
try {
RecordEnumeration re = rs.enumerateRecords(new Filter(value, mapper, false), null, false);
return re.hasNextElement();
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public boolean containsKey(Object key) {
return get(key) != null;
}
public Enumeration elements() {
try {
return new EnumAdapter(rs, mapper, false);
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public Object get(Object key) {
try {
RecordEnumeration re = rs.enumerateRecords(new Filter(key, mapper, true), null, false);
if (re.hasNextElement()) return mapper.value(re.nextRecord());
return null;
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public boolean isEmpty() {
return size() == 0;
}
public Enumeration keys() {
try {
return new EnumAdapter(rs, mapper, true);
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public Object put(Object key, Object value) {
if (value == null) throw new NullPointerException();
try {
Object old = remove(key);
byte[] bytes = mapper.bytes(key, value);
rs.addRecord(bytes, 0, bytes.length);
return old;
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public Object remove(Object key) {
try {
RecordEnumeration re = rs.enumerateRecords(new Filter(key, mapper, true), null, false);
if (re.hasNextElement()) {
Object old = mapper.value(re.nextRecord());
rs.deleteRecord(re.previousRecordId());
return old;
}
return null;
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public int size() {
try {
return rs.getNumRecords();
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public String toString() {
try {
return "RecordMap-" + rs.getName();
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
private RuntimeException wrapped(RecordStoreException e) {
return new RuntimeException(e.getMessage());
}
}
The javax.microedition.rms package provides the Record Management System (RMS) implementation. It is a kind of DBMS, but its API doesnot resemble JDBC or any of other J2SE APIs. The primary concept of RMS is the RecordStore. The RecordStore element is the Record, and Record's essence is a byte array.
I have created a special adaptor to the RMS. It doesnot cover all the RMS capabilities but allows us to use RMS as a backend for a Map-like object structure. There is no java.util.Map interface in MIDP, so our "RecordMap" adapter uses java.util.Hashtable as a pattern. Even more, it extends java.util.Hashtable. It is a pure interface inheritance, all the methods are overridden in our RecordMap.
Our RecordMap is configurable in that it can be parameterized with different implementations of our own EntryMapper interface to provide the concrete functionality of the data mapping between Hashtable key-value pairs and RecordStore byte arrays.
package j2mefancier;
public interface EntryMapper {
byte[] bytes(Object key, Object value);
Object key(byte[] bytes);
Object value(byte[] bytes);
}
Then, here is a couple of small utility classes used by our RecordMap:
package j2mefancier;
import java.util.*;
import javax.microedition.rms.*;
public class EnumAdapter implements Enumeration {
private RecordEnumeration re;
private EntryMapper mapper;
private boolean isKey;
public EnumAdapter(RecordStore rs, EntryMapper mapper, boolean isKey) throws RecordStoreException {
this.re = rs.enumerateRecords(null, null, false);
this.mapper = mapper;
this.isKey = isKey;
}
public boolean hasMoreElements() {
return re.hasNextElement();
}
public Object nextElement() {
try {
byte[] bytes = re.nextRecord();
return isKey ? mapper.key(bytes) : mapper.value(bytes);
} catch (RecordStoreException e) {
return null;
}
}
}
package j2mefancier;
import javax.microedition.rms.*;
public class Filter implements RecordFilter {
private Object obj;
private EntryMapper mapper;
private boolean isKey;
public Filter(Object obj, EntryMapper mapper, boolean isKey) {
if (obj == null) throw new NullPointerException();
this.obj = obj;
this.mapper = mapper;
this.isKey = isKey;
}
public boolean matches(byte[] candidate) {
return obj.equals(isKey ? mapper.key(candidate) : mapper.value(candidate));
}
}
Now, look at the implementation of the EntryMapper interface I use for own purposes. Note that it allows only String keys and values, so the best use case of this implementation is for RMS backend of Properties-like data. Again, there is no java.util.Properties in MIDP, otherwise I'd prefer to use Properties API instead of Hashtable one. Other use cases require more intelligent entry mappers.
package j2mefancier;
import java.io.*;
public class EntryMapperImpl implements EntryMapper {
public byte[] bytes(Object key, Object value) {
if (! (key instanceof String) || ! (value instanceof String))
throw new IllegalArgumentException();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
dos.writeUTF(key.toString());
dos.writeUTF(value.toString());
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
return baos.toByteArray();
}
public Object key(byte[] bytes) {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
try {
return dis.readUTF();
} catch (IOException e) {
return null;
}
}
public Object value(byte[] bytes) {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
try {
dis.readUTF();
return dis.readUTF();
} catch (IOException e) {
return null;
}
}
}
And here is the heart of the thing: the RecordMap class. I don't insist on its implementation optimality etc., the main purpose is to demonstrate the idea. Probably you can develop more efficient implementations for some use cases and also add more clever exception handling. The usual Hashtable toString() functionality is omitted to keep the code more compact.
package j2mefancier;
import java.util.*;
import javax.microedition.rms.*;
public class RecordMap extends Hashtable {
private RecordStore rs;
private EntryMapper mapper;
public RecordMap(RecordStore rs, EntryMapper mapper) {
this.rs = rs;
this.mapper = mapper;
}
public void clear() {
try {
RecordEnumeration re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement()) rs.deleteRecord(re.nextRecordId());
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public boolean contains(Object value) {
try {
RecordEnumeration re = rs.enumerateRecords(new Filter(value, mapper, false), null, false);
return re.hasNextElement();
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public boolean containsKey(Object key) {
return get(key) != null;
}
public Enumeration elements() {
try {
return new EnumAdapter(rs, mapper, false);
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public Object get(Object key) {
try {
RecordEnumeration re = rs.enumerateRecords(new Filter(key, mapper, true), null, false);
if (re.hasNextElement()) return mapper.value(re.nextRecord());
return null;
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public boolean isEmpty() {
return size() == 0;
}
public Enumeration keys() {
try {
return new EnumAdapter(rs, mapper, true);
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public Object put(Object key, Object value) {
if (value == null) throw new NullPointerException();
try {
Object old = remove(key);
byte[] bytes = mapper.bytes(key, value);
rs.addRecord(bytes, 0, bytes.length);
return old;
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public Object remove(Object key) {
try {
RecordEnumeration re = rs.enumerateRecords(new Filter(key, mapper, true), null, false);
if (re.hasNextElement()) {
Object old = mapper.value(re.nextRecord());
rs.deleteRecord(re.previousRecordId());
return old;
}
return null;
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public int size() {
try {
return rs.getNumRecords();
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
public String toString() {
try {
return "RecordMap-" + rs.getName();
} catch (RecordStoreException e) {
throw wrapped(e);
}
}
private RuntimeException wrapped(RecordStoreException e) {
return new RuntimeException(e.getMessage());
}
}
About three weeks ago there appeared an info about the BBC plans of TV+Mobile integration via J2ME. I remember the Alan's comment: "With plans like these for J2ME, I'm not worried about J2ME's future".
And now these plans are turned into reality: according to the BBC press release, for the first time in the UK BBC viewers can participate in a television gameshow using their mobile phones.
The show is "Come and Have a Go... If You Think You're Smart Enough". It allows viewers to compete in real time with a live audience for a significant cash prize. The player downloads a J2ME application that is engaged via a registration code at the start of the show. The J2ME-based interactivity is enabled by a software developed by BBC New Media & Technology in conjunction with Developnet.
Good luck to players! Good luck, BBC! And long live J2ME!
About three weeks ago there appeared an info about the BBC plans of TV+Mobile integration via J2ME. I remember the Alan's comment: "With plans like these for J2ME, I'm not worried about J2ME's future".
And now these plans are turned into reality: according to the BBC press release, for the first time in the UK BBC viewers can participate in a television gameshow using their mobile phones.
The show is "Come and Have a Go... If You Think You're Smart Enough". It allows viewers to compete in real time with a live audience for a significant cash prize. The player downloads a J2ME application that is engaged via a registration code at the start of the show. The J2ME-based interactivity is enabled by a software developed by BBC New Media & Technology in conjunction with Developnet.
Good luck to players! Good luck, BBC! And long live J2ME!
Where are the borders of Mobile Java land? As justly mentioned by Alan, J2ME is more than MIDP. But the land of Java mobility is even more than just J2ME nowadays.
The recently announced Motorola E680 is powered by Linux/Java OS named MotoJUIX. The motojuix.com/net/org domain names are registered by Motorola but there is no significant content at the moment.
Esmertec have presented their Jcap CLDC Wireless Component Application Platform. While CLDC is usually considered just as a basis for MIDP, it appears that Esmertec's brainchild facilitates the development of fully customized mobile phones. The jcap.net domain is owned by Ecmertec, but the site says just "Under Construction".
SavaJe has announced big steps forward in getting its SavaJe Java OS onto mobile phones.
Java moves towards the heart of mobile technologies!
Where are the borders of Mobile Java land? As justly mentioned by Alan, J2ME is more than MIDP. But the land of Java mobility is even more than just J2ME nowadays.
The recently announced Motorola E680 is powered by Linux/Java OS named MotoJUIX. The motojuix.com/net/org domain names are registered by Motorola but there is no significant content at the moment.
Esmertec have presented their Jcap CLDC Wireless Component Application Platform. While CLDC is usually considered just as a basis for MIDP, it appears that Esmertec's brainchild facilitates the development of fully customized mobile phones. The jcap.net domain is owned by Ecmertec, but the site says just "Under Construction".
SavaJe has announced big steps forward in getting its SavaJe Java OS onto mobile phones.
Java moves towards the heart of mobile technologies!
One of the most significant abilities of J2ME is the wireless network connectivity. There is a Generic Connection Framework (GCF) that provides a uniform way of dealing with network resources regardless of a concrete protocol. Let's create an example of a mobile application that uses GCF with HTTP that is the most wide-spread protocol for web-based applications.
Our application is intended to make its user aware of the current Earth population amount according to the U.S. Census Bureau. We'll use the POPClock web page of Census.
Some sites provide an API for web services access to their content (with SOAP, XML-RPC etc.) but it is not the case with Census POPClock, so the solution is to "scrap" their HTML page. Note: some commercial sites disallow such scrapping either by license or by checking the client type ("user agent"), although the user agent info can be forged using HTTP headers.
The data will be delivered through InputStream that is not buffered, so let's create a simple buffer class.
import java.io.*;
public class InBuffer {
private InputStream is;
private byte[] buf;
private int bufSize = 0, bufIdx = 0;
public InBuffer(InputStream is, int size) {
this.is = is;
buf = new byte[size];
}
public int read() throws IOException {
if (bufIdx >= bufSize) {
if ((bufSize = is.read(buf)) < 0) return -1;
bufIdx = 0;
}
return buf[bufIdx++] & 0xff;
}
}
We need an ability of data skipping and collecting. Let's create a simple parser now. Our parser is straightforward (it is even unable to find "XY" bytes in "XXY" stream) but suffices for our purposes. After all, it is not a parser-building exercise... Anyway I have named it "skipper" instead of "parser" ;-).
import java.io.*;
public class InSkipper {
private InBuffer buffer;
public InSkipper (InputStream is) {
buffer = new InBuffer(is, 512);
}
public boolean skip(byte[] stop) throws IOException {
int i = 0, r;
while (true) {
if ((r = buffer.read()) < 0) return false;
if ((byte) r != stop[i]) i = 0;
else if (++i == stop.length) return true;
}
}
public boolean collect(byte alien, StringBuffer dest) throws IOException {
int r;
while (true) {
if ((r = buffer.read()) < 0) return false;
if ((byte) r == alien) return true;
dest.append((char) r);
}
}
}
And here is the midlet class that employs Connector and InputConnection of the Generic Connection Framework.
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
public class Population extends MIDlet implements CommandListener {
private final String TITLE = getAppProperty("title");
private final String DATA_LOCATION = getAppProperty("dataLocation");
private final byte[] SKIP_TO = getAppProperty("skipTo").getBytes();
private final byte COLLECT_TO = (byte) getAppProperty("collectTo").charAt(0);
private Command refreshCommand = new Command("Refresh", Command.SCREEN, 1);
private Command exitCommand = new Command("Exit", Command.EXIT, 2);
private Form form = new Form(TITLE);
private StringItem item = new StringItem(null, "");
public Population() {
form.append(item);
form.addCommand(refreshCommand);
form.addCommand(exitCommand);
form.setCommandListener(this);
}
protected void startApp() {
Display.getDisplay(this).setCurrent(form);
refresh();
}
protected void destroyApp(boolean unconditional) {}
protected void pauseApp() {}
public void commandAction(Command c, Displayable d) {
if (c == exitCommand) notifyDestroyed();
else if (c == refreshCommand) refresh();
}
private void refresh() {
item.setText("Loading...");
String result = "Error";
try {
InputConnection con = (InputConnection) Connector.open(DATA_LOCATION, Connector.READ);
InputStream is = con.openInputStream();
try {
InSkipper skipper = new InSkipper(is);
StringBuffer buf = new StringBuffer();
if (skipper.skip(SKIP_TO) && skipper.collect(COLLECT_TO, buf)) result = buf.toString();
} finally {
is.close();
con.close();
}
} catch (IOException e) {
}
item.setText(result);
}
}
Notice: the midlet is configurable. The application title and data parsing parameters are obtained using getAppProperty() method.
Here is the list of custom manifest entries:
title: Population
dataLocation: http://www.census.gov/cgi-bin/ipc/popclockw
skipTo: <h1>
collectTo: <
Now we can tune the application to use another data source and another data meaning without any midlet code changes.
One of the most significant abilities of J2ME is the wireless network connectivity. There is a Generic Connection Framework (GCF) that provides a uniform way of dealing with network resources regardless of a concrete protocol. Let's create an example of a mobile application that uses GCF with HTTP that is the most wide-spread protocol for web-based applications.
Our application is intended to make its user aware of the current Earth population amount according to the U.S. Census Bureau. We'll use the POPClock web page of Census.
Some sites provide an API for web services access to their content (with SOAP, XML-RPC etc.) but it is not the case with Census POPClock, so the solution is to "scrap" their HTML page. Note: some commercial sites disallow such scrapping either by license or by checking the client type ("user agent"), although the user agent info can be forged using HTTP headers.
The data will be delivered through InputStream that is not buffered, so let's create a simple buffer class.
import java.io.*;
public class InBuffer {
private InputStream is;
private byte[] buf;
private int bufSize = 0, bufIdx = 0;
public InBuffer(InputStream is, int size) {
this.is = is;
buf = new byte[size];
}
public int read() throws IOException {
if (bufIdx >= bufSize) {
if ((bufSize = is.read(buf)) < 0) return -1;
bufIdx = 0;
}
return buf[bufIdx++] & 0xff;
}
}
We need an ability of data skipping and collecting. Let's create a simple parser now. Our parser is straightforward (it is even unable to find "XY" bytes in "XXY" stream) but suffices for our purposes. After all, it is not a parser-building exercise... Anyway I have named it "skipper" instead of "parser" ;-).
import java.io.*;
public class InSkipper {
private InBuffer buffer;
public InSkipper (InputStream is) {
buffer = new InBuffer(is, 512);
}
public boolean skip(byte[] stop) throws IOException {
int i = 0, r;
while (true) {
if ((r = buffer.read()) < 0) return false;
if ((byte) r != stop[i]) i = 0;
else if (++i == stop.length) return true;
}
}
public boolean collect(byte alien, StringBuffer dest) throws IOException {
int r;
while (true) {
if ((r = buffer.read()) < 0) return false;
if ((byte) r == alien) return true;
dest.append((char) r);
}
}
}
And here is the midlet class that employs Connector and InputConnection of the Generic Connection Framework.
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
public class Population extends MIDlet implements CommandListener {
private final String TITLE = getAppProperty("title");
private final String DATA_LOCATION = getAppProperty("dataLocation");
private final byte[] SKIP_TO = getAppProperty("skipTo").getBytes();
private final byte COLLECT_TO = (byte) getAppProperty("collectTo").charAt(0);
private Command refreshCommand = new Command("Refresh", Command.SCREEN, 1);
private Command exitCommand = new Command("Exit", Command.EXIT, 2);
private Form form = new Form(TITLE);
private StringItem item = new StringItem(null, "");
public Population() {
form.append(item);
form.addCommand(refreshCommand);
form.addCommand(exitCommand);
form.setCommandListener(this);
}
protected void startApp() {
Display.getDisplay(this).setCurrent(form);
refresh();
}
protected void destroyApp(boolean unconditional) {}
protected void pauseApp() {}
public void commandAction(Command c, Displayable d) {
if (c == exitCommand) notifyDestroyed();
else if (c == refreshCommand) refresh();
}
private void refresh() {
item.setText("Loading...");
String result = "Error";
try {
InputConnection con = (InputConnection) Connector.open(DATA_LOCATION, Connector.READ);
InputStream is = con.openInputStream();
try {
InSkipper skipper = new InSkipper(is);
StringBuffer buf = new StringBuffer();
if (skipper.skip(SKIP_TO) && skipper.collect(COLLECT_TO, buf)) result = buf.toString();
} finally {
is.close();
con.close();
}
} catch (IOException e) {
}
item.setText(result);
}
}
Notice: the midlet is configurable. The application title and data parsing parameters are obtained using getAppProperty() method.
Here is the list of custom manifest entries:
title: Population
dataLocation: http://www.census.gov/cgi-bin/ipc/popclockw
skipTo: <h1>
collectTo: <
Now we can tune the application to use another data source and another data meaning without any midlet code changes.
Alan have posted an interesting idea: Making J2ME app delivery easier.
I have made some comments there but now I feel a need to describe my approach for midlet delivering in more details.
I have faced the same problem of unwillingness to enter long URLs of midlet JADs with a tiny mobile phone keyboard. So I came up with and idea of bookmarking a single WAP page that could be updated by me. I post JAD locations there (from my desktop, not from a mobile phone) and then go to the bookmarked page to see the link in the mobile phone WAP browser. After the link opening, the midlet installation process is started. Everyone who is able to bookmark a WAP page then is allowed to visit it, to check for updates etc. We can tell our friends "I have posted a new link at my distribution point, go and see if you wish!". It can be used as a bookmark manager in a way similar to del.icio.us.
I use wappy.to site for these purposes occasionally. Any site can be used if it allows WAP page posting and has a short URL.
Alan have posted an interesting idea: Making J2ME app delivery easier.
I have made some comments there but now I feel a need to describe my approach for midlet delivering in more details.
I have faced the same problem of unwillingness to enter long URLs of midlet JADs with a tiny mobile phone keyboard. So I came up with and idea of bookmarking a single WAP page that could be updated by me. I post JAD locations there (from my desktop, not from a mobile phone) and then go to the bookmarked page to see the link in the mobile phone WAP browser. After the link opening, the midlet installation process is started. Everyone who is able to bookmark a WAP page then is allowed to visit it, to check for updates etc. We can tell our friends "I have posted a new link at my distribution point, go and see if you wish!". It can be used as a bookmark manager in a way similar to del.icio.us.
I use wappy.to site for these purposes occasionally. Any site can be used if it allows WAP page posting and has a short URL.
I have checked what's Inside the Record Management System (Part 3 of "J2ME 101" by John Muchow) and...
What do you think about this code?
// Does the text exist?
if (searchText != null && str.indexOf(searchText) != -1)
return true;
else
return false;
The pattern "if something return true else return false" is amusing... Then I have explored the text of the "Core J2ME" book. The same "uber-pattern" there... several times.
I can propose even more impressive variant:
if (...)
return true;
else
return !true;
;-)~
Update:
I absolutely agree it does not actually matter since it produces the same bytecode as the "return the_original_boolean_value". Even more, this "return true / return false" variant can be more convenient for debugging since we can set a breakpoint at "return" lines. Anyway, some "patterns" from a programming textbook can impact our subconsciousness... as it is described at the "idiot counter" part of the "Anonymous Inner Classes" issue of The Java Specialists' Newsletter.
I have checked what's Inside the Record Management System (Part 3 of "J2ME 101" by John Muchow) and...
What do you think about this code?
// Does the text exist?
if (searchText != null && str.indexOf(searchText) != -1)
return true;
else
return false;
The pattern "if something return true else return false" is amusing... Then I have explored the text of the "Core J2ME" book. The same "uber-pattern" there... several times.
I can propose even more impressive variant:
if (...)
return true;
else
return !true;
;-)~
Update:
I absolutely agree it does not actually matter since it produces the same bytecode as the "return the_original_boolean_value". Even more, this "return true / return false" variant can be more convenient for debugging since we can set a breakpoint at "return" lines. Anyway, some "patterns" from a programming textbook can impact our subconsciousness... as it is described at the "idiot counter" part of the "Anonymous Inner Classes" issue of The Java Specialists' Newsletter.
The MIDP versions of packages such as java.lang differ from their J2SE counterparts in that they include only a subset of the J2SE classes, methods and fields. As an example, java.lang.Boolean have lost its TRUE and FALSE constants as well as Boolean.valueOf(boolean) method. What's a big deal with this? Of course we can use new Boolean(boolean) to achieve the required functionality.
But let's look into the J2SE source:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
If we create 1000 Booleans with "new" then we get 1000 Boolean instances. If we create 1000 Booleans with valueOf(boolean) then we get 1000 "flyweight" references: each of them is either Boolean.TRUE or Boolean.FALSE.
There are other capabilities missing from MIDP Boolean such as Boolean.valueOf(String), new Boolean(String) and Boolean.getBoolean(String). The first pair looks similar to Integer.valueOf(String) versus new Integer(String). Both of these Integer creation ways are possible with MIDP. Despite the spirit of the total economy in J2ME we have a duplicate functionality with Integers but are unable to use flyweight approach with Booleans where it really makes sense.
At the J2SE source code we can see
public static Boolean valueOf(String s) {
return toBoolean(s) ? TRUE : FALSE;
}
while private toBoolean method contains
private static boolean toBoolean(String name) {
return ((name != null) && name.equalsIgnoreCase("true"));
}
The Boolean.getBoolean(String) is probably the most confusing method in the whole J2SE: I have met several developers thinking it is something like Boolean.valueOf(String).booleanValue() while it is a near equivalent of Boolean.valueOf(System.getProperty(String)).booleanValue().
System.getProperty(String) is present at MIDP while System.getProperties() is missing. Why so? Just because J2SE System.getProperties() returns java.util.Properties instance. There is no java.util.Properties among the MIDP classes.
Now let's save the J2ME world ;-)
public class Booleans {
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public static Boolean valueOf(String s) {
return toBoolean(s) ? TRUE : FALSE;
}
private static boolean toBoolean(String name) {
return name != null && name.toLowerCase().equals("true");
}
}
There was a gotcha: MIDP String.equalsIgnoreCase() is just nonexistent! Fortunately we can substitute the name.equalsIgnoreCase("true") with name.toLowerCase().equals("true") that is OK for MIDP.
I have not included the getBoolean(String) to avoid the J2SE-style confusion regarding its meaning. You can always implement it yourself using our Booleans.valueOf(String) method.
Update:
Boolean.TRUE and Boolean.FALSE constants and String.equalsIgnoreCase(String) method have been added to the CLDC 1.1. It still lacks Boolean.valueOf(...).
The MIDP versions of packages such as java.lang differ from their J2SE counterparts in that they include only a subset of the J2SE classes, methods and fields. As an example, java.lang.Boolean have lost its TRUE and FALSE constants as well as Boolean.valueOf(boolean) method. What's a big deal with this? Of course we can use new Boolean(boolean) to achieve the required functionality.
But let's look into the J2SE source:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
If we create 1000 Booleans with "new" then we get 1000 Boolean instances. If we create 1000 Booleans with valueOf(boolean) then we get 1000 "flyweight" references: each of them is either Boolean.TRUE or Boolean.FALSE.
There are other capabilities missing from MIDP Boolean such as Boolean.valueOf(String), new Boolean(String) and Boolean.getBoolean(String). The first pair looks similar to Integer.valueOf(String) versus new Integer(String). Both of these Integer creation ways are possible with MIDP. Despite the spirit of the total economy in J2ME we have a duplicate functionality with Integers but are unable to use flyweight approach with Booleans where it really makes sense.
At the J2SE source code we can see
public static Boolean valueOf(String s) {
return toBoolean(s) ? TRUE : FALSE;
}
while private toBoolean method contains
private static boolean toBoolean(String name) {
return ((name != null) && name.equalsIgnoreCase("true"));
}
The Boolean.getBoolean(String) is probably the most confusing method in the whole J2SE: I have met several developers thinking it is something like Boolean.valueOf(String).booleanValue() while it is a near equivalent of Boolean.valueOf(System.getProperty(String)).booleanValue().
System.getProperty(String) is present at MIDP while System.getProperties() is missing. Why so? Just because J2SE System.getProperties() returns java.util.Properties instance. There is no java.util.Properties among the MIDP classes.
Now let's save the J2ME world ;-)
public class Booleans {
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public static Boolean valueOf(String s) {
return toBoolean(s) ? TRUE : FALSE;
}
private static boolean toBoolean(String name) {
return name != null && name.toLowerCase().equals("true");
}
}
There was a gotcha: MIDP String.equalsIgnoreCase() is just nonexistent! Fortunately we can substitute the name.equalsIgnoreCase("true") with name.toLowerCase().equals("true") that is OK for MIDP.
I have not included the getBoolean(String) to avoid the J2SE-style confusion regarding its meaning. You can always implement it yourself using our Booleans.valueOf(String) method.
Update:
Boolean.TRUE and Boolean.FALSE constants and String.equalsIgnoreCase(String) method have been added to the CLDC 1.1. It still lacks Boolean.valueOf(...).
Imagine we want a massive midlet P to populate the record store with a data. Then we'd like (never returning to the P midlet) continuously use this data from another massive midlet U - so massive that P and U are unable to co-exist, say because of the device resource limitations.
Midlets within a midlet suite can access each other's record stores directly. So maybe we can just remove our P after its work done? Unfortunately no: it is impossible to remove separate midlets from a suite.
Then P and U should belong to different suites. But MIDP 1.0 API only allows the midlet suite's own record stores manipulation. There are no possibilities for record sharing between midlets from different suites in MIDP 1.0.
New MIDP 2.0 API allows cross-suite record store sharing if the midlet creating the RecordStore gives such permission. Is our task solved with two suites containing P and U respectively? Not yet. When a midlet suite is removed from a platform all the record stores associated with its midlets will also be removed. So after removing the suite containing P its data is lost.
But our MAX_INTEGER is not 2 ;-) so we can add a third midlet suite with a small "gateway" midlet that creates a record store (or just reopens if run more than once) using MIDP 2.0 openRecordStore(String recordStoreName, boolean createIfNecessary, int authmode, boolean writable).
import javax.microedition.midlet.*;
import javax.microedition.rms.*;
public class Gateway extends MIDlet {
protected void startApp() {
try {
RecordStore.openRecordStore("GatewayRS", true, RecordStore.AUTHMODE_ANY, true).closeRecordStore();
} catch (RecordStoreException e) {
}
notifyDestroyed();
}
protected void destroyApp(boolean unconditional) {
}
protected void pauseApp() {
}
}
Now the record store can be shared with other midlet suites, so P midlet can populate it with a data that will persist after P's life end and will be used by a newly arrived midlet U.
To obtain our record store from P or U we should use openRecordStore(String recordStoreName, String vendorName, String suiteName) from MIDP 2.0. Here we need to consult with the Gateway's JAD (as in Java Application Descriptor) because the vendorName and the suiteName correspond to the MIDlet-Vendor and MIDlet-Name attributes in the manifest of the suite that created the record store.
MIDlet-Name: Gateway
MIDlet-Vendor: J2ME Fancier
So, the record store lookup should be performed as:
RecordStore rs = RecordStore.openRecordStore("GatewayRS", "J2ME Fancier", "Gateway");
Don't forget, we need MIDP 2.0 for this magic!







