Java tutorial
/* J2ME in a Nutshell By Kim Topley ISBN: 0-596-00253-X */ import java.io.IOException; import java.io.InputStreamReader; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import javax.microedition.rms.RecordComparator; import javax.microedition.rms.RecordEnumeration; import javax.microedition.rms.RecordFilter; import javax.microedition.rms.RecordListener; import javax.microedition.rms.RecordStore; import javax.microedition.rms.RecordStoreException; import java.util.Vector; import javax.microedition.lcdui.Alert; import javax.microedition.lcdui.AlertType; 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.lcdui.Screen; import javax.microedition.lcdui.StringItem; import javax.microedition.lcdui.TextField; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; import javax.microedition.rms.RecordEnumeration; import javax.microedition.rms.RecordListener; import javax.microedition.rms.RecordStore; import javax.microedition.rms.RecordStoreException; public class PersistentRankingMIDlet extends MIDlet implements CommandListener, RecordListener, Runnable { private Command exitCommand; private Command okCommand; private Command cancelCommand; private Command newCommand; private Command checkCommand; private Command detailsCommand; private Command backCommand; private Command deleteCommand; private Display display; private TextField isbnField; private StringItem isbnDisplay; private StringItem titleDisplay; private StringItem rankingDisplay; private StringItem reviewDisplay; private StringItem checkTitle; private Form isbnForm; private Form searchForm; private Form resultForm; private Form checkForm; private List bookList; private Vector bookInfoList; private Thread searchThread; private BookStore bookStore; private BookInfo searchBookInfo; protected void startApp() throws MIDletStateChangeException { if (display == null) { initialize(); // If there are any books in the // book store, display the list. // Otherwise, start with the ISBN form. display.setCurrent(getSelectionScreen()); } } protected void pauseApp() { } protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { // Close the book store if (bookStore != null) { try { bookStore.removeRecordListener(this); bookStore.close(); } catch (RecordStoreException ex) { } } } public void commandAction(Command cmd, Displayable d) { if (cmd == exitCommand) { try { destroyApp(true); } catch (MIDletStateChangeException ex) { } notifyDestroyed(); } else if (cmd == okCommand) { String isbn = isbnField.getString().trim(); if (!isbn.equals("")) { isbnDisplay.setText(isbn); display.setCurrent(searchForm); searchForBook(new BookInfo(isbn)); } } else if (cmd == cancelCommand) { searchThread = null; isbnField.setString(null); display.setCurrent(getSelectionScreen()); } else if (cmd == newCommand) { isbnField.setString(null); display.setCurrent(isbnForm); } else if (cmd == detailsCommand || cmd == List.SELECT_COMMAND) { int index = bookList.getSelectedIndex(); searchBookInfo = (BookInfo) bookInfoList.elementAt(index); isbnDisplay.setText(searchBookInfo.getIsbn()); showBookInfo(searchBookInfo); } else if (cmd == deleteCommand) { int index = bookList.getSelectedIndex(); BookInfo bookInfo = (BookInfo) bookInfoList.elementAt(index); try { bookStore.deleteBook(bookInfo); } catch (RecordStoreException ex) { System.out.println("Delete failed: " + ex); } } else if (cmd == checkCommand) { String isbn = searchBookInfo.getIsbn(); checkTitle.setText(searchBookInfo.getTitle()); display.setCurrent(checkForm); searchForBook(searchBookInfo); } else if (cmd == backCommand) { display.setCurrent(getSelectionScreen()); } } public void searchForBook(BookInfo info) { searchBookInfo = info; searchThread = new Thread(this); searchThread.start(); } public void recordAdded(RecordStore recordStore, int recordId) { // Update the book list populateBookList(); } public void recordChanged(RecordStore recordStore, int recordId) { // Update the book list populateBookList(); } public void recordDeleted(RecordStore recordStore, int recordId) { // Update the book list populateBookList(); } public void run() { try { boolean found = Fetcher.fetch(searchBookInfo); if (searchThread == Thread.currentThread()) { if (found && searchBookInfo.getTitle() != null) { // Display the book details showBookInfo(searchBookInfo); // Add the new book to the book store bookStore.saveBookInfo(searchBookInfo); } else { Alert alert = new Alert("Book not found", null, null, AlertType.ERROR); alert.setTimeout(Alert.FOREVER); alert.setString("No book with ISBN " + searchBookInfo.getIsbn() + " was found."); isbnField.setString(null); display.setCurrent(alert, getSelectionScreen()); } } } catch (Throwable ex) { if (searchThread == Thread.currentThread()) { Alert alert = new Alert("Search Failed", null, null, AlertType.ERROR); alert.setTimeout(Alert.FOREVER); alert.setString("Search failed:\n" + ex.getMessage()); isbnField.setString(null); display.setCurrent(alert, getSelectionScreen()); } } } // Shows book details on the result screen private void showBookInfo(BookInfo info) { titleDisplay.setText(info.getTitle()); int ranking = info.getRanking(); int lastRanking = info.getLastRanking(); int change = ranking - lastRanking; String rankingText = ranking == 0 ? "" : String.valueOf(ranking); if (change > 0) { rankingText += ", down by " + change; } else if (change < 0) { rankingText += ", UP by " + (-change); } rankingDisplay.setText(rankingText); int reviews = info.getReviews(); int lastReviews = info.getLastReviews(); change = reviews - lastReviews; String reviewText = reviews == 0 ? "" : String.valueOf(reviews); if (change > 0) { reviewText += ", up by " + change; } reviewDisplay.setText(reviewText); display.setCurrent(resultForm); } // If there are any books in the // book store, display the list. // Otherwise, start with the ISBN form. private Screen getSelectionScreen() { return bookInfoList.isEmpty() ? (Screen) isbnForm : (Screen) bookList; } // Populates the list of books private void populateBookList() { // Clear out any existing content int count = bookList.size(); for (int i = 0; i < count; i++) { bookList.delete(0); } bookInfoList.removeAllElements(); // Add an entry for each book in the store try { RecordEnumeration e = bookStore.getBooks(); while (e.hasNextElement()) { int id = e.nextRecordId(); BookInfo info = bookStore.getBookInfo(id); bookInfoList.addElement(info); String title = info.getTitle(); if (title == null || title.equals("")) { title = info.getIsbn(); } bookList.append(title, null); } e.destroy(); } catch (Exception ex) { // Just leave an empty list. } // The ISBN list should have an exit command // only if the List screen is empty. isbnForm.removeCommand(exitCommand); if (bookInfoList.isEmpty()) { isbnForm.addCommand(exitCommand); } } private void initialize() { display = Display.getDisplay(this); // Open the book store bookStore = new BookStore(); exitCommand = new Command("Exit", Command.EXIT, 0); okCommand = new Command("OK", Command.OK, 0); cancelCommand = new Command("Cancel", Command.CANCEL, 0); newCommand = new Command("New", Command.SCREEN, 1); detailsCommand = new Command("Details", Command.OK, 0); checkCommand = new Command("Check", Command.OK, 0); backCommand = new Command("Back", Command.CANCEL, 1); deleteCommand = new Command("Delete", Command.SCREEN, 1); bookList = new List("Books", List.IMPLICIT); bookList.addCommand(detailsCommand); bookList.addCommand(newCommand); bookList.addCommand(deleteCommand); bookList.addCommand(exitCommand); bookInfoList = new Vector(); isbnForm = new Form("Book Query"); isbnForm.append("Enter an ISBN and press OK:"); isbnField = new TextField("", null, 10, TextField.ANY); isbnForm.append(isbnField); isbnForm.addCommand(okCommand); isbnForm.addCommand(exitCommand); isbnForm.addCommand(backCommand); searchForm = new Form("Book Search"); searchForm.append("Searching for ISBN\n"); isbnDisplay = new StringItem(null, null); searchForm.append(isbnDisplay); searchForm.append("\nPlease wait...."); searchForm.addCommand(cancelCommand); checkForm = new Form("Details Update"); checkForm.append("Getting details for\n"); checkTitle = new StringItem(null, null); checkForm.append(checkTitle); checkForm.append(" from amazon.com\nPlease wait...."); checkForm.addCommand(cancelCommand); resultForm = new Form("Search Results"); titleDisplay = new StringItem("Book title: ", null); rankingDisplay = new StringItem("Ranking: ", null); reviewDisplay = new StringItem("Reviews: ", null); resultForm.append(titleDisplay); resultForm.append(rankingDisplay); resultForm.append(reviewDisplay); resultForm.addCommand(backCommand); resultForm.addCommand(checkCommand); // Register for events from all of the forms isbnForm.setCommandListener(this); searchForm.setCommandListener(this); resultForm.setCommandListener(this); bookList.setCommandListener(this); // Listen for changes in the content of the book store bookStore.addRecordListener(this); // Install the books held in the record store populateBookList(); } } // A class that implements a persistent store // of books, keyed by ISBN. class BookStore implements RecordComparator, RecordFilter { // The name of the record store used to hold books private static final String STORE_NAME = "BookStore"; // The record store itself private RecordStore store; // ISBN to be used during a filter operation private String searchISBN; // Creates a bookstore and opens it public BookStore() { try { store = RecordStore.openRecordStore(STORE_NAME, true); } catch (RecordStoreException ex) { System.err.println(ex); } } // Closes the bookstore public void close() throws RecordStoreException { if (store != null) { store.closeRecordStore(); } } // Gets the number of books in the book store public int getBookCount() throws RecordStoreException { if (store != null) { return store.getNumRecords(); } return 0; } // Adds a listener to the book store public void addRecordListener(RecordListener l) { if (store != null) { store.addRecordListener(l); } } // Removes a listener from the book store public void removeRecordListener(RecordListener l) { if (store != null) { store.removeRecordListener(l); } } // Gets a sorted list of all of the books in // the store. public RecordEnumeration getBooks() throws RecordStoreException { if (store != null) { return store.enumerateRecords(null, this, false); } return null; } // Gets a BookInfo from a store record // given its ISBN public BookInfo getBookInfo(String isbn) throws RecordStoreException, IOException { BookInfo bookInfo = null; searchISBN = isbn; // Look for a book with the given ISBN RecordEnumeration e = store.enumerateRecords(this, null, false); // If found, get its identifier and // fetch its BookInfo object if (e.numRecords() > 0) { int id = e.nextRecordId(); bookInfo = getBookInfo(id); } // Release the enumeration e.destroy(); return bookInfo; } // Gets a BookInfo from a store record // given its record identifier public BookInfo getBookInfo(int id) throws RecordStoreException, IOException { byte[] bytes = store.getRecord(id); DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes)); String isbn = is.readUTF(); BookInfo info = new BookInfo(isbn); info.id = id; info.title = is.readUTF(); info.ranking = is.readInt(); info.reviews = is.readInt(); info.lastRanking = is.readInt(); info.lastReviews = is.readInt(); return info; } // Adds an entry to the store or modifies the existing // entry if a matching ISBN exists. public void saveBookInfo(BookInfo bookInfo) throws IOException, RecordStoreException { if (store != null) { searchISBN = bookInfo.getIsbn(); RecordEnumeration e = store.enumerateRecords(this, null, false); if (e.numRecords() > 0) { // A matching record exists. Set the id // of the BookInfo to match the existing record bookInfo.id = e.nextRecordId(); byte[] bytes = toByteArray(bookInfo); store.setRecord(bookInfo.id, bytes, 0, bytes.length); } else { // Create a new record bookInfo.id = store.getNextRecordID(); byte[] bytes = toByteArray(bookInfo); store.addRecord(bytes, 0, bytes.length); } // Finally, destroy the RecordEnumeration e.destroy(); } } // Deletes the entry for a book from the store public void deleteBook(BookInfo bookInfo) throws RecordStoreException { if (store != null) { store.deleteRecord(bookInfo.id); } } // RecordComparator implementation public int compare(byte[] book1, byte[] book2) { try { DataInputStream stream1 = new DataInputStream(new ByteArrayInputStream(book1)); DataInputStream stream2 = new DataInputStream(new ByteArrayInputStream(book2)); // Match based on the ISBN, but sort based on the title. String isbn1 = stream1.readUTF(); String isbn2 = stream2.readUTF(); if (isbn1.equals(isbn2)) { return RecordComparator.EQUIVALENT; } String title1 = stream1.readUTF(); String title2 = stream2.readUTF(); int result = title1.compareTo(title2); if (result == 0) { return RecordComparator.EQUIVALENT; } return result < 0 ? RecordComparator.PRECEDES : RecordComparator.FOLLOWS; } catch (IOException ex) { return RecordComparator.EQUIVALENT; } } // RecordFilter implementation public boolean matches(byte[] book) { if (searchISBN != null) { try { DataInputStream stream = new DataInputStream(new ByteArrayInputStream(book)); // Match based on the ISBN. return searchISBN.equals(stream.readUTF()); } catch (IOException ex) { System.err.println(ex); } } // Default is not to match return false; } // Writes a record into a byte array. private byte[] toByteArray(BookInfo bookInfo) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream(baos); os.writeUTF(bookInfo.isbn); os.writeUTF(bookInfo.title == null ? "" : bookInfo.title); os.writeInt(bookInfo.ranking); os.writeInt(bookInfo.reviews); os.writeInt(bookInfo.lastRanking); os.writeInt(bookInfo.lastReviews); return baos.toByteArray(); } } /** * A class that represents a book listing * at an online book set, including the number * of reviews for the book and its sales ranking. */ class BookInfo { int id; // Used when persisting String isbn; // The book ISBN String title; // The book title int reviews; // Number of reviews int ranking; // Current ranking int lastReviews; // Last review count int lastRanking; // Last ranking public BookInfo(String isbn) { this.isbn = isbn; } public String getIsbn() { return isbn; } public String getTitle() { return title; } public int getReviews() { return reviews; } public int getRanking() { return ranking; } public int getLastReviews() { return lastReviews; } public int getLastRanking() { return lastRanking; } // Installs details parsed from an input stream public void setFromInputStream(InputStream is) { // Use an InputHelper to search the input InputHelper helper = new InputHelper(is); try { // Default new values to current values int newRanking = this.ranking; int newReviews = this.reviews; boolean found = helper.moveAfterString("buying info: "); if (!found) { return; } // Gather the title from the rest of this line StringBuffer titleBuffer = helper.getRestOfLine(); // Look for the number of reviews found = helper.moveAfterString("Based on "); if (!found) { return; } // Gather the number of reviews from the current location String reviewString = helper.gatherNumber(); // Look for the sales rank found = helper.moveAfterString("Sales Rank: "); if (!found) { return; } // Gather the number from the current location String rankingString = helper.gatherNumber(); // Having safely found everything, set the new title title = titleBuffer.toString().trim(); // Now convert the reviews and ranking to integers. // If they fail to convert, just leave the existing // values. try { newRanking = Integer.parseInt(rankingString); } catch (NumberFormatException ex) { } if (newRanking != ranking) { lastRanking = ranking; ranking = newRanking; if (lastRanking == 0) { // First time, set last and current // to the same value lastRanking = ranking; } } try { newReviews = Integer.parseInt(reviewString); } catch (NumberFormatException ex) { } if (newReviews != reviews) { lastReviews = reviews; reviews = newReviews; if (lastReviews == 0) { // First time, set last and current // to the same value lastReviews = reviews; } } } catch (IOException ex) { } finally { // Allow garbage collection helper.dispose(); helper = null; } } } // A class that scans through an input // stream for strins without reading the // entire stream into a large string. class InputHelper { // Size of the input buffer private static final int BUFFER_SIZE = 1024; // The input buffer private final char[] buffer = new char[BUFFER_SIZE]; // Number of characters left in the buffer private int charsLeft; // Index of the next character in the buffer private int nextChar; // InputStreamReader used to map to Unicode private InputStreamReader reader; // Constructs a helper to read a given stream public InputHelper(InputStream is) { reader = new InputStreamReader(is); } // Cleans up when no longer needed public void dispose() { if (reader != null) { try { reader.close(); } catch (IOException ex) { } reader = null; } } // Looks for a given string in the input // stream and positions the stream so that the // next character read is one beyond the string. // Returns true if the string was found, false if // not (and the stream will have been completely read). public boolean moveAfterString(String str) throws IOException { char[] chars = str.toCharArray(); int count = chars.length; char firstChar = chars[0]; char c = (char) 0; for (;;) { if (c != firstChar && !findNext(firstChar)) { // Reached the end of the input stream return false; } boolean mismatch = false; for (int i = 1; i < count; i++) { c = getNext(); if (c != chars[i]) { mismatch = true; break; } } if (!mismatch) { return true; } // Mismatch. 'c' has the first mismatched // character - start the loop again with // that character. This is necessary because we // could have found "wweb" while looking for "web" } } // Gets the characters for a number, ignoring // the grouping separator. The number starts at the // current input position, but any leading non-numerics // are skipped. public String gatherNumber() throws IOException { StringBuffer sb = new StringBuffer(); boolean gotNumeric = false; for (;;) { char c = getNext(); // Skip until we find a digit. boolean isDigit = Character.isDigit(c); if (!gotNumeric && !isDigit) { continue; } gotNumeric = true; if (!isDigit) { if (c == '.' || c == ',') { continue; } break; } sb.append(c); } return sb.toString(); } // Gets the balance of the current line // and returns it as a StringBuffer public StringBuffer getRestOfLine() throws IOException { StringBuffer sb = new StringBuffer(); char c; for (;;) { c = getNext(); if (c == '\n' || c == (char) 0) { break; } sb.append(c); } return sb; } // Gets the next character from the stream, // returning (char)0 when all input has been read. private char getNext() throws IOException { if (charsLeft == 0) { charsLeft = reader.read(buffer, 0, BUFFER_SIZE); if (charsLeft < 0) { return (char) 0; } nextChar = 0; } charsLeft--; return buffer[nextChar++]; } // Finds the next instance of a given character in the // input stream. The input stream is positioned after // the located character. If EOF is reached without // finding the character, false is returned. private boolean findNext(char c) throws IOException { for (;;) { if (charsLeft == 0) { charsLeft = reader.read(buffer, 0, BUFFER_SIZE); if (charsLeft < 0) { return false; } nextChar = 0; } charsLeft--; if (c == buffer[nextChar++]) { return true; } } } }