j2me - How to store custom objects using RMS - java-me

In the applications I'm developing I need to store data for Customer,Products and their Prices.
In order to persist that data I use RMS, but knowing that RMS doesn't support object serializing directly and since that data I read already comes in json format, I store every JSONObject as its string version, like this:
rs = RecordStore.openRecordStore(mRecordStoreName, true);
JSONArray jsArray = new JSONArray(data);
for (int i = 0; i < jsArray.length(); i++) {
JSONObject jsObj = jsArray.getJSONObject(i);
stringJSON = jsObj.toString();
addRecord(stringJSON, rs);
}
The addRecord Method
public int addRecord(String stringJSON, RecordStore rs) throws JSONException,RecordStoreException {
int id = -1;
byte[] raw = stringJSON.getBytes();
id= rs.addRecord(raw, 0, raw.length);
return id;
}
So I have three RecordStores (Customer,Products and their Prices) and for each of them I do the save as shown above to save their corresponding data.
I know this might be a possible to solution, but I'm sure there's gotta be a better implementation. Even more,considering that over those three "tables" I'm going to perform searching, sorting,etc.
In those cases, having to deserialize before proceeding to search or sort doesn't seem a very good idea.
That's why I want to ask you guys. In your experience, how do store custom objects in RMS in way that is easy to work with them later??
I really appreciate all your comments and suggestions.
EDIT
It seems that it's easier to work with records when you define a fixed max length for each field. So here's what I tried:
1) First all, this is the class I use to retrieve the values from the record store:
public class Customer {
public int idCust;
public String name;
public String IDNumber;
public String address;
}
2) This is the code I use to save every jsonObject to the record store:
RecordStore rs = null;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
JSONArray js = new JSONArray(data);
for (int i = 0; i < js.length(); i++) {
JSONObject jsObj = js.getJSONObject(i);
byte[] record = packRecord(jsObj);
rs.addRecord(record, 0, record.length);
}
} finally {
if (rs != null) {
rs.closeRecordStore();
}
}
The packRecord method :
private byte[] packRecord(JSONObject jsonObj) throws IOException, JSONException {
ByteArrayOutputStream raw = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(raw);
out.writeInt(jsonObj.getInt("idCust"));
out.writeUTF(jsonObj.getString("name"));
out.writeUTF(jsonObj.getString("IDNumber"));
out.writeUTF(jsonObj.getString("address"));
return raw.toByteArray();
}
3) This is how I pull all the records from the record store :
RecordStore rs = null;
RecordEnumeration re = null;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement()) {
Customer c;
int idRecord = re.nextRecordId();
byte[] record = rs.getRecord(idRecord);
c = parseRecord(record);
//Do something with the parsed object (Customer)
}
} finally {
if (re != null) {
re.destroy();
}
if (rs != null) {
rs.closeRecordStore();
}
}
The parseRecord Method :
private Customer parseRecord(byte[] record) throws IOException {
Customer cust = new Customer();
ByteArrayInputStream raw = new ByteArrayInputStream(record);
DataInputStream in = new DataInputStream(raw);
cust.idCust = in.readInt();
cust.name = in.readUTF();
cust.IDNumber = in.readUTF();
cust.address = in.readUTF();
return cust;
}
This is how I implemented what Mister Smith suggested(hope it's what he had in mind). However, I'm still not very sure about how to implement the searchs.
I almost forget to mention that before I made theses changes to my code, the size of my RecordStore was 229048 bytes, now it is only 158872 bytes :)

RMS is nothing of the sort of a database. You have to think of it as a record set, where each record is a byte array.
Because of this, it is easier to work with it when you define a fixed max length for each field in the record. For instance, a record could be some info about a player in a game (max level reached, score, player name, etc). You could define the level field as 4 bytes long (int), then a score field of 8 bytes (a long), then the name as a 100 bytes field (string). This is tricky because strings usually will be of variable length, but you would probably like to have a fixed max length for this field, and if some string is shorter than that, you'd use a string terminator char to delimite it. (This example is actually bad because the string is the last field, so it would have been easier to keep it variable length. Just imagine you have several consecutive fields of type string.)
To help you with serialization/deserialization, you can use DataOutputstream and DataInputStream. With these classes you can read/write strings in UTF and they will insert the string delimiters for you. But this means that when you need a field, as you don't know exactly where it is located, you'll have to read the array up to that position first.
The advantage of fixed lengths is that you could later use a RecordFilter and if you wanted to retrieve recors of players that have reached a score greater than 10000, you can look at the "points" field in exactly the same position (an offset of 4 bytes from the start of the byte array).
So it's a tradeoff. Fixed lengths means faster access to fields (faster searches), but potential waste of space. Variable lengths means minimum storage space but slower searches. What is best for your case will depend on the number of records and the kind of searches you need.
You have a good collection of tutorials in the net. Just to name a few:
http://developer.samsung.com/java/technical-docs/Java-ME-Record-Management-System
http://developer.nokia.com/community/wiki/Persistent_Data_in_Java_ME

Related

Differences between new String and Bytes.toString

I am using hbase utility org.apache.hadoop.hbase.util.Bytes
I generated a an array of Bytes from a string (in a example in Scala):
val bytes = Bytes.toBytes("test")
and want to convert back in String.
What is the difference between new String(bytes,"UTF-8") and Bytes.toString(bytes)
They both works.
At a guess that you are talking about https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/util/Bytes.html, basically nothing: Bytes.toString will call new String, except if the array is empty. You can see the method called here:
public static String toString(final byte [] b, int off, int len) {
if (b == null) {
return null;
}
if (len == 0) {
return "";
}
return new String(b, off, len, UTF8_CHARSET);
}
For the future, please mention any libraries you are using in the question (and the question is completely unrelated to Scala).

j2me - Does a List have any property to keep track of 'key' that identify the item

How do you usually deal with Lists and the fact that they don't have a property to clearly identity an specific item ?
So far, the only solucion I could come up with is to put the key I use at the beginning, followed by a hyphen and the text that is shown on every item.
This way when I retrieve the text from the selected item I can get the key for the item.
This is how I do it, but surely there's gotta be a better solution and I'd really like that you could share your experience in this kind of scenarios.
Thanks in advance.
The picture ooks like you keep all the data managed in your application inside the text of the items of a standard list.
Better hava a separate class for the data container objects and an overview screen derived from List that takes an array of those container objects and instantiate the Items from that. This screen could then provide a method
DataContainer getSelectedObject()
which uses getSelectedIndex() internally to look up the object.
More specifically (Overview.java)
package mvc.midlet;
import javax.microedition.lcdui.List;
public class Overview extends List {
private final DomainObject[] data;
public static Overview create(DomainObject[] data) {
int i = 0;
for(; i < data.length; i++) {
if(data[i] == null) break;
}
String[] names = new String[i];
for(int j = 0; j < i; j++) {
names[j] = data[j].name;
}
return new Overview(names, data);
}
protected Overview(String names[], DomainObject[] data) {
super("Overview", IMPLICIT, names, null);
this.data = data;
}
public DomainObject getSelectedObject() {
return data[this.getSelectedIndex()];
}
}

In J2ME, How to re-index records in recordstore after deleting any record

I am developing a Location-based J2ME app & in that I'm using RMS to store data.
In RecordStore when I delete any record, the underlying records doesn't get re-indexed. For example, if I have 5 records & I delete record no.2 then record ids will be {1, 3, 4, 5}. But I want record ids after deletion to be {1, 2, 3, 4}. How should I do this??? Because recordId is playing an important role in my app to retrieve & update the record.
You need to change your application logic. ID is just for identification, and not for sorting. Because it is for identification, it must remains the same.
Very often the easiest thing to do is to read and write the whole recordstore at once.
So, since you've said that your record store is basically small (not that much data), I would recommend simply adding your own custom id field to each record. As Meier said, the RMS record id is not really meant to be recalculated, and changed, once a record has been created. So, I would use your own.
If each of your records contain:
boolean isMale
int age
String firstName
then, I would simply add another field at the start of each record:
int id
It makes your records a little bigger, but not much (4 bytes/record). If you'll have less than 64k records, then you could also use a short for the id, and save a couple bytes.
Here's an example (adapted from this IBM tutorial), of reading, writing, and deleting with this kind of record:
private RecordStore _rs;
// these next two methods are just small optimizations, to allow reading and
// updating the ID field in a record without the overhead of creating a new
// stream to call readInt() on. this assumes the id is a 4 byte int, written
// as the first field in each record.
/** Update one record with a new id field */
private static final void putIdIntoRecord(int id, byte[] record) {
// we assume the first 4 bytes are the id (int)
record[0] = (byte)(id >> 24);
record[1] = (byte)(id >> 16);
record[2] = (byte)(id >> 8);
record[3] = (byte)id;
}
/** Get the id field from one record */
private static final int getIdFromRecord(byte[] record) {
// we assume the first 4 bytes are the id (int)
return ((0xFF & record[0]) << 24) |
((0xFF & record[1]) << 16) |
((0xFF & record[2]) << 8) |
(0xFF & record[3]);
}
/** delete a record with the given (custom) id, re-indexing records afterwards */
private void delete(int idToDelete) {
try {
RecordEnumeration enumerator = _rs.enumerateRecords(new IdEqualToFilter(idToDelete),
null, false);
_rs.deleteRecord(enumerator.nextRecordId());
// now, re-index records after 'idToDelete'
enumerator = _rs.enumerateRecords(new IdGreaterThanFilter(idToDelete), null, true);
while (enumerator.hasNextElement()) {
int recordIdToUpdate = enumerator.nextRecordId();
byte[] record = _rs.getRecord(recordIdToUpdate);
// decrement the id by 1
int newId = getIdFromRecord(record) - 1;
// copy the new id back into the record
putIdIntoRecord(newId, record);
// update the record, which now has a lower id, in the store
_rs.setRecord(recordIdToUpdate, record, 0, record.length);
}
} catch (RecordStoreNotOpenException e) {
e.printStackTrace();
} catch (InvalidRecordIDException e) {
e.printStackTrace();
} catch (RecordStoreException e) {
e.printStackTrace();
}
}
/** generate some record store data ... example of writing to store */
public void writeTestData()
{
// just put 20 random records into the record store
boolean[] booleans = new boolean[20];
int[] integers = new int[20];
String[] strings = new String[20];
for (int i = 0; i < 20; i++) {
booleans[i] = (i % 2 == 1);
integers[i] = i * 2;
strings[i] = "string-" + i;
}
writeRecords(booleans, integers, strings);
}
/** take the supplied arrays of data, and save a record for each array index */
public void writeRecords(boolean[] bData, int[] iData, String[] sData)
{
try
{
// Write data into an internal byte array
ByteArrayOutputStream strmBytes = new ByteArrayOutputStream();
// Write Java data types into the above byte array
DataOutputStream strmDataType = new DataOutputStream(strmBytes);
byte[] record;
for (int i = 0; i < sData.length; i++)
{
// Write Java data types
strmDataType.writeInt(i); // this will be the ID field!
strmDataType.writeBoolean(bData[i]);
strmDataType.writeInt(iData[i]);
strmDataType.writeUTF(sData[i]);
// Clear any buffered data
strmDataType.flush();
// Get stream data into byte array and write record
record = strmBytes.toByteArray();
_rs.addRecord(record, 0, record.length);
// Toss any data in the internal array so writes
// starts at beginning (of the internal array)
strmBytes.reset();
}
strmBytes.close();
strmDataType.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
/** read in all the records, and print them out */
public void readRecords()
{
try
{
RecordEnumeration re = _rs.enumerateRecords(null, null, false);
while (re.hasNextElement())
{
// Get next record
byte[] recData = re.nextRecord();
// Read from the specified byte array
ByteArrayInputStream strmBytes = new ByteArrayInputStream(recData);
// Read Java data types from the above byte array
DataInputStream strmDataType = new DataInputStream(strmBytes);
// Read back the data types
System.out.println("Record ID=" + strmDataType.readInt());
System.out.println("Boolean: " + strmDataType.readBoolean());
System.out.println("Integer: " + strmDataType.readInt());
System.out.println("String: " + strmDataType.readUTF());
System.out.println("--------------------");
strmBytes.close();
strmDataType.close();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
Here, I make use of a couple small RecordFilter classes, to use when searching the record store:
/** helps filter out records greater than a certain id */
private class IdGreaterThanFilter implements RecordFilter {
private int _minimumId;
public IdGreaterThanFilter(int value) {
_minimumId = value;
}
public boolean matches(byte[] candidate) {
// return true if candidate record's id is greater than minimum value
return (getIdFromRecord(candidate) > _minimumId);
}
}
/** helps filter out records by id field (not "recordId"!) */
private class IdEqualToFilter implements RecordFilter {
private int _id;
public IdEqualToFilter(int value) {
_id = value;
}
public boolean matches(byte[] candidate) {
// return true if candidate record's id matches
return (getIdFromRecord(candidate) == _id);
}
}

Lines of a StreamReader to an array of string

I want to get a string[] assigned with a StreamReader. Like:
try{
StreamReader sr = new StreamReader("a.txt");
do{
str[i] = sr.ReadLine();
i++;
}while(i < 78);
}
catch (Exception ex){
MessageBox.Show(ex.ToString());
}
I can do it but can't use the string[]. I want to do this:
MessageBox.Show(str[4]);
If you need further information feel free to ask, I will update.
thanks in advance...
If you really want a string array, I would approach this slightly differently. Assuming you have no idea how many lines are going to be in your file (I'm ignoring your hard-coded value of 78 lines), you can't create a string[] of the correct size up front.
Instead, you could start with a collection of strings:
var list = new List<string>();
Change your loop to:
using (var sr = new StreamReader("a.txt"))
{
string line;
while ((line = sr.ReadLine()) != null)
{
list.Add(line);
}
}
And then ask for a string array from your list:
string[] result = list.ToArray();
Update
Inspired by Cuong's answer, you can definitely shorten this up. I had forgotten about this gem on the File class:
string[] result = File.ReadAllLines("a.txt");
What File.ReadAllLines does under the hood is actually identical to the code I provided above, except Microsoft uses an ArrayList instead of a List<string>, and at the end they return a string[] array via return (string[]) list.ToArray(typeof(string));.

Delete Specific record in RMS?

I am creating one J2ME application which read/write RMS record. I able to read and write
record in RMS but now problem is that I want to delete record by accepting some value
like accountNumber.
Format of RMS record.
101,ABC,12345,12345
and String str contain following data.
String str=accountSrNumber +","+ name +","+ balance +","+ TextDeposit;
deleteRecStore(str,accountSrNumber);
And I need to accept accountNumber(101) from user and need to delete this record.
Here is my Delete method.
public void deleteRecStore(String str, String accNumber121) //
{
int s=str.indexOf(accNumber121);
System.out.println("index in delete function"+s);
if(s==0)
{
try{
rs.deleteRecord(s);
// RecordStore.deleteRecordStore(REC_STORE);
System.out.println("record delete successfully");
}
catch (Exception e)
{}
}
}
I tried to use both of method rs.deleteRecord(s) and RecordStore.deleteRecordStore(REC_STORE);.
But none helps.
You always delete record 0 which is the first record, which is a bad idea.
For example, if you add two records, and delete them, and than add another record, it will be indexed as 2, so you will have to call deleteRecord(2) to remove it.
Method deleteRecordStore() removes entire recordStore (which contains records) - after that, if you create one, the next added record will be indexed as zero.
If I got the idea, you want to delete a record by it's acoountNumber.
If i'm right, you need to find the recordID by it's contents. The code will probably look like this (may have mistakes, did not test it, but the idea is important):
public void deleteRecStore(String accNumber121) {
RecordEnumeration e = rs.enumerateRecords();
int found = -1;
while (e.hasMoreElements()) {
int id = e.nextRecordId();
String next = new String(e.nextRecord());
if (next.startsWidth(accNumber121)) {
found = id;
}
}
if (found == -1) {
System.out.println("not found!");
} else {
rs.deleteRecord(found);
}
}

Resources