Data base in MIDP, part 1: the concept of Record Management System

a key component is the MIDP Record Management System (RMS). This API provides the ability to store data locally in the device memory. For most MIDP-capable phones is the only way to store data — only a small number of devices support access to the regular file system. Easy to guess that a full understanding of the mechanism, RMS need to write any application requiring the storage of local data.

This is the first article in the series, which will highlight the most common problems regarding the use of RMS applications, such as interaction with external data sources such as relational databases. First we find out what we can offer RMS, and write some simple debuggers.

Key concepts



From the title it is clear that RMS is a system for records management. The entry (record) is an element of data. RMS does not impose any restrictions on the contents of a record, it can contain number, string, array, image — anything that can be thought of as a sequence of bytes. If you can encode the data in binary format (and decode back), then you can save them in the record, of course, if they are within the size limit imposed by the system.

Many newcomers to RMS baffling concept record. They ask: "where is the field?", wondering how the system divides the individual records into a separate data sequence. The answer is simple: the entry of RMS does not contain any fields. Rather, the record contains one binary field is of arbitrary length. A function of interpreting the contents of a record rests with the application. RMS provides the storage and a unique identifier, nothing more. This creates difficulties for application, but retains the RMS is simple and flexible, which is quite important for the subsystem MIDP.

On the API level, records are simply byte arrays.

Storage record

Store (record store) is an ordered collection of records. Each record belongs to a repository and is only available through him. Store guarantees atomic read and write data, preventing their damage.

When an entry is created, the repository assigns a unique integer identifier (record ID). The first record gets id 1, second 2, etc. Is not an index: when a record is deleted the remaining items are not renumbered.

Name (name) is used to identify the repository within the MIDlet. The name can contain from 1 to 32 unicode characters and must be unique within the MIDlet that created the vault. In MIDP 1.0 the repository cannot be used more than one application. MIDP 2.0 optionally allows to do it, in this case, the storage identificireba not only the name but also the name and manufacturer of the application that created this vault.

In addition, the repository contains information about the last modified date and version. Applications can also bind to the storage event handler to change the data in it.

At the API level storage represented by an instance of the class javax.microedition.rms.RecordStore. All classes and interfaces defined in the RMS package the javax.microedition.rms.

aspects of the RMS


Restrictions on the size of the data

The amount of memory available for the storage of records is different on different devices. The MIDP specification requires reservations at least 8 KB of memory for persistent data storage. The size of each entry is not limited. RMS provides methods for determining the size of the record size, total storage size and free memory. Remember that permanent memory is shared for all applications, use it sparingly.

Any MIDlet using RMS must specify a minimum storage size, in bytes, required for his work. To do this, in the manifest of the jar file and jad file should be installed attribute MIDlet-Data-Size. Do not specify too much value — some devices may prevent installation of the application, if free space is insufficient. In practice, most devices allow applications to go beyond the starting size of the data.
Note that some implementations of MIDP require additional attributes related to the required memory size. This should be stated in the documentation for the device.

Speed

Operation with constant memory is usually made slower than RAM. In particular, on some platforms the data record can take a long time. To increase performance, use caching of frequently used data in memory. To not slow down the responsiveness of the user interface, do not perform operations with RMS thread event handling of MIDlet.

Security of using threads

RMS operations are thread-safe. However, the threads must coordinate among themselves, as with any shared resource. This applies simultaneously running MIDlets that use the same repository.

Exceptions

In General, methods in the RMS API throw a few exceptions (in addition to the standard exceptions like java.lang.IllegalArgumentException). These exceptions are defined in the package javax.microedition.rms:
the
    the
  • InvalidRecordIDException — the operation could not be performed because the transmitted a wrong record id.
  • the
  • RecordStoreFullException — has run out of available memory.
  • the
  • RecordStoreNotFoundException — the specified bucket does not exist.
  • the
  • RecordStoreNotOpenException is the app is trying to use the repository that was closed.
  • the
  • RecordStoreException is the superclass of the previous exceptions are also thrown when a General error not covered by them.

Note that for brevity we neglect exception handling in some instances (due to its simplicity).

Using RMS


The rest of the article we will devote the main operations with records using RMS API. Some of them are presented in the class RMSAnalyzer designed for analyzing repositories. You can use it as a debugging tool in your projects.

Search for repositories

The list of repositories can be obtained by using the RecordStore.listRecordStores(). This static method returns an array of strings, each of which is a name owned by the MIDlet storage. If you have not created any repositories, null is returned.

Method RMSAnalyzer.analyzeAll() uses listRecordStores () to call analyze() for each store:
the
public void analyzeAll() {
String[] names = RecordStore.listRecordStores();

for( int i = 0;
names != null && i < names.length;
i++) {
analyze( names[i] );
}
}

Note that the array contains the names of the repositories created by our MIDlet. The MIDP specification does not provide any way to get a list of all the stores of other MIDlets. MIDP 1.0 does not give access to other people's repositories. In MIDP 2.0, the application may mark the storage as shared (shareable), but other MIDlets can use it only if they know his name.

Opening and closing the store

RecordStore.openRecordStore() is used to open (and sometimes create) storage. This static method returns an instance of a RecordStore object, as can be seen from this version RMSAnalyzer.analyze():
the
public void analyze( String rsName ) {
RecordStore rs = null;

try {
rs = RecordStore.openRecordStore( rsName, false );
analyze( rs ); // overloaded method
} catch( RecordStoreException e ) {
logger.exception( rsName, e );
} finally {
try {
rs.closeRecordStore();
} catch( RecordStoreException e ){
// ignore this exception
}
}
}

The second parameter of the method openRecordStore() indicates whether the created repository if it does not exist. In MIDP 2.0 to open the store, created by another application that used a form of openRecordStore():
the
...
String name = "mySharedRS";
String vendor = "EricGiguere.com";
String suite = "TestSuite";
RecordStore rs = RecordStore.openRecordStore( name, vendor, suite );
...

Manufacturer and name of the MIDlet must match that specified in the manifest.

After finishing work, close the store a call to RecordStore.closeRecordStore () as in the above method analyze().

The unique instance of the RecordStore in MIDlet: after it opened, all subsequent calls to openRecordStore() with the same arguments will return a reference to the same object. This instance is common to all MIDlets in the collection.
Each instance of the RecordStore counts the number of times it was opened the vault. It will not be closed until closeRecordStore() is called on the same number of times. After closure of the repository attempt to use it will result in an exception RecordStoreNotOpenException.

Create storage

To create a storage (not accessible to other MIDlets), you need to call the openRecordStore(), setting the second parameter to true:
the
...
// create storage
RecordStore rs = null;

try {
rs = RecordStore.openRecordStore( "myrs", true );
} catch( RecordStoreException e ){
// failed to create or open a repository
}
...

To accomplish the initial provisioning of storage, check the value of the getNextRecordID() — if it is 1 in the vault no entries:
the
if( rs.getNextRecordID() == 1 ){
// initial initialization
}

Alternative method — checking the value returned by getNumRecords():
the
if( rs.getNumRecords() == 0 ){
// the storage is empty, reinitialize
}

To create a public repository (in MIDP 2.0 only), use the following call openRecordStore() with four parameters:
the
boolean writable = true;

rs = RecordStore.openRecordStore( "myrs", true, 
RecordStore.AUTHMODE_ANY, writable );

If the second parameter is true and the repository does not exist, the last two parameters control its access mode, and the ability to record. The access mode determines whether other applications use this repository. You have two options: RecordStore.AUTHMODE_PRIVATE (access only has our application) or RecordStore.AUTHMODE_ANY (access has any application). The writable flag determines whether the other application can have write access if writable=false, it will be able only to read data.

App-the owner of the store can change these settings at any time using the RecordStore.setMode():
the
rs.setMode( RecordStore.AUTHMODE_ANY, false );

In fact, it is best to create the repository private, and access is only open after it has been initialized.

Adding and editing records

Recall that records are byte arrays. To add a new record to the open storage method is used RecordStore.addRecord():
the
...
byte[] data = new byte[]{ 0, 1, 2, 3 };
int recordID;

recordID = rs.addRecord( data, 0, data.length );
...

You can create an empty record, passing as the first parameter is null. The second and third parameters specify the starting position and the number of bytes you want to save. If successful, returns the record id, otherwise an exception is raised (for example, RecordStoreFullException).

At any time you can update the record using RecordStore.setRecord():
the
...
int recordID = ...; // ID of a record
byte[] data = new byte[] { 0, 10, 20, 30 };

rs.setRecord( recordID, data, 1, 2 ); 
// replace all data in the record 10, 20
...

To know what id will be assigned to the next added record, using the method of RecordStore.getNextRecordID(). All existing records have id's less than this.

In second part we look at ways of converting objects and other data into a byte array.

Read entries

Reading records are used RecordStore.getRecord() in one of two forms. In the first embodiment, this method creates an array of desired length and writes the data:
the
...
int recordID = .... // ID of a record
byte[] data = rs.getRecord( recordID );
...

In the second option the data is copied to already created array, starting at the specified position, it returns the number of bytes written:
the
...
int recordID = ...; // the record ID
byte[] data = ...; // array
int offset = ...; // starting position

int numCopied = rs.getRecord( recordID, data, offset );
...

The array should have sufficient length to accommodate the data, otherwise there will be java.lang.An ArrayIndexOutOfBoundsException. To determine the required size of the array is used RecordStore.getRecordSize(). In fact, the first form of getRecord() is equivalent to the following:
the
...
byte[] data = new byte[ rs.getRecordSize( recordID ) ];
rs.getRecord( recordID, data, 0 );
...

The second form is useful if you iterate through the many records in a loop, because it reduces the number of memory requests. For example, you can use it with getNextRecordID() and getRecordSize() to search through all of the records in the repository:
int nextID = rs.getNextRecordID(); byte[] data = null; for( int id = 0; id < nextID; ++id ) { try { int size = rs.getRecordSize( id ); if( data == null || data.length < size ) { data = new byte[ size ]; } rs.getRecord( id, data, 0 ); processRecord( rs, id, data, size ); // do something with the found record } catch( InvalidRecordIDException e ){ // ignore and move on to the next record } catch( RecordStoreException e ){ handleError( rs, id, e ); // error handling } } ...
However, it is better to use a RecordStore.enumerateRecords(). We will consider this method in the third part series.

Destruction of records and repositories

To delete records the intended function of the RecordStore.deleteRecord():
the
...
int recordID = ...; 
rs.deleteRecord( recordID );
...

After deleting a record, any attempt to use it leads to a InvalidRecordIDException.

You can remove all of the entire storage by using the RecordStore.deleteRecordStore():
the
...
try {
RecordStore.deleteRecordStore( "myrs" );
} catch( RecordStoreNotFoundException e ){
// no such repository
} catch( RecordStoreException e ){
// the store is open
}
...

The repository cannot be deleted while it is open in any application. Delete a vault only the MIDlet that created it.

Other operation

There are only a few operations with RMS, they are methods of the RecordStore class:
the
    the
  • getLastModified() returns the last modification time of the repository in the same format as System.currentTimeMillis().
  • the
  • getName() returns the name of the repository.
  • the
  • getNumRecords() returns the number of records in the repository.
  • the
  • getSize() returns the total storage size, in bytes, including the length of records, and service fields that the system requires for its organization.
  • the
  • getSizeAvailable() returns the amount of free space in bytes. Actual available size may be smaller due to the additional bytes used by the store for storage of each record.
  • the
  • getVersion() returns the version number of the repository. Is a positive integer, incrementing by one each time the data changes.

A MIDlet can also track changes to the repository, registering the handler using the addRecordListener(), to remove it is designed removeRecordListener(). In the third part of these methods will be discussed in more detail.

Class RMSAnalyzer


Finish this article source RMSAnalyzer class, our analyzer repositories. For analysis you need to perform the following code:
the
...
RecordStore rs = ...; // open storage
RMSAnalyzer analyzer = new RMSAnalyzer();
analyzer.analyze( rs );
...

By default, the output is redirected to System.out and looks like this:
=========================================
Record store: recordstore2
Number of records = 4
Total size = 304
Version = 4
Last modified = 1070745507485
Size available = 975950,

Record #1 of length 56 bytes
06 5f 62 75 2e 6b 1c 42 58 3f _b.u.k.BX?
1e 2e 6a 7c 24 74 29 56 30 32 ..j$t)|V02
5f 67 13 47 5a 77 7a 7d 68 49 _gZ.Gzwh}I
50 74 50 20 14 60 78 6b 58 4b PtP k.x XK
1a 61 67 65 20 53 0a 2f 2b 23 .ag Se./#+
16 42 10 4e 37 6f .B.N7o
Record #2 length of 35 bytes
22 4b 19 22 15 7d 74 1f 65 26 "K.".}t.e&
4e 1e 50 50 6e 4f 62 6a 47 26 N. PbPnOGj&
31 11 74 36 33 0a 7a 0e 51 61 1.t6z.3Qa.
04 75 6a 2a 2a .uj**
Record #3 of length 5 bytes
47 04 43 22 1f G. C".
Record #4 of length 57 bytes
6b 6f 42 1d 5b 65 2f 72 0f 7a koB.[e/r.z
2a 6e 07 57 51 71 5f 68 4c 5c *n.WQq_hL\
1a 2a 44 02 7b 7d 19 73 4f 0b .*D{.}.sO.
75 03 34 58 17 19 5e 6a 5e 80 u.4X..^j^?
39 28 2a 5c 4a 4d 4e 21 57 75 *9(\JN!WMu
80 68 77 06 26 3b 33 ?h.&;w3

Actual size of records = 153
-----------------------------------------

This format is useful when testing with the J2ME Wireless Toolkit. When testing on a real device you can send the output of the analyzer to the serial port or even across the network. You need to create a new class with the interface RMSAnalyzer.Logger and pass its instance to the constructor RMSAnalyzer.

Finally, this article project J2ME Wireless Toolkit called RMSAnalyzerTest showing the use of the analyzer: pastebin.com/n36QLuAs

the Rest of the article in English can be seen here. Does it make sense to continue translating them?
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Briefly on how to make your Qt geoservice plugin

Database replication PostgreSQL-based SymmetricDS

Yandex.Widget + adjustIFrameHeight + MooTools