/** * @author Conlan Rios (conlan@gmail.com) * A midlet for retrieving stock quotes off the internet via Yahoo! finances. * Allows you to get quotes for a specific symbol, lookup a ticker, and save * commonly viewed symbols in a portfolio. */ import java.io.IOException; import java.io.InputStream; import java.util.Vector; import javax.microedition.io.Connector; 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.Gauge; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.ImageItem; import javax.microedition.lcdui.Item; import javax.microedition.lcdui.ItemCommandListener; import javax.microedition.lcdui.ItemStateListener; 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.rms.RecordEnumeration; import javax.microedition.rms.RecordFilter; import javax.microedition.rms.RecordStore; import javax.microedition.rms.RecordStoreException; public class StockMIDlet extends MIDlet implements ItemStateListener, CommandListener, ItemCommandListener { public StockMIDlet() { Image getQuotesImage = null; Image portfolioImage = null; Image symbolLookupImage = null; try { getQuotesImage = Image.createImage("/getQuotes.PNG"); portfolioImage = Image.createImage("/myPortfolio.PNG"); symbolLookupImage = Image.createImage("/symbolLookup.PNG"); } catch (IOException ex) { ex.printStackTrace(); } getQuotesImageItem = getImageItem(GET_QUOTES_TITLE, getQuotesImage); portfolioImageItem = getImageItem(MY_PORTFOLIO_TITLE, portfolioImage); symbolLookupImageItem = getImageItem(SYMBOL_LOOKUP_TITLE, symbolLookupImage); mainScreen = new Form(MIDLET_TITLE); mainScreen.append(getQuotesImageItem); mainScreen.append(symbolLookupImageItem); mainScreen.append(portfolioImageItem); mainScreen.setItemStateListener(this); mainScreen.setCommandListener(this); mainScreen.addCommand(cmdExit); } private ImageItem getImageItem(String text, Image image) { ImageItem item = new ImageItem(text, image, Item.LAYOUT_2, null); item.setDefaultCommand(cmdGo); item.setItemCommandListener(this); return item; } public void itemStateChanged(Item item) { if (item == getQuotesField) getQuotesField.setString(getQuotesField.getString().toUpperCase()); } public void commandAction(Command c, Displayable d) { if (d == getQuotesScreen) { if ((c == cmdSearch) && (!getQuotesField.getString().trim().equals(""))) { previousScreen = getQuotesScreen; new Thread(new GetQuoteRunner(getQuotesField.getString())).start(); getQuotesField.setString(""); } else if (c == cmdBack) { display.setCurrent(mainScreen); getQuotesField.setString(""); } } else if (d == getQuotesResults) { if (c == cmdBack) display.setCurrent(previousScreen); else if (c == cmdSave) { String symbol = ((StringItem) getQuotesResults.get(0)).getText(); saveToPortfolio(symbol); display.setCurrent(successfulSave, previousScreen); } } else if (d == symbolLookupScreen) { if ((c == cmdSearch) && (!symbolLookupField.getString().trim().equals(""))) { previousScreen = symbolLookupScreen; new Thread(new SymbolLookupRunner(symbolLookupField.getString())).start(); symbolLookupField.setString(""); } if (c == cmdBack) { display.setCurrent(mainScreen); symbolLookupField.setString(""); } } else if (d == symbolLookupResults) { if (c == cmdBack) display.setCurrent(symbolLookupScreen); else if (c == List.SELECT_COMMAND || c == cmdGo) { String element = results.elementAt( symbolLookupResults.getSelectedIndex()).toString(); int index = element.indexOf(SymbolLookupRunner.SYMBOL_DELIMETER); String query = element.substring(0, index); previousScreen = symbolLookupResults; new Thread(new GetQuoteRunner(query)).start(); } } else if (d == portfolioScreen) { if (c == cmdBack) display.setCurrent(mainScreen); else if (c == List.SELECT_COMMAND || c == cmdGo) { if (portfolioScreen.size() > 0) { String query = portfolioScreen.getString( portfolioScreen.getSelectedIndex()); previousScreen = portfolioScreen; new Thread(new GetQuoteRunner(query)).start(); } } } else if (d == waitingScreen) { setDisplayScreen(previousScreen, null, null); } else if (d == mainScreen) { if (c == cmdExit) { destroyApp(true); notifyDestroyed(); } } } public void commandAction(Command c, Item i) { if (i == getQuotesImageItem) { if (getQuotesScreen == null) setupQuoteLookup(); if (waitingScreen == null) setupMiscScreens(); display.setCurrent(getQuotesScreen); } else if (i == symbolLookupImageItem) { if (symbolLookupScreen == null) setupSymbolLookup(); if (waitingScreen == null) setupMiscScreens(); display.setCurrent(symbolLookupScreen); } else if (i == portfolioImageItem) { if (portfolioScreen == null) setupPortfolioScreen(); if (waitingScreen == null) setupMiscScreens(); loadMyPortfolio(); display.setCurrent(portfolioScreen); } } protected void destroyApp(boolean arg0) {} protected void pauseApp() {} protected void startApp() { if (display == null) display = Display.getDisplay(this); display.setCurrent(mainScreen); } private void setupPortfolioScreen() { portfolioScreen = new List(MY_PORTFOLIO_TITLE, List.IMPLICIT); portfolioScreen.addCommand(cmdGo); portfolioScreen.addCommand(cmdBack); portfolioScreen.setCommandListener(this); } private void loadMyPortfolio() { portfolioScreen.deleteAll(); RecordStore rs = null; RecordEnumeration re = null; try { try { rs = RecordStore.openRecordStore(RECORDSTORE_NAME, true); re = rs.enumerateRecords(null, null, false); while (re.hasNextElement()) { String symbol = new String(re.nextRecord()); portfolioScreen.append(symbol, null); } } finally { if (re != null) re.destroy(); if (rs != null) rs.closeRecordStore(); } } catch (RecordStoreException ex) { ex.printStackTrace(); } } /** * Initiates the components for getting quotes. */ private void setupQuoteLookup() { getQuotesField = new TextField(null, "", QUERY_MAXLENGTH, TextField.ANY); getQuotesScreen = new Form(null); getQuotesScreen.append(getQuotesField); getQuotesScreen.addCommand(cmdBack); getQuotesScreen.addCommand(cmdSearch); getQuotesScreen.setItemStateListener(this); getQuotesScreen.setCommandListener(this); getQuotesScreen.setTitle(GET_QUOTES_TITLE); } /** * Initiates the components for misc use. (waiting, invalid erorrs, saving) */ private void setupMiscScreens() { waitingScreen = new Alert("Searching..."); waitingScreen.setTimeout(Alert.FOREVER); waitingScreen.setIndicator(new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING)); waitingScreen.addCommand(new Command("Cancel", Command.CANCEL, 1)); waitingScreen.setCommandListener(this); invalidTickerScreen = new Alert("Invalid Symbol or Query", "No results were found. Please try again.", null, AlertType.ERROR); successfulSave = new Alert("Saved to Portfolio", "Symbol saved to portfolio.", null, AlertType.CONFIRMATION); } private void setupSymbolLookup() { symbolLookupField = new TextField(null, "", SYMBOL_MAXLENGTH, TextField.ANY); symbolLookupScreen = new Form(SYMBOL_LOOKUP_TITLE); symbolLookupScreen.append(symbolLookupField); symbolLookupScreen.addCommand(cmdBack); symbolLookupScreen.addCommand(cmdSearch); symbolLookupScreen.setCommandListener(this); symbolLookupScreen.setTitle(SYMBOL_LOOKUP_TITLE); } /** * Saves a commonly viewed symbol to the portfolio for easy access later. */ private void saveToPortfolio(String symbol) { RecordStore rs = null; RecordEnumeration re = null; try { try { rs = RecordStore.openRecordStore(RECORDSTORE_NAME, true); re = rs.enumerateRecords(new PortfolioFilter(symbol), null, false); // check if the symbol to be saved is already there if (re.hasNextElement()) return; byte[] raw = symbol.getBytes(); rs.addRecord(raw, 0, raw.length); } finally { if (re != null) re.destroy(); if (rs != null) rs.closeRecordStore(); } } catch (RecordStoreException ex) { ex.printStackTrace(); } } /** * Sets the current display. Synchronized so a user that decides to cancel * a search doesn't have their display change when search is completed. * @param setTo screen to set to * @param conditionScreen null or screen that display has to have set as * current */ private synchronized void setDisplayScreen(Screen setTo, Screen conditionScreen, Alert optionalAlert) { if (conditionScreen != null) { if (display.getCurrent() != conditionScreen) return; } if (optionalAlert == null) display.setCurrent(setTo); else display.setCurrent(optionalAlert, setTo); } /** * Used when searching for a specific symbol in the portfolio. */ private class PortfolioFilter implements RecordFilter { public PortfolioFilter(String toFilterFor) { toFilter = toFilterFor; } public boolean matches(byte[] raw) { String record = new String(raw); return record.equals(toFilter); } private String toFilter; } /** * Runner class for retrieving quotes off the net via yahoo! finances */ private class GetQuoteRunner implements Runnable { public GetQuoteRunner(String aSymbol) { symbol = aSymbol; symbol = symbol.replace(' ', '+'); } public void run() { display.setCurrent(waitingScreen); StringBuffer quote = new StringBuffer(); InputStream is = null; try { try { int ch = 0; is = Connector.openDataInputStream(GETQUOTES_URL + symbol + GETQUOTES_FORMAT); while ((ch = is.read()) != -1) quote.append((char) ch); } finally { if (is != null) is.close(); } } catch (IOException ex) { ex.printStackTrace(); } quoteInfo = new String(quote).trim(); parseQuoteInfo(); } /** * Parses the string returned by the 'get quotes' and puts it in the * correct format. */ private void parseQuoteInfo() { if (getQuotesResults == null) { getQuotesResults = new Form(null); getQuotesResults.addCommand(cmdBack); getQuotesResults.addCommand(cmdSave); getQuotesResults.setCommandListener(StockMIDlet.this); try { upImage = Image.createImage("/up.PNG"); downImage = Image.createImage("/down.PNG"); } catch (IOException ex) { ex.printStackTrace(); } } String quoteNameString = getStringFromQuoteInfo(true); String lastTradeString = getStringFromQuoteInfo(false); String dateString = getStringFromQuoteInfo(true); // check for valid symbol if (dateString.equals("N/A")) { setDisplayScreen(previousScreen, waitingScreen, invalidTickerScreen); return; } String tradeTimeString = getStringFromQuoteInfo(true); String changeString = getStringFromQuoteInfo(false); getQuotesResults.setTitle(quoteNameString + " Summary"); getQuotesResults.deleteAll(); getQuotesResults.append(getStringItem("Name", quoteNameString, true)); getQuotesResults.append(getStringItem("Date", dateString, true)); getQuotesResults.append(getStringItem("Change", changeString, false)); double changeDouble = Double.parseDouble(changeString); ImageItem imageItem = new ImageItem(null, null, Item.LAYOUT_2 | Item.LAYOUT_NEWLINE_AFTER, null); getQuotesResults.append(imageItem); if (changeDouble > 0) imageItem.setImage(upImage); else imageItem.setImage(downImage); getQuotesResults.append(getStringItem("Last Trade", lastTradeString, true)); getQuotesResults.append(getStringItem("Trade Time", tradeTimeString, true)); setDisplayScreen(getQuotesResults, waitingScreen, null); } private String getStringFromQuoteInfo(boolean enclosedInParentheses) { commaIndex = nextCommaIndex + 1; nextCommaIndex = quoteInfo.indexOf(",", commaIndex); if (enclosedInParentheses) return quoteInfo.substring(commaIndex + 1, nextCommaIndex - 1); else return quoteInfo.substring(commaIndex, nextCommaIndex); } private StringItem getStringItem(String title, String text, boolean newLine) { StringItem si = new StringItem(title, text); if (newLine) si.setLayout(Item.LAYOUT_2 | Item.LAYOUT_NEWLINE_AFTER); else si.setLayout(Item.LAYOUT_2); return si; } private int commaIndex = 0; private int nextCommaIndex = -1; private String quoteInfo; private String symbol; } /** * Runner class for looking up symbols off the net. */ private class SymbolLookupRunner implements Runnable { public SymbolLookupRunner(String aQuery) { query = aQuery; query = query.replace(' ', '+'); } public void run() { display.setCurrent(waitingScreen); StringBuffer input = new StringBuffer(); InputStream is = null; try { try { int ch = 0; is = Connector.openDataInputStream(SYMBOL_LOOKUP_URL + query); while ((ch = is.read()) != -1) input.append((char) ch); } finally { if (is != null) is.close(); } } catch (IOException ex) { ex.printStackTrace(); } parseSearchResults(input.toString()); } private void parseSearchResults(String input) { StringBuffer symbol = new StringBuffer(); if (results == null) results = new Vector(); else results.removeAllElements(); int curIndex = 0; int from = 0; while ((curIndex = input.indexOf(SYMBOL_LOOKUP_PATTERN, from)) != -1) { boolean inCarot = true; boolean stop = false; int i = curIndex + SYMBOL_LOOKUP_PATTERN.length(); int phrases = 0; while (!stop) { i ++; char ch = input.charAt(i); if (inCarot) { if (ch == OUT_CAROT_CHAR) inCarot = false; } else { if (ch == IN_CAROT_CHAR) inCarot = true; else { symbol.append(ch); if (input.charAt(i + 1) == IN_CAROT_CHAR) { phrases ++; if (phrases == MAX_SYMBOL_PHRASES) stop = true; else symbol.append(SYMBOL_DELIMETER); } } } } results.addElement(symbol.toString()); // clear the string buffer for next time symbol.delete(0, symbol.length()); curIndex ++; from = curIndex; } resolveSearchResults(); } private void resolveSearchResults() { if (results.size() > 0) { // remove the last element, which is 'view all symbols' results.removeElementAt(results.size() - 1); if (symbolLookupResults == null) { symbolLookupResults = new List("Search Results", List.IMPLICIT); symbolLookupResults.addCommand(cmdBack); symbolLookupResults.addCommand(cmdGo); symbolLookupResults.setCommandListener(StockMIDlet.this); } symbolLookupResults.deleteAll(); int size = results.size(); for (int i = 0; i < size; i++) { String result = results.elementAt(i).toString(); int index = result.indexOf(SYMBOL_DELIMETER) + SYMBOL_DELIMETER.length(); String finalResult = result.substring(index, result.length()); symbolLookupResults.append(finalResult, null); } setDisplayScreen(symbolLookupResults, waitingScreen, null); } else { setDisplayScreen(symbolLookupScreen, waitingScreen, invalidTickerScreen); } } public static final String SYMBOL_DELIMETER = "|_|"; private String query; private final int MAX_SYMBOL_PHRASES = 2; private final char IN_CAROT_CHAR = '<'; private final char OUT_CAROT_CHAR = '>'; } private Alert waitingScreen; private Alert successfulSave; private Alert invalidTickerScreen; private Display display; private Form mainScreen; private Form getQuotesScreen; private Form getQuotesResults; private Form symbolLookupScreen; private Image upImage; private Image downImage; private ImageItem getQuotesImageItem; private ImageItem symbolLookupImageItem; private ImageItem portfolioImageItem; private List symbolLookupResults; private List portfolioScreen; private TextField getQuotesField; private TextField symbolLookupField; private Screen previousScreen; private Vector results; private final Command cmdGo = new Command("Go", Command.SCREEN, 1); private final Command cmdBack = new Command("Back", Command.BACK, 1); private final Command cmdExit = new Command("Quit", Command.EXIT, 1); private final Command cmdSave = new Command("Save", Command.SCREEN, 1); private final Command cmdSearch = new Command("Lookup", Command.SCREEN, 1); private final int SYMBOL_MAXLENGTH = 24; private final int QUERY_MAXLENGTH = 12; private final String MIDLET_TITLE = "Stock MIDlet"; private final String GET_QUOTES_TITLE = "Get Quotes"; private final String SYMBOL_LOOKUP_TITLE = "Lookup Symbol"; private final String MY_PORTFOLIO_TITLE = "My Portfolio"; private final String RECORDSTORE_NAME = "Portfolio"; private final String GETQUOTES_URL = "http://download.finance.yahoo.com/d/quotes.csv?s="; private final String GETQUOTES_FORMAT = "&f=sl1d1t1c1ohgv&e=.csv"; private final String SYMBOL_LOOKUP_URL = "http://finance.yahoo.com/lookup?s="; private final String SYMBOL_LOOKUP_PATTERN = "a href=\"/q?s="; }