Back to the     section ...

Porting DynaWorks to a J2ME implementation

If you want to use DynaWorks in an other environment than currently provided, you can port the DynaWorks framework to that platform. All you have to do is to implement the interfaces needed by the framework to run in an environment - and you can compile and run your DynaWorks application on the platform of your choice.

It would be even possible (and not too complicated either) to write an emulator for DynaWork using J2SE that would run as a Java apllet from the web in any browser: you wouldn't even need a PalmOS™ ROM image to get that to work!

Writing a port is straight forward: The functionality is very PalmOS™-oriented, so any J2ME™ implementation running on PalmOS™ is likely to support this (still) limited set of methods to make the port easy (This may change in the next versions as DynaWorks will support more PalmOS™ functionality over the time...).

To demonstrate the things you have to do during a port, this section describes all steps that were neccessary to port DynaWorks to the KJava environment that comes with the KVM / CLDC from Sun.


Overview

The image on the right (also found in the Basic DynaWorks design section of this manual) shows the basic components that are needed to run a DynaWorks application in a PalmOS™/J2ME™ environment:

The details of the underlaying implementation are hidden to the DynaWorks application (and developer); all 'communication' with implementation dependent features is routed through the singelton instance Environment that delegates the request to the actual Implementation running on the device.

So the very first thing a DynaWorks application should do is to tell the environment, which Implementation to use. You can use wrapper classes to do so (see the documentation for further details) or the first statement in the main method of the start-up class should be Environment.setImpl (new J9Impl()) or something like that to initialize the environment. This must be done before anything else on the device can be done from the application!

There are four processes/objects that are implementation-dependent:

  1. Registering the event queue:
    To receive events (user interactions, notifications, ...) from the PalmOS™, an application has to register an EventQueue object. This is done via the method Environment.register(EventQueue).

  2. Accessing the screen:
    To draw on the screen, the application has to ask the Environment for a Graphics object. The application can than use the methods defined on the object to perform graphics operations.

  3. Accessing databases:
    To access databases stored on the PalmOS™ device (custom or built-in databases), the application has to ask the

    Environment to create an 'access object' based on database identifiers passed in as arguments to the method.

  4. Accessing the speaker (audio):
    To play predefined system tones on the device, the application has to ask the Environment for an Audio object. The application can than use the methods defined on the object to play sound snippets.

Involved interfaces

There are four interfaces in the DynaWorks framework that must be implemented if you want to use a certain PalmOS™/J2ME™ environment:

InterfaceDescription
brf.j2me.dynaworks.env.Implementation Class factory for implementation dependend object instances like Graphics, Audio and Database objects. An Implememtation can also register EventQueue instances with the internal PalmOS™ event loop.
brf.j2me.dynaworks.env.Graphics A Graphics object represents the screen of the device the application is running on. There is only one Graphics object in DynaWorks (singleton instance) and the screen size is supposed to be 160 x 160 pixel.

A reference to the Graphics object is returned by the getGraphics() method of an Implementation object.

brf.j2me.dynaworks.env.Audio An Audio object represents the audio system of the device and allows the playback of a limited number of 'system tones'.

A reference to the Audio object is returned by the getAudio() method of an Implementation object.

brf.j2me.dynaworks.env.Database A Database object represents a persistent object store on the device. It allows multiple Databases identified by a string that can be read and written with custom records.

A Database object is created by the getDatabase() method of an Implementation object.

All these interfaces must be implemented for each environment you want to use. The next sections describe these interfaces in more detail. Each of these sections also include the source code for the KJava implementation.

Of course each implementing class requires a constructor method. You are free to use any constructor arguments you need to perform the object creation in your environment. This comes in handy, since you can use complex initialization arguments if you need and the applications will never need to know: objects are never created via the new operator in a DynaWorks application, but thrught the class factory methods in the Implementation interface Although not explicitly defined by the interface, DynaWorks requires these constructors to confirm to certain rules; these rules are explained for each interface.

Back to top of page ...


The `Audio´ interface

Let's start with an easy one - the Audio interface:

public interface Audio {
:
  // play a sound.
  void playSound (int sound);
	
  // dispose any allocated resources.
  void dispose ();
}

The Audio interface allows the application to playback sound snippets defined by the system. There is no support for any kind of other sound snippets or MP3 files - all tones are predefined by the hardware platform. This is a limited approach, but sound is not a main issue in DynaWorks yet.

The following sound constants are defined by the interface:

<<Constants>>
NameValue
SOUND_ALARM0
SOUND_CLICK1
SOUND_CONFIRMATION2
SOUND_ERROR3
SOUND_INFO4
SOUND_STARTUP5
SOUND_WARNING6
The following methods need to be implemented:
Audio (String param) (<init>)>
The constructor for the Audio class. The argument list and the implementation depend on the platform and runtime environment.
void playSound (int sound)
Use to play one sound from a list of predefined 'system sounds'. These sounds are defined as constants in the class (see above) and should sound different to each other when played (or the sound wouldn't carry an information ?!).
void dispose ()
Called by the framework when the implementation goes out of scope. This is the right point to release any system resources (audio) that were allocated in the constructor.

Back to top of page ...


The `Graphics´ interface

The Graphics interface is a bit more complex than the Audio, but I should also be quite easy to implement on any PalmOS™ environment:

public interface Graphics {
:
  // get screen dimension
  int getWidth ();
  int getHeight ();
	
  // clear screen.
  void clearScreen ();

  // set/reset a clipping region for drawing.
  void setDrawRegion (int x, int p, int w, int h);
  void resetDrawRegion ();

  // primitive draw methods.
  void drawBorder (int x, int y, int w, int h, int mode, int width, int round);
  void drawRectangle (int x, int y, int w, int h, int mode, int round);
  void drawBitmap (int x, int y, Bitmap icon);
  void drawLine (int x, int y, int xx, int yy, int mode);

  // process labels
  int getStringWidth (String s);
  int getStringHeight (String s);
  int drawString (String text, int x, int y, int mode);
  int drawString (String text, int x, int y);

  // blitter methods.
  void bitBlt (int sx, int sy, int w, int h, int tx, int ty, int mode, int from, int to);
  void bitBlt (int sx, int sy, int w, int h, int tx, int ty, int mode);
	
  // dispose object
  void dispose ();
}

The Graphics object represents the 'canvas' where your application can draw bitmaps and primitives (like rectangles, lines, etc.), clear the screen, setting clipping areas and move raster areas around on the screen.

A 'hidden' screen of the same size as the 'visible' screen is available for double-buffering or screen backup purposes.

The coordinate system (x- and y-coordinates) has its origin (0,0) in the upper left corner of the screen. Positive x-coordinates run to the right and positive y-coordinates to the bottom.

Currenly there is no support for color or fonts in DynaWorks. I hope this will change in one of the next versions.

Since the Graphics interface is "derived" from the normal set of graphics routines in the PalmOS™, you find a lot more information about the single methods in the PalmOS™ documentation.

The following constants are defined by the interface:

<<Constants>>
NameValue NameValue
Border modes BitBlt operation modes
SIMPLE0 OVERWRITE6
RAISED1 AND7
Draw styles AND_NOT8
PLAIN2 XOR9
GRAY3 OR10
ERASE4 NOT11
INVERT5 double-buffering constants
BUFFER13
SCREEN12
The following methods must be implemented:
int getWidth()
int getHeight()
Return the size of the screen in pixel. Although nothing limits this pixel range, it is still common to return 160 pixel in each direction as screen resolution. Whenever Palm makes PalmOS™ work with a different screen size and we have a layout manager in DynaWorks, these methods make much more sense - but they are here already.
void clearScreen()
void setDrawRegion (int x, int p, int w, int h)
void resetDrawRegion()
clearScreen clears the complete canvas to the background color (that is white).

setDrawRegion(...) defines a clipping area on the screen at the defined position and dimension. Any draw operation that follows only leaves visible 'marks' within the defined clipping area.

resetDrawRegion() removes any clipping area. Please make sure that you nest calls to setDrawRegion() and resetDrawRegion() correctly, since not all implementations may have a stack of clipping areas.

void drawBorder (int x, int y, int w, int h, int mode, int width, int round)
void drawRectangle (int x, int y, int w, int h, int mode, int round)
void drawBitmap (int x, int y, Bitmap icon)
void drawLine (int x, int y, int xx, int yy, int mode);
These are the basic methods for the graphical primitives Border (a filled rectangular area with rounded corners and shadow), Rectangle (a outline of a rectangular area with rounded corners), Bitmap (a bloack/white raster image) and Line (a line between two points in different thickness and color).

The drawing mode can be set to different value from the constant table above.

int getStringWidth (String s)
int getStringHeight (String s)
These methods return the length and height (in pixels) of the bounding box of a string on the canvas. These methods are used by the application to determine the size on the screen before painting a string.

Note: Since there are no fonts in DynaWorks yet, the PalmOS™ built-in font is used!

int drawString (String text, int x, int y, int mode)
int drawString (String text, int x, int y)
These methods are used to print text strings on the screen. Texts are not wrapped at the end of the screen, so it is up to the application to ensure texts fit on the screen.

The mode argument can be one of the draw style values (PLAIN, GRAY, ERASE, INVERT). The second method call has PLAIN preset as a mode argument.

void bitBlt (int sx, int sy, int w, int h, int tx, int ty, int mode)
void bitBlt (int sx, int sy, ... , int ty, int mode, int from, int to)
These methods allow to copy an area of the screen (or of the background buffer) to another area of the same size on the screen (or in the buffer). The mode of the operation determines how the source and the target pixel behave.

The first method is used to bitblt areas on the screen; the second method allows to specify a source ('from' = SCREEN|BUFFER) and a target ('to' = SCREEN|BUFFER), so you can bitblt between screen and background buffer.

The pixel in each area is represented as by a bit; if it is 0 you see the background (white); if it is 1, the pixel is drawn as a black dot. So any 'boolean' operation between pixels can be expressed as bit logic. The bitblt mode decides, which boolean operation should be performed during the operation:

ModeDescription
OVERWRITECopy the source pixels over destination
ANDCopy source only where target is black
AND_NOTInverted AND
XORToggle pixels in source and destination
ORMerge source and destination
NOTCopy inverted source

void dispose ()
Called by the framework when the implementation goes out of scope. This is the right point to release any system resources (graphic) that were allocated in the constructor.

Back to top of page ...


The `Database´ interface

The Database interface encapsulates the concept of a persistent data store. Since DynaWorks is designed to run on PalmOS™ devices, the basic funnctionality is 'compatible' with PalmOS™ databases.

Generally spoken a Database is just a named container for variable length byte fields that can be addressed by their sequence number (record number). So implementing the functionality for databases is quite easy, even if you J2ME implementation has a different database API.

In an implementing class, you have to construct an Database according to the environment you are working in. Since instances of this interface are always created by an Implementation object, the internal creation of Database object is hidden to DynaWorks and the application developer.

The class factory method defined in the Implementation interface has a string and two integer values as arguments. The meaning of these 'identifier' arguments is fixed in DynaWorks to allow compatibility across different J2ME implementations. The format also defines access to the built-in databases on the Palm. You can find more information in the description of the Implentation interface.

public interface Database {
:
  // return the current database error code.
  int getErrorCode ();

  // Open the database. If the database does not exist, it
  // is created and then opened.
  boolean open ();
    
  // Is the database opened?
  boolean isOpen ();
    
  // Close the database.
  void close ();

  // Return the number of records in the database.
  int numberOfRecords ();

  // add a record to the database.
  int addRecord (Dataset rec);

  // Retreive a record from the database at a specific position.
  boolean getRecord (int idx, Dataset rec);

  // Store a record in the database at a specific position.
  boolean putRecord (int idx, Dataset rec);

  // Delete a record at a specified index.
  boolean deleteRecord (int idx);   
}

The following error codes are defined by the interface:

<<Error Codes>>
NameValue NameValue
S_OK0 ERR_WRITE5
ERR_CREATE1 ERR_DELETE6
ERR_OPEN2 ERR_NODATA7
ERR_NODB3 ERR_READ8
ERR_APPEND4 ERR_DBFULL9
ERR_DEVICE10

The following methods have to be implemented:

int getErrorCode ()
Return the current error code from the database. The return value can be any of the defined error codes from the table above.
boolean open ()
Open the database object: If the database does not exist, it is created and then opened.

All information required to address and to open the database were already passed to the object in the constructor call.

void close ()
Close the database. In an implementing class this is a good point to free any resources allocated during an open() call.
int numberOfRecords ()
Return the number of records in a database. In case the database can not be accessed or there is an error of any kind, the method returns the value -1; otherwise the number of (undeleted) records is returned.

Please note: The KVM implementation of the PalmOS™ database is somewhat strange: It returns the overall number of records in the database, including deleted entries (that always show up at the end of the database). More...

int addRecord (Dataset rec)
This method adds a new record into the database. The argument passed in referes to an implementing class of the interface Dataset (a generic byte field container). This interface is implemented by the Record class in the package brf.j2me.dynaworks.db.

public interface Dataset {

  // Get the 'byte' length of the record.
  int size ();
    
  // Get the byte array representing the record content.
  byte[] getContent ();

  // update the record with new data.
  boolean setContent (byte[] data);
}

The method returns the sequence index of the new record in the database.

Please note: The KVM implementation of the PalmOS™ database does not allow access to the unique index of a record. Therefore all record indexing is based on the current sequence of records in the database (and that may change due to deletion operations). This is likely to change in next versions of DynaWorks. More...

boolean getRecord (int idx, Dataset rec)
Read a record from the database at a given sequence index. The record is passed in as a Dataset instance.

Please note: The KVM implementation of the PalmOS™ database does not allow access to the unique index of a record. Therefore all record indexing is based on the current sequence of records in the database (and that may change due to deletion operations). This is likely to change in next versions of DynaWorks. More...

boolean putRecord (int idx, Dataset rec)
Write a record to the database at a given sequence index. The record is passed in as a Dataset instance.

Please note: The KVM implementation of the PalmOS™ database does not allow access to the unique index of a record. Therefore all record indexing is based on the current sequence of records in the database (and that may change due to deletion operations). This is likely to change in next versions of DynaWorks. More...

boolean deleteRecord (int idx)
Delete a record from the database at a given sequence index.

The behaviour of the method varies among different J2ME implementions. This is vary unsatisfying, so the next release of DynaWorks will have a overhaul of the Database interface...

Please note: The KVM implementation of the PalmOS™ database does not allow access to the unique index of a record. Therefore all record indexing is based on the current sequence of records in the database (and that may change due to deletion operations). This is likely to change in next versions of DynaWorks. More...

Back to top of page ...


The `Implementation´ interface

The Implementation is the 'glue' interface between the Environment (application-side) and the graphics, audio and database objects that are requested by the application.

public interface Implementation {

  // register an EventQueue. The Implementation is going
  // to dispatch all events to the registered EventQueue instance.
  // Returns previous EventQueue (or null).
  public EventQueue register (EventQueue queue);

  // get access to the Graphics implementation.
  // An Graphics object is a singleton.
  public Graphics getGraphics ();

  // get access to the Audio implementation.
  // An Audio object is a singleton.
  public Audio getAudio ();

  // get a database object identified by a string.
  public Database getDatabase (String id);
}

The following methods have to be implemented:

EventQueue register (EventQueue queue)
This method registers the argument EventQueue with the PalmOS™ event loop. All events are now passed to this event queue until another event queue registers with the Environment.

The method returns the event queue that was registered before (or 'null' in case there was no such queue) to the caller.

Graphics getGraphics ()
This method returns an access object for drawing operations. This object is a singleton (e.g. there is only one instance of the implementing class in the framework).
Audio getAudio()
This method returns an access object for audio playback. This object is a singleton (e.g. there is only one instance of the implementing class in the framework).
Database getDatabase (String name)
The string argument to the constructor contains all information neccessary to access a database on the PalmOS™ platform. It must confirm to the following notation: The string consists of three parts that are each delimited by a colon character (:). The single parts are:

  1. Creator Id
    The first four characters form the 'Creator ID'. This is a unique id for each application you write and deploy. You should register each such application with Palm.

    A 'Creator Id' is a four character string that can be converted to an int via 'int Environment.decodeIdString (String s)' - whenever you need that number.

  2. Type Id
    The next four characters after the delimiter designate the database type. A database type is something like 'DATA', 'RSRC' or the like. It can also be converted to an int via 'int Environment.decodeIdString (String s)'.

  3. Database name
    All the rest of the characters after the delimiter form the database name. It can be 32 characters long and can be set by the application.

    The database names for PalmOS™ built-in databases are described in the PalmOS™ database section of the tutorial.

A note about 'Creator Id' and 'Type Id': You only have understand the whole concept behind these Ids if you plan to port DynaWorks to a non-PalmOS™ platform - in this case you have to fully reproduce the internal behaviour of PalmOS™ databases. If you work on a PalmOS™ implementation, you simply pass in these arguments to the internal methods.

Back to top of page ...


Copyright © 2000, Bernd R. Fix. All Rights Reserved.