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);
}
}
Related
I'm using in my app this example to let the user search/filter a table.
When you run the application, it works perfect.
The problem is when I left the program opened, minimized, and after a while (10-15min) I try to use it again. It's then when the search/filter doesn't work at all.
Here is the code:
#FXML
private void initialize() {
// 0. Initialize the columns.
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
// 1. Wrap the ObservableList in a FilteredList (initially display all data).
FilteredList<Person> filteredData = new FilteredList<>(masterData, p -> true);
// 2. Set the filter Predicate whenever the filter changes.
filterField.textProperty().addListener((observable, oldValue, newValue) -> {
filteredData.setPredicate(person -> {
// If filter text is empty, display all persons.
if (newValue == null || newValue.isEmpty()) {
return true;
}
// Compare first name and last name of every person with filter text.
String lowerCaseFilter = newValue.toLowerCase();
if (person.getFirstName().toLowerCase().contains(lowerCaseFilter)) {
return true; // Filter matches first name.
} else if (person.getLastName().toLowerCase().contains(lowerCaseFilter)) {
return true; // Filter matches last name.
}
return false; // Does not match.
});
});
// 3. Wrap the FilteredList in a SortedList.
SortedList<Person> sortedData = new SortedList<>(filteredData);
// 4. Bind the SortedList comparator to the TableView comparator.
sortedData.comparatorProperty().bind(personTable.comparatorProperty());
// 5. Add sorted (and filtered) data to the table.
personTable.setItems(sortedData);
}
}
I tried the code and it works for me.
However, step 2 must be the last step because the filteredData.setPredicate will be effective only after the second user input.
If someone just add a single letter, then the filtering will not happen.
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
I'm trying to filter some records using the RecordFilter interface. In my app I have a couple of interfaces similar to this one, on which the user can enter an ID or Name (he/she could enter both or neither of them too)
Here's what I've done so far:
The Customer filter.
Here if the user didn't enter an ID, I pass 0 as a default value, that's why I evaluate customerID!=0
public class CustomerFilter implements RecordFilter {
private String mName_Filter;
private int mID_Filter;
public CustomerFilter(String name_Filter, int id_Filter) {
this.mName_Filter = name_Filter.toLowerCase();
this.mID_Filter = id_Filter;
}
public boolean matches(byte[] candidate) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream(candidate);
DataInputStream dis = new DataInputStream(bis);
int customerID = dis.readInt();
String customerName = dis.readUTF().toLowerCase();
if ((customerName != null && customerName.indexOf(mName_Filter) != -1) && (customerID != 0 && customerID == mID_Filter))
return true;
if (customerName != null && customerName.indexOf(mName_Filter) != -1 && customerID == 0)
return true;
if (customerName == null && (customerID != 0 && customerID == mID_Filter))
return true;
if (customerName == null && customerID == 0)
return true;
} catch (IOException ex) {
//What's the point in catching a exception here???
}
return false;
}
}
The search method:
Note: This method is in a class that I call "RMSCustomer", in which I deal with everything related to RMS access. The search method receives two parameters (id and name) and uses them to instantiate the filter.
public Customer[] search(int id, String name) throws RecordStoreException, IOException {
RecordStore rs = null;
RecordEnumeration recEnum = null;
Customer[] customerList = null;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
if (rs.getNumRecords() > 0) {
CustomerFilter filter = new CustomerFilter(name, id);
try {
recEnum = rs.enumerateRecords(filter, null, false);
if (recEnum.numRecords() > 0) {
customerList = new Customer[recEnum.numRecords()];
int counter = 0;
while (recEnum.hasNextElement()) {
Customer cust;
int idRecord = recEnum.nextRecordId();
byte[] filterRecord = rs.getRecord(idRecord);
cust = parseRecord(filterRecord);
cust.idRecord = idRecord;
customerList[counter] = cust;
counter++;
}
}
else{
customerList = new Customer[0];
//How to send a message to the midlet from here
//saying something like "No Record Exists.Please select another filter"
}
} finally {
recEnum.destroy();
}
}
else{
//How to send a message to the midlet from here
//saying something like "No Record Exists.Please Add record"
}
} finally {
rs.closeRecordStore();
}
return customerList;
}
Even though, the code shown above works I still have some questions/problems:
In the Filter :
1) How can I improve the code that evaluates the possible values of the filters (name,id)? What if I had more filters?? Will I have to test all the possible combinations??
2) If the user doesn’t enter neither a ID nor a name, should I display all the records or should I display a message "Please enter a name or ID"?? What would you do in this case?
3) Why do I have to put a try-catch in the filter when I can't do anything there?? I can't show any alert from there or can I?
In the search method:
1) How can I show a proper message to the user from that method? something like "No records" (see the "ELSE" parts in my code
Sorry If I asked too many questions, it's just that there's any complete example of filters.
Thanks in advance
How can I improve the code that evaluates the possible values of the
filters (name,id)?
The ID is the first field in the record and the fastest one to search for. If the Id matches, It doesn't really matter what the customer name is. Normally you'll be looking for the records where the ID matches OR the customer name matches, so once the ID matches you can return true. This is my proposal for the CustomerFilter class:
public class CustomerFilter implements RecordFilter {
private String mName_Filter;
//Use Integer instead of int.
//This way we can use null instead of zero if the user didn't type an ID.
//This allows us to store IDs with values like 0, -1, etc.
//It is a bit less memory efficient,
//but you are not creating hundreds of filters, are you? (If you are, don't).
private Integer mID_Filter;
public CustomerFilter(String name_Filter, Integer id_Filter) {
this.mName_Filter = normalizeString(mName_Filter);
this.mID_Filter = id_Filter;
}
//You should move this function to an StringUtils class and make it public.
//Other filters might need it in the future.
private static String normalizeString(final String s){
if(s != null){
//Warning: you might want to replace accentuated chars as well.
return s.toLowerCase();
}
return null;
}
public boolean matches(byte[] candidate) {
ByteArrayInputStream bis = new ByteArrayInputStream(candidate);
DataInputStream dis = new DataInputStream(bis);
try {
if(mID_Filter != null){
//If the ID is unique, and the search is ID OR other fields, this is fine
int customerID = dis.readInt();
if(mID_Filter.intValue == customerID){
return true;
} else {
return false;
}
}
if(mName_Filter != null){
String customerName = normalizeString(dis.readUTF());
if(customerName != null && customerName.indexOf(mName_Filter) != -1){
return true;
}
}
if(mID_Filter == null && mName_Filter == null){
return true; // No filtering, every record matches.
}
} catch (IOException ex) {
//Never swallow exceptions.
//Even if you are using an underlying ByteArrayInputStream, an exception
//can still be thrown when reading from DataInputStream if you try to read
//fields that do not exists.
//But even if no exceptions were ever thrown, never swallow exceptions :)
System.err.println(ex);
//Optional: throw ex;
} finally {
//Always close streams.
if(bis != null){
try {
bis.close();
} catch(IOException ioe){
System.err.println(ioe);
}
}
if(dis != null){
try {
dis.close();
} catch(IOException ioe){
System.err.println(ioe);
}
}
}
return false;
}
}
What if I had more filters?? Will I have to test all the possible
combinations??
It depends on your project. Usually the ID is unique and no two records exist with the same id. In this case you should explicitly design the screen so that the user understands that either he types an Id, or else he fills in the other fields. The condition would be like this:
idMatches OR (field1Matches AND field2Matches AND ... fieldNMatches)
If the user types nothing, then all records will be returned.
But then again this is more a UX issue, I don't know if it is valid for your requirements.
From the programming point of view, what is clear is that the more fields you add, the more messy your filter will became. To prevent this, you could use patterns like Decorator, Composite, and even Chain of responsibility. You'll probably have to trade good design for performance though.
If the user doesn’t enter neither a ID nor a name, should I display
all the records or should I display a message "Please enter a name or
ID"?? What would you do in this case?
It depends. Is there any other way to view all records? If so, then show the message.
Why do I have to put a try-catch in the filter when I can't do
anything there?? I can't show any alert from there or can I?
You shouldn't. This class is only responsible of filtering, not of interacting with the user. You can still log the error from the catch clause, and then throw the exception again. That will propagate the exception up to RMSCustomer.search, so whatever client code is calling that function will handle the exception in the same way you are handling the other ones thrown by that method. But keep the finally clause to close the streams.
How can I show a proper message to the user from that method?
something like "No records" (see the "ELSE" parts in my code)
You shouldn't do anything related to the GUI (like showing dialogs) from the RMSCustomer class. Even if you are not using the Model-View-Controller pattern, you still want to keep your class focused on a single responsibility (managing records). This is called the Single responsibility principle.
Keeping your class isolated from the GUI will allow you to test it and reuse it in environments without GUI.
The no records case should be handled by the screen when there are zero results. An array of lenght == 0 is fine here, and the screen will show the "No results" message. For other kinds of errors, you can extend the Exception class and throw your own custom exceptions, i.e: RecordParsingException, from the RMSCustomer.search method. The screen class will then map the different exceptions to the error message in the language of the user.
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);
}
}
I am using LWUIT and showing data with Table, say, flight information!
Instead of writing air companies with text I just like to replace them with icons.
So, I need to override protected Component createCell(Object value, final int row, final int column, boolean editable) method of Table.
This is how I've implemented:
Initializing
imgAln[i]=null;
try {
imgAln[i] = Image.createImage(strPathToImage[i]);
//e.g /uta.png,/somonair.png and so on
lAln[i] = new Label(imgAln[i]);
} catch (IOException e) { }
Creating Table object
Table table = new Table(model) {
protected Component createCell(Object value, final int row,
final int column, boolean editable) {
final Component c = super.createCell(value, row, column, editable);
if (column == 6) {
return lAln[value]; //it does not work here
}
}
};
need help to add Image to table cell!!!
Is there any example??? links are welcome!
The problem in your createCell(...) implementation is that it does not return the super.createCell(...) when the column is not 6. Also your array of labels (lAln) may not be properly created. Try my implementation below, but make sure you store the appropriate image name in the table models' column 0.
This should solve it:
TableModel model = new DefaultTableModel(
new String[]{"Uneditable", "Editable", "CheckBox", "Multiline"},
new Object[][]{
{"/animations.png", "", new Boolean(false), "Multi-line text\nright here"},
{"/buttons.png", "", new Boolean(true), "Further text that\nspans lines"},
{"/dialogs.png", "", new Boolean(true), "No span"},
{"/fonts.png", "", new Boolean(false), "Spanning\nFor\nEvery\nWord"},
});
Table table = new Table(model) {
protected Component createCell(Object value, final int row,
final int column, boolean editable) {
if (row != -1 && column == 0) {
try {
//In my case Column 0 store the resource path names
return new Label(Image.createImage((String)value));
} catch (Exception ex) {
ex.printStackTrace();
}
}
return super.createCell(value, row, column, editable);
}
};
NOTE: If you see the names instead of images in column 0 it means the image path is incorrect, fix it to see the images.
Did you manage to have a look at TableLayoutDemo.java in project LWUITDemo? If i remember it correct, this comes bundled download package LWUIT1.5.zip (or you can always google it).
Let me know if you need more specific help.