Archive for the ‘JSRs’ Category

Localized resource building with JSR 238: a simple custom ant task example

For today’s programming adventure, let’s tackle resource building!!!

Creating a series of jar files containing different sets of specialized resources is one of the basic reasons for setting up a build process with a tool such as Ant rather than just building with ktoolbar (as I discussed the other day). To put this in concrete terms, let’s look at a practical example that illustrates both how to write a simple Ant task and how to create resource files for the Mobile Internationalization API (JSR 238).

The Mobile Internationalization API provides a standard way to customize resources such as label strings and images and pack them efficiently into the jar file. As long as the resources are organized correctly, the API consults the platform behind the scenes to determine which set of resources should be used at any given moment. The trick is building the resource files and the correct resource directories. JSR 238 defines a special file format (.res) that allows you to pack a set of different resources together in a single, efficient binary file. As you might imagine, that means there’s a little work to be done to create the resource files — you can’t just create them by hand in a text editor as you might with a properties file.

Nokia has been kind enough to provide a reference implementation for this JSR that you can download here. The Nokia reference implementation provides both the tools to create the resource files and an emulator that allows you to test a MIDlet that uses JSR 238. All of these tools were written with the assumption that you’re using Windows — and the emulator won’t run on any other operating system — but the tools to build and test the resource files are pure Java, so you can use them on any system.

The Nokia reference implementation provides some complete sample programs as well to demonstrate how to call the resource building tools from within an Ant build file. You can see from looking at the MoonDemo that in fact that ResourceMaker tool can be integrated into an ant build as it is. Here’s the relevant snippet from the build.xml file:

<target name=”makeres” depends=”preverify”>
<java classname=”com.nokia.phone.ri.global.tools.ResourceMaker”
classpath=”${ri.home}/tools” >
<arg line=”src/res.xml src res MoonPhases en fi-FI da-DK hu-HU” />
</java>
</target>
Any java class can be called from within an Ant build file by using the “java” element (the “java” ant task) as seen here.

However, I wanted to get a better idea of how to write a custom Ant task, and this seemed like as good an application as any since the argument line is a little complicated and not terribly intuitive. So as an exercise, I wrote an Ant task that makes exactly the same call to the nokia ResourceMaker tool, but does it from a custom Ant task as follows:

<?xml version=”1.0″ encoding=”ISO-8859-1″?>

<!– A simple example ant task –>
<project name=”ResourceAntTask” default=”resources” basedir=”.”>

<!– define the task by binding it to the corresponding java code –>
<taskdef name=”buildres” classname=”net.frog_parrot.ant.BuildResources” classpath=”/home/carol/myAntTask/bin:/home/carol/JSR_238_RI_1_0/tools/ResourceMaker/bin/”/>
<!– define a custom type that is used by the task –>
<typedef name=”locale” classname=”net.frog_parrot.ant.StringElement” classpath=”/home/carol/myAntTask/bin”/>

<!– This build file’s one target calls the custom task –>
<target name=”resources”>
<buildres descriptor=”/home/carol/JSR_238_RI_1_0/demos/MoonDemo/src/res.xml” source=”/home/carol/JSR_238_RI_1_0/demos/MoonDemo/src/” destination=”.” name=”MoonPhases”>
<locale name=”en”/>
<locale name=”hu-HU”/>
</buildres>
</target>

</project>
The above is a complete build file, which would normally be named build.xml.

To create the ant task, I need to write the class BuildResources.java which extends org.apache.tools.ant.Task (see the Apache Ant JavaDoc for details on the various classes available).Here’s the code:

Continue reading

Advertisements

Creating a basic M3G file with Blender

When using the Mobile 3D Graphics API (JSR 184), it’s important to understand how to create 3D objects, as discussed in my last couple of posts. However, in a real application you will typically use files created by 3D modeling software.

Many commercial software suites allow you to export 3D scenes in M3G format (required by JSR 184), including a free one called Blender, which you can download here. Don’t let the fact that it’s free fool you — like a lot of free software, it’s a full-featured software package suitable for professional use. By default Blender doesn’t export files in M3G format — you have to install an additional plugin such as the one I found from Nelson Games here. The plugin is very easy to install, you just need to make sure that Python (version 2.4 or greater) is correctly installed, and then you place the additional script in the Blender python scripts folder.

As the Blender documentation freely admits, Blender is not terribly newbie-friendly(really it could stand a few basic tutorials…) but it’s quite powerful and configurable once you get the hang of it. Here are some basic steps to get started and create a simple M3G file:

screenshotblendercube.png

This is what you will see when you first open Blender. (Click on the image to see it full-size.)

A new file in Blender starts off with a cube near the origin. You can see what the cube looks like when rendered by selecting render. The camera used for this rendering is that little line drawing near the bottom right corner.

If you don’t feel like using the default cube, just get rid of it by just pressing the delete key. (Since the cube is initially selected by default, the delete key deletes it.) If you’d like to keep it instead, you can transform it. If you’re in object or edit mode (the first drop-down menu in the toolbar along the bottom in the above screenshot is the mode), you’ll see some little buttons with icons for different ways of transforming the objcet: a triangle for translation, a donut for rotation, and a square for scale. To add further objects, go to add > mesh as seen in the following screenshot:

screenshotaddmesh.png

At this point you have the idea of how to create a very basic file, and you can try fiddling with all of the various gizmos on your own to see what else you can do. 😀

Once you have your scene ready, you can export it in M3G format by selecting M3G under the file > export menu. This option will appear automatically if the plugin is correctly installed. If you’re using the same plugin I’ve recommended, you’ll have a choice between exporting as M3G or as Java code. The Java code option is what I like about this particular plugin (I don’t know if others offer it). But let’s start by exporting it as M3G since that’s what you’ll usually do in a real application, and talk about exporting it as Java code later in this example.

Rendering the M3G file in your Java application is even easier than creating it! (Although — as with creating the file — there’s no end to the possibilities once you get started and get the hang of it.) All you do is put your M3G file in the MIDlet’s Jar file, load it with the Loader class, find the World node, and render it using the Graphics3D class.

There are a bunch of tutorials that show these basic steps, but I’ll show them here for completeness since there are only a few essential lines. Let’s assume we called the file “cube.m3g”. Here’s the code for the initialization step:

Object3D[] allNodes = Loader.load(“/cube.m3g”);

// find the world node
for(int i = 0, j = 0; i < allNodes.length; i++) {
if(allNodes[i] instanceof World) {
myWorld = (World)allNodes[i];
}
}

Then in the paint method of the Canvas (or other rendering target), you start by binding the Graphics3D singleton to the target’s Graphics instance, then call render on the World you read from the file, then release the target. You can do more entertaining things by manipulating the scene graph that is accessible from the World node, but if all you want to do is display the image file you created, then you’re done.

/**
* Paint the graphics onto the screen.
*/
protected void paint(Graphics g) {
Graphics3D g3d = null;
try {
// Start by getting a handle to the Graphics3D
// object which does the work of projecting the
// 3-D scene onto the 2-D screen (rendering):
g3d = Graphics3D.getInstance();
// Bind the Graphics3D object to the Graphics
// instance of the current canvas:
g3d.bindTarget(g);

// Now render: (project from 3D scene to 2D screen)
g3d.render(myWorld);

} catch(Exception e) {
e.printStackTrace();
} finally {
// Done, the canvas graphics can be freed now:
g3d.releaseTarget();
}
}

In my earlier examples we had to define the camera, but when rendering a world node like this, the default camera is defined in the world’s data. This mode of rendering is called “retained mode” (the earlier examples were “immediate mode”). A typical way to think about these two modes is that immediate mode is what you would use when defining a simple object in the code whereas retained mode is what you use for rendering M3G files. That’s what it usually comes down to in practice, but that isn’t the real difference, especially since you can define a world node and complete scene graph in the code and render it in retained mode or you could extract the VertexBuffer and IndexBuffer from a Mesh you found in an M3G file and render it in immediate mode if you like.

The fact that you can create the same data in Java code as you can define in an M3G file becomes very apparent if you export your scene from Blender as Java code. In fact, that way you can look at the code and see how it’s done, tweaking it and seeing the effects of your modifications if you like. Even though the Java class generally takes more space in the jar (even after obfuscation), there are real-world applications for exporting the file as Java code since on many platforms Java code loads faster than resources from the Jar file.

The Java code produced by this plugin is quite easy to use. It creates a Java class with the name you choose when you export, and the class has a static “getRoot” method that returns the world node. The only things I had to change to get it to work were to add it to my package and change the use of “Canvas3D” to “Canvas”. (I’m not sure why Canvas3D is used here since Canvas is more widely supported and works just the same.)

If you’d like to have a look at the code it generates, I’m posting the exported code of a simple Blender-generated cube below the fold.

Continue reading

Abbey normal???

It’s just a little thing, but one point I’ve found kind of confusing when playing with the Mobile 3D graphics API (JSR 184) is how to use the normal vectors.

When you’re defining your 3-dimensional polygon, you can give each vertex some position coordinates (okay, that’s reasonable), color coordinates (kind of amusing for the color to be given by coordinates, but okay), and a normal vector.

But what is the normal vector for?

If you’re on the face of one of the triangles of your polygon, the normal vector is logically the vector perpendicular to the flat face of the triangle. So you don’t need to define a normal since the normal is determined by the triangle. I guess that’s why the places we get to define the normal vectors are at the vertices. But what does it even mean to be perpendicular to a pointy part of the surface? It might be used to give the graphics functionality a hint as to how to smooth out and round the point, except that the 3D graphics engine doesn’t do that…

The normals are used to tell the graphics engine how to make light reflect off the surface. So in a sense it does give information about how to smooth and round off the corners — at least from the point of view of the reflected light. If you define the normal vector to be perpendicular to a triangle face, light will reflect off of the triangle as if the triangle is perfectly flat, with a nice, crisp point at the vertex. But if you’d rather smooth out your surface a little — say you’re making a ball and you want it to be less obvious that it’s constructed of triangles — you can define a normal pointing directly out from the center of the ball towards each vertex, giving what the perpendicular direction (normal vector) would be if the ball were smooth at that point. (The flat-faces-and-sharp-points model requires you to define more vertices than the smoothed/rounded model because when you’re smoothing you can re-use the normal vector, whereas to get flat faces you need to define a new normal vector — hence a new corresponding vertex — for each face that meets at a given point.)

As an example, I’ve taken my pyramid from my previous example and I’ve given it some normal vectors. Here’s the set of vertices:

private short[] myVertices2 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
0, -10, 0, 10, 0, 0, 0, 0, 10
};

Recall that using a TriangleStripArray I’ve grouped the vertices into the following strip of triangles: { (0, 0, 10), (10, 0, 0), (0, 10, 0) }, { (10, 0, 0), (0, 10, 0), (0, -10, 0) }, { (0, 10, 0), (0, -10, 0), (-10, 0, 0) }, { (0, -10, 0), (-10, 0, 0), (0, 0, 10) }, plus one last triangle pasted on { (0, -10, 0), (10, 0, 0), (0, 0, 10) }.

And here are the normals I’ve defined for the vertices:

private short[] myNormals2 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
1, -1, 1, 1, -1, 1, 1, -1, 1
};

So you can see that for the top strip of triangles, I’ve defined the normal as going directly out from the center in order to get a smoothing effect. Then for the pasted on triangle (the last three vertices), I have defined all three normals as being the same vector perpendicular to the triangle to get a nice flat surface.

Here’s the result:

normal.png

The top triangle in this picture is the flat one.

In order to illustrate this better, I’ve rotated the pyramid a little and moved the camera closer. Also, I’ve switched the pyramid’s appearance from polygon mode to having a shiny surface material and I’ve placed a light source (omnidirectional, Light.OMNI) near the camera so you can see how the light reflects off the surface. If you’re using ambient light (light.AMBIENT) which lights all surfaces equally in all directions, you don’t need to bother with normals, but with the other three types of light (OMNI, DIRECTIONAL, and SPOT) the light hits the surface from a particular direction, so the normals are used to calculate how the light should bounce off.

Just for fun, I decided to see what would happen if I defined some crazy normal vectors — some “Abbey Normals” 😉 — for my pyramid:

private short[] myAbbeyNormals2 = {
0, 1, 1, -1, 0, 0, 1, 0, 1, 0, 1, -1, -1, 0, 0, 1, 1, 1,
-1, 1, 1, 1, -1, 1, 1, 1, -1
};

This isn’t necessarily useful in practice, but I was curious to see what would happen. Here’s the result:

abnormals.png

Note that in both these examples, I didn’t worry at all about the length of each normal vector. The length of the normal doesn’t matter — it isn’t taken into account. All that matters is the direction.

Here’s the code for today’s adventure. I’ve thrown in a method to rotate the pyramid in response to pressing the arrow keys so you can see it from all sides, and the corresponding MIDlet class can be found in my earlier pyramid post:

package net.frog_parrot.test;

import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;

/**
* This is a very simple example class to illustrate 3-D coordinates.
*/
public class DemoCanvas extends Canvas {

/**
* The information about where the scene is viewed from.
*/
private Camera myCamera;

/**
* The information about how to move the camera.
*/
private Transform myCameraTransform = new Transform();

/**
* The information about how to move the pyramid.
*/
private Transform myObjectTransform = new Transform();

/**
* The distance to move the camera in response to a keypress.
*/
public static final float DEFAULT_DISTANCE = 10.0f;

/**
* The background. (self-explanatory 😉 )
*/
private Background myBackground = new Background();

/**
* The set of vertices.
*/
private VertexBuffer myVertexBuffer;

/**
* The object that defines how to map the set of vertices into
* a polygon.
*/
private IndexBuffer myIndexBuffer;

/**
* Information on how the polygon should look in terms of
* color, texture, shading, etc..
*/
private Appearance myAppearance;

/**
* The list of vertices for the first example pyramid.
*/
private short[] myVertices1 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
};

/**
* The rule for how to piece together the vertices into a polygon.
*/
private int[] myTriangleStrip1 = { 6 };

/**
* The list of vertices for the second example pyramid.
*/
private short[] myVertices2 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
0, -10, 0, 10, 0, 0, 0, 0, 10
};

/**
* The list of normals for the second example pyramid.
*/
private short[] myNormals2 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
1, -1, 1, 1, -1, 1, 1, -1, 1
};

/**
* The list of crazy normals for the second example pyramid.
*/
private short[] myAbbeyNormals2 = {
0, 1, 1, -1, 0, 0, 1, 0, 1, 0, 1, -1, -1, 0, 0, 1, 1, 1,
-1, 1, 1, 1, -1, 1, 1, 1, -1
};

/**
* The rule for how to piece together the vertices into a polygon.
*/
private int[] myTriangleStrip2 = { 6, 3 };

/**
* Initialize everything.
*/
public DemoCanvas() {
try {
// Create the camera object to define where the polygon is being
// viewed from and in what way:
myCamera = new Camera();
// Set the camera so that it will project the 3-D picture onto the
// screen in perspective, with a vanishing point in the distance:
myCamera.setPerspective(60.0f, (float)getWidth() / (float)getHeight(),
1.0f, 1000.0f);

// Here we construct the VertexArray, which is a generic data
// structure for storing collections of coordinate points:
int numVertices = myVertices2.length / 3;
// specify how many vertices, plus the fact that each vertex has
// three coordinates, and each coordinate is coded on two bytes:
VertexArray va = new VertexArray(numVertices, 3, 2);
// set the data, starting from index 0:
va.set(0, numVertices, myVertices2);

// define the normals:
VertexArray na = new VertexArray(numVertices, 3, 2);
// set the data, starting from index 0:
na.set(0, numVertices, myAbbeyNormals2);

// Now create a 3-D object of it.
// Here we can group a set of different VertexArrays, one
// giving positions, one, giving colors, one giving normals:
myVertexBuffer = new VertexBuffer();
myVertexBuffer.setPositions(va, 1.0f, null);
myVertexBuffer.setNormals(na);
// Color the polygon white:
myVertexBuffer.setDefaultColor(0xffffff);

// Here we define how to piece together the vertices into
// a polygon:
myIndexBuffer = new TriangleStripArray(0, myTriangleStrip2);

// Let’s try creating a more complex appearance:
Material material = new Material();
material.setShininess(100.0f);
myAppearance = new Appearance();
myAppearance.setMaterial(material);

// color the background black:
myBackground.setColor(0x000000);

// We set the camera’s X position and Y position to 0
// so that we’re looking straight down at the origin
// of the x-y plane. The Z coordinate tells how far
// away the camera is — increasing this value takes
// you farther from the polygon, making it appear
// smaller.
myCameraTransform.postTranslate(0.0f, 0.0f, 25.0f);

// reset the object’s original orientation:
myObjectTransform.setIdentity();
} catch(Exception e) {
e.printStackTrace();
}
}

/**
* Paint the graphics onto the screen.
*/
protected void paint(Graphics g) {
try {
// Start by getting a handle to the Graphics3D
// object which does the work of projecting the
// 3-D scene onto the 2-D screen (rendering):
Graphics3D g3d = Graphics3D.getInstance();
// Bind the Graphics3D object to the Graphics
// instance of the current canvas:
g3d.bindTarget(g);
// Clear the screen by painting it with the
// background image:
g3d.clear(myBackground);

// now add the light:
Light light = new Light();
light.setMode(Light.OMNI);
light.setIntensity(2.0f);
Transform lightTransform = new Transform();
lightTransform.postTranslate(0.0f, 0.0f, 50.0f);
g3d.resetLights();
g3d.addLight(light, lightTransform);

g3d.setCamera(myCamera, myCameraTransform);

// Now render, project the 3D scene onto the flat screen:
g3d.render(myVertexBuffer, myIndexBuffer, myAppearance,
myObjectTransform);

// Done, the canvas graphics can be freed now:
g3d.releaseTarget();

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

/**
* Move the object in response to game commands.
*/
public void keyPressed(int keyCode) {
switch(getGameAction(keyCode)) {
case Canvas.UP:
myObjectTransform.postRotate(DEFAULT_DISTANCE, -1.0f, 0.0f, 0.0f);
break;
case Canvas.DOWN:
myObjectTransform.postRotate(DEFAULT_DISTANCE, 1.0f, 0.0f, 0.0f);
break;
case Canvas.RIGHT:
myObjectTransform.postRotate(DEFAULT_DISTANCE, 0.0f, 1.0f, 0.0f);
break;
case Canvas.LEFT:
myObjectTransform.postRotate(DEFAULT_DISTANCE, 0.0f, -1.0f, 0.0f);
break;
default:
break;
}
repaint();
}

}

Coordinates in JSR 184: A very simple example

I love the way the JavaDoc for Graphics3D encouragingly starts with “Using the Graphics3D is very straightforward” just before launching into the precisions about the different types of rendering modes and targets, viewports, antialiasing, dithering, etc. 😉

JSR 184 gives an incredibly rich API for creating 3-D animations (and especially games) for a MIDP handset. It’s designed to allow you to import complex computer-generated 3-D objects as well as to create your own 3-D objects by hand, with a fantastic array of options in terms of lighting, surface textures, etc. It also comes with an equally rich assortment of new terms (Mesh, IndexBuffer, TriangleStripArray, CompositingMode…) that can look like a foreign language to those new to 3-D programming. And just understanding how all of the different coordinate systems work and interact with each other is not trivial.

Not to be discouraging or anything — it’s not so daunting if you start simple. So my programming challenge that I assigned myself for today was to write the simplest 3-D example possible that illustrates the basics of how the coordinate system works and how to define a 3-D polygon.

My example is a pyramid with a square base viewed from above.

This is a two-part example: In part one, I start with just the square base with two of the triangular sides (opposite each other) attached. So picture a square paper with a triangle attached along the right edge and the left edge, and the two triangles folded up to meet at a point at the top. In part two, I start with this same incomplete pyramid and glue on a third side. From there, completeing the pyramid should be no problem. 😀

I’m going to start by posting the code so you can have a look, and then I’ll explain in a little more detail, particularly how the VertexArray works and how the TriangleStripArray defines how to piece the vertices together to form a polygon.

DemoCanvas.java:


package net.frog_parrot.test;

import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;

/**
* This is a very simple example class to illustrate 3-D coordinates.
*/
public class DemoCanvas extends Canvas {

/**
* The information about where the scene is viewed from.
*/
private Camera myCamera;

/**
* The background. (self-explanatory 😉 )
*/
private Background myBackground = new Background();

/**
* The set of vertices.
*/
private VertexBuffer myVertexBuffer;

/**
* The object that defines how to map the set of vertices into
* a polygon.
*/
private IndexBuffer myIndexBuffer;

/**
* Information on how the polygon should look in terms of
* color, texture, shading, etc..
*/
private Appearance myAppearance;

/**
* The list of vertices for the first example pyramid.
*/
private short[] myVertices1 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
};
/**
* The rule for how to piece together the vertices into a polygon.
*/
private int[] myTriangleStrip1 = { 6 };

/**
* The list of vertices for the second example pyramid.
*/
private short[] myVertices2 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
0, -10, 0, 10, 0, 0, 0, 0, 10
};
/**
* The rule for how to piece together the vertices into a polygon.
*/
private int[] myTriangleStrip2 = { 6, 3 };

/**
* Initialize everything.
*/
public DemoCanvas() {
try {
// Create the camera object to define where the polygon is being
// viewed from and in what way:
myCamera = new Camera();
// Set the camera so that it will project the 3-D picture onto the
// screen in perspective, with a vanishing point in the distance:
myCamera.setPerspective(60.0f, (float)getWidth() / (float)getHeight(),
1.0f, 1000.0f);

// Here we construct the VertexArray, which is a generic data
// structure for storing collections of coordinate points:
int numVertices = myVertices1.length / 3;
// specify how many vertices, plus the fact that each vertex has
// three coordinates, and each coordinate is coded on two bytes:
VertexArray va = new VertexArray(numVertices, 3, 2);
// set the data, starting from index 0:
va.set(0, numVertices, myVertices1);

// Now create a 3-D object of it.
// Here we could group a set of different VertexArrays, one
// giving positions, one, giving colors, one giving normals,
// but for simplicity we're only setting position coordinates:
myVertexBuffer = new VertexBuffer();
myVertexBuffer.setPositions(va, 1.0f, null);
// Color the polygon white:
myVertexBuffer.setDefaultColor(0xffffff);

// Here we define how to piece together the vertices into
// a polygon:
myIndexBuffer = new TriangleStripArray(0, myTriangleStrip1);

// We want the appearance as simple as possible, so set the
// appearance to polygon mode:
PolygonMode pm = new PolygonMode();
pm.setShading(PolygonMode.SHADE_FLAT);

myAppearance = new Appearance();
myAppearance.setPolygonMode(pm);

// color the background black:
myBackground.setColor(0x000000);
} catch(Exception e) {
e.printStackTrace();
}
}

/**
* Paint the graphics onto the screen.
*/
protected void paint(Graphics g) {
try {
// Start by getting a handle to the Graphics3D
// object which does the work of projecting the
// 3-D scene onto the 2-D screen (rendering):
Graphics3D g3d = Graphics3D.getInstance();
// Bind the Graphics3D object to the Graphics
// instance of the current canvas:
g3d.bindTarget(g);
// Clear the screen by painting it with the
// background image:
g3d.clear(myBackground);

// Now set where we're viewing the scene from:
Transform cameraTransform = new Transform();
// We set the camera's X position and Y position to 0
// so that we're looking straight down at the origin
// of the x-y plane. The Z coordinate tells how far
// away the camera is -- increasing this value takes
// you farther from the polygon, making it appear
// smaller. Try changing these values to view the
// polygon from different places:
cameraTransform.postTranslate(0.0f, 0.0f, 100.0f);
g3d.setCamera(myCamera, cameraTransform);

// Now set the location of the object.
// if this were an animation we would probably
// translate or rotate it here:
Transform objectTransform = new Transform();
objectTransform.setIdentity();

// Now render: (Yay!!! finally!!!)
g3d.render(myVertexBuffer, myIndexBuffer, myAppearance, objectTransform);

// Done, the canvas graphics can be freed now:
g3d.releaseTarget();

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

}

***

The first example (using myVertices1 and myTriangleStrip1) gives a result that looks like this:

pyramid1.png

The second example (using myVertices2 and myTriangleStrip2) gives a result that looks like this:

pyramid2.png

In the first example, I constructed my TriangleStripArray with the arguments 0 and a one-element array: { 6 }. That means start from the first vertex in the vertex array (actually the zeroth element — you know what I mean), and then make one strip of triangles from the six vertices. The triangles are defined by taking all sets of three consecutive vertices, the first one starting from the first vertex, the second one starting from the second vertex, etc. So each triangle in the triangle strip shares a side with the next triangle and shares another side with the previous triangle as you can see from this example:

Vertex array #1 looks like this: { 0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10 },– where every three values together form one vertex and three vertices form a triangle — so the corresponding strip is made of the following set of trianges: { (0, 0, 10), (10, 0, 0), (0, 10, 0) }, { (10, 0, 0), (0, 10, 0), (0, -10, 0) }, { (0, 10, 0), (0, -10, 0), (-10, 0, 0) }, { (0, -10, 0), (-10, 0, 0), (0, 0, 10) }. (Here I’ve surrounded the xyz-coordinates of each vertex in parentheses and each triangle in brackets so you can see the list of triangles more easily — this doesn’t represent any syntax that appears in the code.) Note that the first (and last) vertex is the top point and the middle two triangles together form the square base of the pyramid.

Even though the square base is part of the polygon, it turned out black on the screen. That’s because by default the inside faces are invisible; not rendered.  If you’d like the inside faces to be visible, then set your PolygonMode’s culling to CULL_NONE. The computer determines which side is “inside” and which side is “outside” by whether the vertices of the triangle are defined in clockwise or counter-clockwise order. I’d explain how to figure out which side is which if I weren’t dyslexic — normally in such cases I guess, then compile and run, then invert the values if I guessed wrong. 😉

In the second example, I constructed my TriangleStripArray with the arguments 0 and a two-element array: { 6, 3 }. This creates the same strip of triangles as the first one, and then makes another strip of triangles from the next three vertices it finds. Three vertices make one triangle, so using the larger second example array we get the additional triangle { (0, -10, 0), (10, 0, 0), (0, 0, 10) } giving one additional side. This side is white the way I’ve defined it, but if I’d defined it in the wrong order (i.e. attached it backwards), it would appear black from my camera’s angle. (Note that I could actually have used myVertices2 for both examples and the result would have been the same.)

For completeness, I’ll post the simple MIDlet class that goes with this. Also note that this program uses the type float, and I found I had to set my project to CLDC-1.1 (under Settings > API Selection in ktoolbar) to get it to compile:


package net.frog_parrot.test;

import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;

/**
* A simple 3D example.
*/
public class TestMIDlet extends MIDlet implements CommandListener {

private Command myExitCommand = new Command("Exit", Command.EXIT, 1);
private DemoCanvas myCanvas = new DemoCanvas();

/**
* Initialize the Displayables.
*/
public void startApp() {
myCanvas.addCommand(myExitCommand);
myCanvas.setCommandListener(this);
Display.getDisplay(this).setCurrent(myCanvas);
myCanvas.repaint();
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}

/**
* Change the display in response to a command action.
*/
public void commandAction(Command command, Displayable screen) {
if(command == myExitCommand) {
destroyApp(true);
notifyDestroyed();
}
}

}

Bluetooth Checkers!!!

My bluetooth checkers game is ready to go!!!

If your MIDP 2 handset supports JSR 82 (Bluetooth API), you can download and install it (free) from my usual checkers wml page or my games html page!!!

I promise this is the last Checkers version (for a long time anyway 😉 ), and next I’ll move on to some graphics discussions. But I wanted to take one standard game and program it using a few different protocols to compare and contrast how each one works in a multiplayer game.

I think Bluetooth is a little more challenging and complicated to program than SMS. Part of the reason for that is that SMS uses the operators’ standard way of routing communications to a handset. You find the right destination by typing in the right phone number. And it’s the same phone number you’d use to route other comunications to that device. So you don’t have to reinvent the wheel to find, identify, and authenticate the device you’re looking for.

Bluetooth allows you to find all active, bluetooth-enabled devices within range and see what kind of services they offer. Each server offering a bluetooth service publishes a set of detailed service records describing the services available. So you have a fair amount of work on the server side creating useful descriptions and on the client side reading the descriptions and choosing the right service on the right device. Add to that the fact that you have the option of choosing a plain socket type connection or discrete packets, different authentication and/or encryption schemes, and even object-oriented communications using Java OBEX, you have practically unlimited potential to customize (and complicate 😉 ) your application according to your needs.

In my case, I decided I wanted to make my game’s communications as simple as possible. So I started with the assumption — since bluetooth is a protocol for local communications — that you and your friend are in the same room together and have verbally agreed to start up a game of checkers on your two handsets. My checkers game is fundamentally a peer-to-peer type game, but bluetooth is a client-server protocol, so I had my game start with a screen where the user can choose whether his handset will be acting as the client or as the server (the first player selects server and the second client).

Then there was the question of writing the code so that the two devices will find each other instead of finding other random bluetooth devices and services. Fortunately — with a simple services like this one — all you really need is a UUID. You can create your own UUID (in both Linux and Windows) using the uuidgen command. Then on the server side, all you need to do is construct your bluetooth URL using your UUID and open the connection and wait for clients to call you up. My game’s server URL looks like this:

btspp://localhost:2BBC2D287C8C11DBA1500040F45842EF;name=Checkers;authorize=false

The “btspp” indicates that I’d like to use the bluetooth streaming (socket-like) protocol, the “localhost” makes me a server; then I add on my UUID (what the “uuidgen -t” with the hyphens removed), then some attributes (the application name and the fact that I don’t want to require authorization). Opening this connection creates the corresponding service record, which you can get a handle to and add attributes to if you like. The WTK’s BluetoothDemo shows a good example of how to add additional descriptive attributes. The BluetoothDemo has a server that makes images available for bluetooth clients to download, and there you can see the programmer has added a custom attribute to give the name of the image being offered. As you can see in the ServiceRecord JavaDoc, there are a number of common standard attributes that you can use as well. The attributes aren’t too complicated to use — you just need to keep in mind that different attributes values can be of different types so the values are wrapped in DataElements that allow you to handle all of the different types of values in a consistent way.

Once your server is up and running, the next thing is to program the client to find it and connect to it. Basically, all you need to do is grab a handle to the local DiscoveryAgent and send it off in search of devices and services. It toodles around on its own thread looking for them, then it calls the assigned DiscoveryListener when it’s done.

Discovery is a two-step procedure: First you gather a list of devices, then you search the devices for the service you want. (Since I didn’t bother with any special service record attributes on the server side, when I started searching my device list for services I sent a null attrSet to the searchServices method.) Note that if you’re writing a program where the client frequently reconnects to the same set of known servers, you can optimize discovery by caching a list of known devices.

It might seem like you could simplify the discovery procedure by calling the method DiscoveryAgent.selectService() which finds all of the available connections that provide the desired service (without first getting all the devices and searching each one). Or that you could optimize by searching each device for services as you find it rather than first finding all devices and then searching them one by one. However, over on developer.sun.com I found this interesting little powerpoint presentation full of bluetooth programming best practices, and this little slideshow specifically says not to do either one of those things. Because they seem like good ideas, but really the lead to nothing but badness. And I figure that if anyone would know the people over at developer.sun.com would, so I decided to follow their advice.

Once the client side of the program and the server side find each other, the rest is a breeze! Whew! From then on it’s just standard Java socket programming. The server’s acceptAndOpen command stops blocking and returns a handle to the connection. Both the client and the server open the InputStream and OutputStream of their respective connections, and start sending each other data. All you have to do is send the right data down the stream and read it and interpret it correctly on the other side. What could be easier? 😉

For the bluetooth version of the checkers game, I eliminated the “taunt” feature I’d added for SMS Checkers. With SMS checkers, you can play against your friend wherever he may be. When you start up the game and enter your friend’s phone number — thanks to the push registry — the invitation SMS will buzz his handset to invite him to play whether he’s on the other side of the world or in a meeting or whatever. So it’s useful to be able to send a text message along with every move. In the bluetooth version you essentially have to have already agreed together that you want to play, so the taunt feature is less useful. It would have been possible to hook up the bluetooth version to the push registry as well, but I figured it was already complicated enough as it is.

So please try it out and tell me what you think!

More about JSR 75

For today’s JSR adventure, I improved my SMS checkers game — as promised — by adding a feature to read contacts from the handset address book to choose opponents. The new version is available as CheckersPlus. Then I figured that since I’m playing with JSR 75, I should write a sample program to test the other half of JSR 75: the FileConnection API.

I like the elegance of the FileConnection API, and in particular the way it’s designed to function just like the various network connection APIs (using Connector.open with a URL, returning a Connection with an InputStream and and OutputStream just as any a network connection). It’s a very natural way of accessing files both from the Java perspective (where files have traditionally been accessed through the same streams as sockets, etc.) and in the general networking universe (where users are already accustomed to reading local files in an Internet browser using a file://-type url).

Another cool advantage to FileConnection is that you can access parts of the handset’s memory that you can’t touch from MIDP’s standard little RMS memory sandbox. I found by listing the available file roots on my Sagem my700x that I can access the images folder. So for some handsets it may be possible to have a Java MIDlet store a wallpaper or a ringtone as a wallpaper or a ringtone by storing it in the right place where the handset can use it.

But that brings us to the usual disadvantage: you have to know the particular handset very well to know which folders you have access to and how to access them. So it’s great if your project has a small set of target handsets, and not so great if you want your application to be useful on all MIDP handsets across the board. The RMS (Record Management System) has the advantage of always being supported, so it’s probably better to use the RMS if all you want is to set some simple data aside and find it again later.

Of course, even the RMS isn’t as predictible as one might like. There are handsets where RMS access is slower than FileConnection access (and some where it’s not), some that handle individual records in unexpected ways (setting aside a big space for each record even if it’s not used, or failing to free up the memory of a deleted record). Then there’s the fact that the RMS is not convenient for pooling data among multiple MIDlet suites: To share a record store between suites, you need to leave the access door open to every MIDlet on the device, plus the record store is deleted when the associated MIDlet suite is deleted even if other MIDlet suites still wanted to access the data.

So, there’s a bit of a trade-off, meaning it’s better to know both the FileConnection API as well as the RMS, and choose which one to use depending on your application.

For today’s fun, here’s a sample class to illustrate the FileConnection API, doing a few very basic, standard things like listing the available roots and files; reading, writing, creating a test file, etc. As with PIM, FileConnection access is done in its own thread:

package net.frog_parrot.test;

import java.util.Enumeration;
import java.io.*;

import javax.microedition.io.*;
import javax.microedition.io.file.*;

/**
* A simple file Connection testing utility.
*/
public class FCRunner extends Thread {

FCTest myMidlet;

FCRunner(FCTest test) {
myMidlet = test;
}

public void run() {
FileConnection rootdir = null;
try {
Enumeration items = FileSystemRegistry.listRoots();

// Now print the available roots:
while(items.hasMoreElements()) {
String rootname = (String)(items.nextElement());
myMidlet.display(“\n *** new root: ” + rootname);

// open the root directory:
// note there are three slashes before the root name
// because there is no “host” for this connection:
rootdir = (FileConnection)Connector.open(
“file:///” + rootname);
// List the current files:
Enumeration ls = rootdir.list();
while(ls.hasMoreElements()) {
String filename = (String)(ls.nextElement());
myMidlet.display(” file: ” + filename);

// print the contents of the file:
FileConnection file = null;
try {
file = (FileConnection)Connector.open(
“file:///” + rootname + “/” + filename);
if(file.canRead()) {
InputStream is = file.openInputStream();
byte[] contents = new byte[25];
int len = is.read(contents);
is.close();
myMidlet.display(” contents: ”
+ new String(contents, 0, len));
} else {
myMidlet.display(” * not readable”);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
file.close();
} catch(Exception e) {}
}
}

// now try to create a file:
FileConnection newfile = null;
try {
newfile = (FileConnection)Connector.open(
“file:///” + rootname + “myNewFile”);
if(newfile.exists()) {
OutputStream os = newfile.openOutputStream();
os.write((new String(“overwriting old contents”)).getBytes());
os.close();
} else {
newfile.create();
OutputStream os = newfile.openOutputStream();
os.write((new String(“creating new contents”)).getBytes());
os.close();
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
newfile.close();
} catch(Exception e) {}
}
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
rootdir.close();
} catch(Exception e) {}
}
}

}

For completeness, here’s the MIDlet class to use to run the above FileConnection-testing class.  These two classes together make a complete MIDlet that can be easily built by creating a new project for it in the WTK:

package net.frog_parrot.test;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.midlet.MIDlet;

/**
* A simple MIDlet to test which APIs are implemented.
*/
public class FCTest extends MIDlet implements CommandListener {

private Command myExitCommand = new Command(“Exit”, Command.EXIT, 1);
private Command myOkCommand = new Command(“OK”, Command.OK, 1);
private Form myResultScreen;

/**
* Empty constructor.
*/
public FCTest() {
}

/**
* Initialize the Displayables.
*/
public void startApp() {
myResultScreen = new Form(“Results”);
myResultScreen.addCommand(myExitCommand);
myResultScreen.addCommand(myOkCommand);
myResultScreen.setCommandListener(this);
Display.getDisplay(this).setCurrent(myResultScreen);
}

/**
* Implementation of MIDlet.
*/
public void pauseApp() {
}

/**
* Implementation of MIDlet.
*/
public void destroyApp(boolean unconditional) {
}

/**
* Respond to a button push.
*/
public void commandAction(Command command, Displayable screen) {
if(command == myExitCommand) {
destroyApp(true);
notifyDestroyed();
} else {
FCRunner runner = new FCRunner(this);
runner.start();
}
}

/**
* Append a string to the current display.
*/
public void display(String str) {
myResultScreen.append(str);
myResultScreen.append(“\n”);
}

}

A simple PIM example

Now that I’ve got my SMS Checkers game up and running, I’d like to play it against a friend. But I don’t want to have to type my friend’s mobile number into that little phone number text field every time — really I don’t even know his number off the top of my head since I always just call people by selecting numbers from my address book. What to do?

This looks like a job for JSR 75: PIM!!!

JSR 75 also covers the File Connection API, but for today let’s talk about the Personal Information Management API.

Browsing through the JavaDoc, it seems pretty clear that this API is one where they’ve opted for flexibility over simplicity. The addressbooks, calendars, and to-do lists on the various handsets each have a fixed structure, however there’s no reason to expect all of these personal information datastores to have the same structure as each other. So the PIM API was designed to allow you to find the fields and records you need regardless of the underlying structrure of the datastore.

It’s actually a little confusing to try to figure out from the JavaDoc how to get the information you want. Fortunately, there aren’t too many classes. The classes to focus on are PIMItem and PIMList, which essentially describe records and record stores respectively. Between these two classes (and subclasses such as Contact), you’ll find the methods you need to determine what fields (such as TEL) a record has data for, which attributes (such as HOME or MOBILE) are associated with the field, and what type of data the (such as String or int) is contained in the field.

Confused yet?

My first instinct in such a case is to just write a simple example program.

It took a few tries, so I was a little disappointed that there was no clear() method to delete the whole ContactList so I could start over. Yet, it’s probably wise of the API designers to have left that off considering that in real life if your program deletes the user’s entire address book — even if he gave permission to modify it — this would lead to a “bad user experience” in every case. So it’s better not to even have the method there to tempt you to deliberately write some malicious code, which is a no-no in Java ;-).

The solution I found (for the WTK) is just to delete all the files in the following directory whenever I wanted to start over with a clean slate: WTK2.2/appdb/DefaultColorPhone/pim/contacts/Contacts/

The following class creates a couple of very simple records then iterates through the resulting record store printing out exactly what kind of data structure was created. Note that this example isn’t something you’d put in a real application (especially given all the System.out.println() statements). Also note that I made it a subclass of Thread. Accessing the PIM system is a potentially blocking operation that should not be done from any method that needs to return immediately such as startApp() or commandAction().

Here’s the code:

package net.frog_parrot.test;

import java.util.Enumeration;

import javax.microedition.pim.*;

/**
* A simple PIM testing utility.
*/
public class PIMRunner extends Thread {

  public void run() {
      try {
        ContactList addressbook
            = (ContactList)(PIM.getInstance().openPIMList(
            PIM.CONTACT_LIST, PIM.READ_WRITE));
        Contact contact = null;

        // Each PIMItem -- new or found -- is associated with
        // a particular PIMList.
        contact = addressbook.createContact();
        if(addressbook.isSupportedField(Contact.FORMATTED_NAME)) {
          contact.addString(Contact.FORMATTED_NAME, Contact.ATTR_NONE,
             "Lynn Hanson");
        }
        if(addressbook.isSupportedField(Contact.TEL)) {
          contact.addString(Contact.TEL, Contact.ATTR_HOME,
             "555-HOME-NUMBER");
          contact.addString(Contact.TEL, Contact.ATTR_MOBILE,
                            "555-MOBILE-NUMBER");
        }
        // Here's a quick search to see if this contact
        // is already present in the addressbook:
        Enumeration matching = addressbook.items(contact);
        if(matching.hasMoreElements()) {
          System.out.println("found the first contact");
        } else {
          System.out.println("adding the first contact");
          contact.commit();
        }
      
        // Let's create one more contact:
        contact = addressbook.createContact();
        if(addressbook.isSupportedField(Contact.FORMATTED_NAME)) {
          contact.addString(Contact.FORMATTED_NAME, Contact.ATTR_NONE,
             "Spencer Hobbs");
        }
        if(addressbook.isSupportedField(Contact.TEL)) {
          contact.addString(Contact.TEL, Contact.ATTR_HOME,
             "5555-HOME-NUMBER");
          contact.addString(Contact.TEL, Contact.ATTR_MOBILE,
             "5555-MOBILE-NUMBER");
        }
        matching = addressbook.items(contact);
        if(matching.hasMoreElements()) {
          System.out.println("found the second contact");
        } else {
          System.out.println("adding the second contact");
          contact.commit();
        }
            
        // Now print the contents of the addressbook:
        Enumeration items = addressbook.items();
        while(items.hasMoreElements()) {
          System.out.println("\n *** new item ***");
          contact = (Contact)(items.nextElement());
          int[] fields = contact.getFields();
          for(int i = 0; i < fields.length; i++) {
            int fieldIndex = fields[i];
            System.out.println(" field " + fieldIndex + ": "
                + addressbook.getFieldLabel(fieldIndex));
            int dataType = addressbook.getFieldDataType(fieldIndex);
            System.out.println(" * data type: " + dataType);
            if(dataType == PIMItem.STRING) {
              for(int j = 0; j < contact.countValues(fieldIndex); j++) {
                int attr = contact.getAttributes(fieldIndex, j);
                System.out.print(" " + j + ". (");
                System.out.print(addressbook.getAttributeLabel(attr) + "): ");
                System.out.println(contact.getString(fieldIndex, j));
              }
            }
          }
        }
      } catch(Exception e) {
        e.printStackTrace();
      }
  }
  
}

And here’s what it printed out:

adding the first contact
adding the second contact

*** new item ***
field 105: Formatted Name
* data type: 4
0. (None): Lynn Hanson
field 114: Revision
* data type: 2
field 115: Tel.
* data type: 4
0. (Home): 555-HOME-NUMBER
1. (Mobile): 555-MOBILE-NUMBER
field 117: UID
* data type: 4
0. (None): 1.vcf

*** new item ***
field 105: Formatted Name
* data type: 4
0. (None): Spencer Hobbs
field 114: Revision
* data type: 2
field 115: Tel.
* data type: 4
0. (Home): 5555-HOME-NUMBER
1. (Mobile): 5555-MOBILE-NUMBER
field 117: UID
* data type: 4
0. (None): 2.vcf

Mobile Zoo!!!

My planned post for today was going to be some interaction exercises (either HTTP or SMS — I hadn’t decided), but it looks like I won’t have anything post-worthy on that front before Wednesday at the earliest (I got sidetracked this weekend by some household tasks), so instead I’ll tell you about playing with the new alpha download from MobileZoo. It should be kosher to blog about this since I don’t see any way this could be a competitor to MobileScope.

I’ve chosen this topic not only because I find MobileZoo’s concept intriguing, but also on the principle that — since this is a new blog — I give preferential treatment to anyone who leaves a comment on my blog. Even if the comment is just an ad for your new start-up. 😉  (As long as the comment is relevant to my post of course.)  My goal is to get so many comments that the welcome default comment from “Mr. WordPress” drops out of the “Recent Comments” section of my sidebar. Sure I could just delete it, but that would be cheating…

The idea of MobileZoo appears to be the creation of a centralized database of all of the precise specs a Java developer might need to optimize an application for a given handset (including which JSRs are supported with version numbers if applicable, plus screen and canvas size and colors, etc.). All of this information is harvested by a MIDlet along the same lines as the MIDlet I posted the other day only more extensive, gathering up every bit of information about the handset that is accessible via Java. Then the MIDlet sends this information to MobileZoo. It looks like the revenue model is to offer a premium service and/or a stats-gathering library to registered developers. (Or maybe they’re just planning to get paid through putting ads on their site?)

So, I downloaded their jar of goodies to try it out.

The first thing I noticed was that the download is a jar file alone. I guess that’s okay since this product/service is aimed at developers, and one can reasonably expect that a Java ME developer would know how to take a jar file from his local PC and get it installed on a handset. However, personally I don’t have a cable or other simple way of connecting my handset directly to my PC, so I ended up writing my own jad (descriptor) file for it. For that I had to open the jar file and copy a bunch of lines out of the mainfest file and then add the jar size and jar url attributes by hand. Then I uploaded the files to my site so I could download them onto my handset from there. Since this project depends on persuading as many people as possible to run this program on their handsets, MobileZoo might want to make their MIDlet available on a WAP page (if it isn’t already) and post the URL on their main page. There’s no reason not to do that since it would only take a few minutes to add this feature to their site, and it might make it easier for some novice developers and other random users.

It’s pretty clear that it’s an alpha. Some of the links on the site don’t work, the “français” page is the same as the English page, there are grammatical errors, etc. (Of course I’m hardly one to taalk — I’ve noticed that my previous blog entry is riddled with typos, but I can’t go back and correct it without screwing up the formatting on the code sample because of the screwey blog editing software. But that’s an unrelated minor squabble between me and Mr. WordPress. 😉 .) Anyway, every good idea has to start somewhere.

I was almost a little hesitant to run this program on my handset, since it’s an unknown application that I just happened to download off the Internet that’s going to read information from my handset and then send that information to some unknown site on the Internet. However — given Java’s security system — I don’t think there’s really much danger that a MIDlet running with the “untrusted” security level can do any harm. (I know it’s running as “untrusted” since there’s no digital signature in the jad file I wrote for it…) I double-checked the PIM API JavaDoc to make sure that an untrusted MIDlet can’t read information from my addressbook without asking for permission, so I know they’re not even trying to harvest my contacts’ phone numbers for some nefarious marketing purposes or something. Even so, since I’m not much of a trusting soul I guess, I ran it first on the WTK to see what it was going to do. (There it had a strange Exception, but ran anyway.)

And here’s the result.

Amusingly, the program failed to identify my handset as a Sagem — it looks like this is the first Sagem they’ve captured stats for.

All in all, this looks like a fun idea. My one concern would be to wonder how they’ll persuade enough people to install and run their program. It costs the user effort and network time, and the user doesn’t get much in return except detailed developer-level information about the local handset. I guess that’s enough to make it interesting for me, but for how many others? Maybe they should reward the user by showing an animation of a dancing monkey during the upload? Hehe, just kidding, but it seems like it would be a good idea to somehow make this little procedure more cool and fun so kids will recommend it on forums or mySpace or whatever… 😉

JSRs galore!!!

One of the things I had to learn about when I switched from programming Enterprise Edition Java to Mobile Edition was Java Specification Requests. Of course I was vaguely aware of JSRs and the Java Community Process as en Enterprise Edition programmer, but I didn’t really have to deal with them much in my daily life. (This was back in the old days when it was called “J2EE” 😉 )

But when you’re programming for mobile phones, as much as you’d like it to really be as simple as “write once, run anywhere,” devices unfortunately vary in implementation (behavior) as well as in terms of which standard (optional) APIs are available. So you get to know your JSRs, and keep track of which ones your program needs in order to run.

The very first new thing I wanted to try on my new Sagem my700x was to write a simple program to tell me which JSRs are available for me to play with. This info is probably available through everyone’s best friend (Google), but you’d probably have to dig around for it. Normally the specs you find on the Internet (like here) will tell you if the handset has Java, and whether it’s MDIP 1 or MIDP 2 and not much more. That’s okay, because it sets us up for a fun little exercise!!!

Let’s make a simple MIDlet to find out what JSR APIs are implemented on this handset!!!

It’s maybe not realistic to check for all possible JSRs, but at least we can check for the ones that are implemented in the WTK. Running the ktoolbar application and selecting the “Settings” button (after opening a new project), I get a window that lists the following optional JSRs: JSR 75 (PIM), JSR 75 (File Connection), JSR 82 (Bluetooth), JSR 135 (Mobile Media), JSR 172 (Web Services RMI), JSR 172 (Web Services SAX), JSR 184 (3D Graphics), (JSR 205 Wireless Messaging).

I looked them all up here and downloaded the final draft documentation for each one.

The next step was to write a MIDlet that attempts to load a class from each optional package. I assume that if one class is there, the whole package is probably there too. For fun I decided to check for MobileScope’s EGE API as well!!! 😀

The result on the WTK was exactly as expected: all of the above APIs are supported except MobileScope’s EGE API.

Here’s the result for the Sagem m700x:

JSR 75 PIM: supported,
JSR 75 File Connection: supported,
JSR 82 Bluetooth: not supported,
JSR 135 Mobile Media: supported,
JSR 172 Web Services RMI: not supported,
JSR 172 Web Services SAX: not supported,
JSR 184 3D Graphics: supported,
JSR 205 Wireless Messaging: not supported,
EGE: supported.

If you’d like to try the MIDlet on your own handset, I’ve put it on my site here: http://www.frog-parrot.net/hello.wml

Here’s the code:


package net.frog_parrot.test;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form ;
import javax.microedition.lcdui.List;
import javax.microedition.midlet.MIDlet;
/**
* A simple MIDlet to test which APIs are implemented.
*/
public class Test extends MIDlet implements CommandListener {
  
  private Command m_exitCommand = new Command("Done", Command.EXIT, 1);
  private List m_menu;
  private Form m_resultScreen;
  /**
   * The list of API labels.
   */
  private String[] m_jsrs = {
    "JSR 75 PIM",
    "JSR 75 File Connection",
    "JSR 82 Bluetooth",
    "JSR 135 Mobile Media",
    "JSR 172 Web Services RMI",
    "JSR 172 Web Services SAX",
    "JSR 184 3D Graphics",
    "JSR 205 Wireless Messaging",
    "EGE",
  };
  
  /**
   * The index constants for the label array.
   */
  private static final String SUPPORTED = "Supported";
  private static final String NOT_SUPPORTED = "Not Supported";
  private static final int JSR_75_PIM = 0;
  private static final int JSR_75_FC = 1;
  private static final int JSR_82_BLUETOOTH = 2;
  private static final int JSR_135_MOBILE_MEDIA = 3;
  private static final int JSR_172_WEB_SERVICES_RMI = 4;
  private static final int JSR_172_WEB_SERVICES_SAX = 5;
  private static final int JSR_184_3D_GRAPHICS = 6;
  private static final int JSR_205_WIRELESS_MESSAGING = 7;
  private static final int EGE = 8;
  
  /**
   * Empty constructor.
   */
  public Test() {
  }
  
  /**
   * Initialize the Displayables.
   */
  public void startApp() {
    m_menu = new List("Test Menu", List.IMPLICIT, m_jsrs, null);
    m_menu.addCommand(m_exitCommand);
    m_menu.setCommandListener(this);
    Display.getDisplay (this).setCurrent(m_menu);
    m_resultScreen = new Form("Results");
    m_resultScreen.addCommand(m_exitCommand);
    m_resultScreen.setCommandListener(this);
  }
  
  public void pauseApp() {
  }
  
  public void destroyApp(boolean unconditional) {
  }
  
  /**
   * Change the display in response to a command action.
   */
  public void commandAction(Command command, Displayable screen) {
    if(screen == m_menu) {
      if(command == m_exitCommand) {
        destroyApp(false);
        notifyDestroyed();
      } else {
        m_resultScreen.deleteAll();
        int index = m_menu.getSelectedIndex();
        switch(index) {
        case JSR_75_PIM:
          m_resultScreen.append(m_jsrs[JSR_75_PIM] + ": ");
          try {
            Class.forName("javax.microedition.pim.PIMException");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case JSR_75_FC:
          m_resultScreen.append(m_jsrs[JSR_75_FC] + ": ");
          try {
            Class.forName("javax.microedition.io.file.ConnectionClosedException");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case JSR_82_BLUETOOTH:
          m_resultScreen.append(m_jsrs[JSR_82_BLUETOOTH] + ": ");
          try {
            Class.forName("javax.bluetooth.BluetoothConnectionException");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case JSR_135_MOBILE_MEDIA:
          m_resultScreen.append(m_jsrs[JSR_135_MOBILE_MEDIA] + ": ");
          try {
            Class.forName("javax.microedition.media.MediaException");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case JSR_172_WEB_SERVICES_RMI:
          m_resultScreen.append(m_jsrs[JSR_172_WEB_SERVICES_RMI] + ": ");
          try {
            Class.forName("java.rmi.RemoteException");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case JSR_172_WEB_SERVICES_SAX:
          m_resultScreen.append(m_jsrs[JSR_172_WEB_SERVICES_SAX] + ": ");
          try {
            Class.forName("org.xml.sax.SAXParseException");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case JSR_184_3D_GRAPHICS:
          m_resultScreen.append(m_jsrs[JSR_184_3D_GRAPHICS] + ": ");
          try {
            Class.forName("javax.microedition.m3g.CompositingMode");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case JSR_205_WIRELESS_MESSAGING:
          m_resultScreen.append(m_jsrs[JSR_205_WIRELESS_MESSAGING] + ": ");
          try {
            Class.forName("javax.wireless.messaging.SizeExceededException");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        case EGE:
          m_resultScreen.append(m_jsrs[EGE] + ": ");
          try {
            Class.forName("com.infusio.ege.toolkit.TLV");
            m_resultScreen.append(SUPPORTED);
          } catch(ClassNotFoundException e) {
            m_resultScreen.append(NOT_SUPPORTED);
          } catch(Exception e) {
            m_resultScreen.append(e.getClass()
                                  + ": " + e.getMessage());
          }
          Display.getDisplay(this).setCurrent(m_resultScreen);
          break;
        default:
          break;
        }
      }
    } else {
      Display.getDisplay (this).setCurrent(m_menu);
    }
  }
}