0001: /*
0002: *
0003: * Copyright (c) 2007, Sun Microsystems, Inc.
0004: *
0005: * All rights reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * * Redistributions of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: * * Redistributions in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in the
0015: * documentation and/or other materials provided with the distribution.
0016: * * Neither the name of Sun Microsystems nor the names of its contributors
0017: * may be used to endorse or promote products derived from this software
0018: * without specific prior written permission.
0019: *
0020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0031: */
0032: package example.stock;
0033:
0034: import java.io.*;
0035:
0036: import java.util.Timer;
0037: import java.util.TimerTask;
0038:
0039: import javax.microedition.io.*;
0040: import javax.microedition.lcdui.*;
0041: import javax.microedition.midlet.*;
0042: import javax.microedition.rms.*;
0043:
0044: /**
0045: * <p>The MIDlet application class that we'll run for the Stock Demo.</p>
0046: *
0047: */
0048: public class StockMIDlet extends MIDlet {
0049: /**
0050: * Since there is no support in MIDP/CLDC for floating point numbers,
0051: * and all of the stock data comes in the form ##.### (usually) then we
0052: * have to find a way to store the decimals as well as the integer part
0053: * of the quote data. Multiplying by the OFFSET will shift the decimal
0054: * place far enough over to make the number an integer which we can
0055: * store. Every time we retrieve quote data from our
0056: * <code>RecordStore</code> we
0057: * must make sure that we divide by the OFFSET to correctly display and
0058: * use the value we actually want.
0059: */
0060: private static int OFFSET = 10000; // each 0 is one decimal place
0061:
0062: /**
0063: * Back <code>Command</code>
0064: */
0065: private static final Command BACK_COMMAND = new Command("Back",
0066: Command.BACK, 0);
0067:
0068: /**
0069: * Main Menu <code>Command</code>
0070: */
0071: private static final Command MAIN_MENU_COMMAND = new Command(
0072: "Main", Command.SCREEN, 1);
0073:
0074: /**
0075: * Done <code>Command</code>
0076: */
0077: private static final Command DONE_COMMAND = new Command("Done",
0078: Command.OK, 2);
0079:
0080: /**
0081: * Set <code>Command</code>
0082: */
0083: private static final Command SET_COMMAND = new Command("Set",
0084: Command.OK, 4);
0085:
0086: /**
0087: * Exit <code>Command</code>
0088: */
0089: private static final Command EXIT_COMMAND = new Command("Exit",
0090: Command.STOP, 5);
0091:
0092: /**
0093: * Calc <code>Command</code>
0094: */
0095: private static final Command CALC_COMMAND = new Command("Calc",
0096: Command.OK, 6);
0097:
0098: /**
0099: * The <code>MIDlet</code>'s display object
0100: */
0101: Display display = null;
0102:
0103: /**
0104: * The <code>Ticker</code> that scrolls along the top of the screen
0105: */
0106: private Ticker stockTicker = null;
0107:
0108: /**
0109: * A <code>List</code> of stocks
0110: */
0111: private List choose = null;
0112:
0113: /**
0114: * The Stock Tracker menu
0115: */
0116: private List view = null;
0117:
0118: /**
0119: * The Main menu
0120: */
0121: private List menu = null;
0122:
0123: /**
0124: * The Alert menu
0125: */
0126: private List alertList = null;
0127:
0128: /**
0129: * The Settings menu
0130: */
0131: private List settingsList = null;
0132:
0133: /**
0134: * The 'What If?' <code>Form</code> upon which we enter our query data
0135: */
0136: private Form whatif = null;
0137:
0138: /**
0139: * The <code>Form</code> to display the different update intervals settings
0140: */
0141: private Form updatesForm = null;
0142:
0143: /**
0144: * Used to input a stock symbol
0145: */
0146: private TextBox stockSymbolBox = null;
0147:
0148: /**
0149: * Used to enter the price at which the user wishes to be alerted to the
0150: * stocks's value
0151: */
0152: private TextBox alertPriceBox = null;
0153:
0154: /**
0155: * The original price the user purchased the stock at, used on the What If?
0156: * <code>Form</code>
0157: */
0158: private TextField origPurchPriceField = null;
0159:
0160: /**
0161: * The number of shares the users wishes to sell, used on the What If?
0162: * <code>Form</code>
0163: */
0164: private TextField numSharesField = null;
0165:
0166: /**
0167: * The radio buttons for the update interval time
0168: */
0169: private ChoiceGroup updatesChoices = null;
0170:
0171: /**
0172: * A textual reference to the current menu that is displayed
0173: * onscreen to
0174: * allow the <code>StockCommandListener</code> to decide what
0175: * action to perform upon
0176: * execution of a command
0177: */
0178: private String currentMenu = null;
0179:
0180: /**
0181: * A reference to the stock that has been chosen from a list of stocks
0182: * onscreen. Using this we can extract the correct stock's data from
0183: * the <code>StockDatabase</code>
0184: */
0185: private String stockSymbol = null;
0186:
0187: /**
0188: * The reference to the <code>StockDatabase</code> that stores
0189: * the stock data
0190: */
0191: private StockDatabase stocks = null;
0192:
0193: /**
0194: * The reference to the <code>AlertDatabase</code> that stores the alerts
0195: */
0196: private AlertDatabase alerts = null;
0197:
0198: /**
0199: * The server from which the quotes are downloaded
0200: *
0201: * NOTE: Currently, only the quote.yahoo.com server is supported
0202: */
0203: private String quoteServerURL = "http://quote.yahoo.com/d/quotes.csv?s=";
0204:
0205: /**
0206: * The format parameter for the quote server to retrieve stock data
0207: *
0208: * NOTE: Currently, only this format is supported
0209: */
0210: private String quoteFormat = "&f=slc1wop";
0211:
0212: /**
0213: * The proxy server that must be negotiated
0214: *
0215: * NOTE: Proxy server is optional and a blank string indicates that
0216: * no proxy should be used
0217: */
0218:
0219: // Initial default but read from settings file on subsequent runs
0220: private String proxyURL = null;
0221:
0222: /**
0223: * The <code>Timer</code> object that refreshes the stock
0224: * quotes periodically
0225: */
0226: private Timer stockRefresh = null;
0227:
0228: /**
0229: * The <code>TimerTask</code> that the <code>Timer</code>
0230: * performs periodically.
0231: * It refreshes the stock quotes
0232: */
0233: private StockRefreshTask stockRefreshTask = null;
0234:
0235: /**
0236: * How often are the stocks' data updated off of the server?
0237: */
0238:
0239: // Initial default but read from settings file on subsequent runs
0240: private int refresh_interval = 900000; // 1000 = 1 second
0241: private boolean firstTime;
0242:
0243: /**
0244: * <p>Default constructor that is called by the application
0245: * manager to create a new instance of this <code>MIDlet</code> after
0246: * which the <code>MIDlet</code> enters the <code>Paused</code> state.</p>
0247: */
0248: public StockMIDlet() throws MIDletStateChangeException {
0249: synchronized (this ) {
0250: // Open the Stocks file
0251: stocks = new StockDatabase();
0252:
0253: try {
0254: stocks.open("Stocks");
0255: } catch (Exception e) {
0256: try {
0257: stocks.cleanUp("Stocks");
0258: } catch (Exception e2) {
0259: }
0260: }
0261:
0262: // Open the Alerts file
0263: alerts = new AlertDatabase();
0264:
0265: try {
0266: alerts.open("Alerts");
0267: } catch (Exception e) {
0268: try {
0269: alerts.cleanUp("Alerts");
0270: } catch (Exception e2) {
0271: }
0272: }
0273:
0274: // Create all the menus and forms
0275: origPurchPriceField = new TextField(
0276: "Original Purchase Price:", "", 5,
0277: TextField.NUMERIC);
0278: numSharesField = new TextField("Number Of Shares:", "", 9,
0279: TextField.NUMERIC);
0280:
0281: menu = new List("Stock Menu", Choice.IMPLICIT);
0282: menu.append("Stock Tracker", null);
0283: menu.append("What If?", null);
0284: menu.append("Alerts", null);
0285: menu.append("Settings", null);
0286: menu.addCommand(EXIT_COMMAND);
0287: menu.setCommandListener(new StockCommandListener());
0288: //menu.setTicker(stockTicker);
0289: whatif = new Form("What If?");
0290: //whatif.setTicker(stockTicker);
0291: whatif.append(origPurchPriceField);
0292: whatif.append(numSharesField);
0293: whatif.addCommand(BACK_COMMAND);
0294: whatif.addCommand(CALC_COMMAND);
0295: whatif.setCommandListener(new StockCommandListener());
0296:
0297: alertList = new List("Alert Menu", Choice.IMPLICIT);
0298: //alertList.setTicker(stockTicker);
0299: alertList.append("Add", null);
0300: alertList.append("Remove", null);
0301: alertList.addCommand(BACK_COMMAND);
0302: alertList.setCommandListener(new StockCommandListener());
0303:
0304: settingsList = new List("Settings", Choice.IMPLICIT);
0305: //settingsList.setTicker(stockTicker);
0306: settingsList.append("Updates", null);
0307: settingsList.append("Add Stock", null);
0308: settingsList.append("Remove Stock", null);
0309: settingsList.addCommand(BACK_COMMAND);
0310: settingsList.setCommandListener(new StockCommandListener());
0311:
0312: alertPriceBox = new TextBox("Alert me when stock reaches:",
0313: "", 9, TextField.NUMERIC);
0314: //alertPriceBox.setTicker(stockTicker);
0315: alertPriceBox.addCommand(DONE_COMMAND);
0316: alertPriceBox.addCommand(BACK_COMMAND);
0317: alertPriceBox
0318: .setCommandListener(new StockCommandListener());
0319:
0320: updatesForm = new Form("Updates");
0321: updatesChoices = new ChoiceGroup("Update Interval:",
0322: Choice.EXCLUSIVE);
0323: updatesChoices.append("Continuous", null); // will be 30 seconds
0324: updatesChoices.append("15 minutes", null); // default for JavaONE
0325: updatesChoices.append("30 minutes", null);
0326: updatesChoices.append("1 hour", null);
0327: updatesChoices.append("3 hours", null);
0328:
0329: switch (refresh_interval) {
0330: case 30000:
0331: updatesChoices.setSelectedIndex(0, true);
0332:
0333: break;
0334:
0335: case 1800000:
0336: updatesChoices.setSelectedIndex(2, true);
0337:
0338: break;
0339:
0340: case 3600000:
0341: updatesChoices.setSelectedIndex(3, true);
0342:
0343: break;
0344:
0345: case 10800000:
0346: updatesChoices.setSelectedIndex(4, true);
0347:
0348: break;
0349:
0350: case 900000:
0351: default:
0352: updatesChoices.setSelectedIndex(1, true);
0353:
0354: break;
0355: }
0356:
0357: //updatesForm.setTicker(stockTicker);
0358: updatesForm.append(updatesChoices);
0359: updatesForm.addCommand(BACK_COMMAND);
0360: updatesForm.addCommand(DONE_COMMAND);
0361: updatesForm.setCommandListener(new StockCommandListener());
0362:
0363: stockSymbolBox = new TextBox("Enter a Stock Symbol:", "",
0364: 5, TextField.ANY);
0365: //stockSymbolBox.setTicker(stockTicker);
0366: stockSymbolBox.addCommand(DONE_COMMAND);
0367: stockSymbolBox.addCommand(BACK_COMMAND);
0368: stockSymbolBox
0369: .setCommandListener(new StockCommandListener());
0370:
0371: // Open the Settings file
0372: try {
0373: RecordStore settings = RecordStore.openRecordStore(
0374: "Settings", true);
0375: refresh_interval = Integer.valueOf(
0376: new String(settings.getRecord(1))).intValue();
0377: settings.closeRecordStore();
0378:
0379: // No settings file existed
0380: } catch (Exception e) {
0381: refresh_interval = 900000;
0382: }
0383: } // synchronized
0384:
0385: firstTime = true;
0386: }
0387:
0388: /**
0389: * <p>This method is invoked when the <code>MIDlet</code>
0390: * is ready to run and
0391: * starts/resumes execution after being in the <code>Paused</code>
0392: * state. The
0393: * <code>MIDlet</code> acquires any resources it needs,
0394: * enters the
0395: * <code>Active</code> state and begins to perform its service,
0396: * which in this
0397: * case means it displays the main menu on the screen.</p>
0398: *
0399: * <p>The method proceeds like so:</p>
0400: * <li> open the <code>StockDatabase</code> and the
0401: * <code>AlertDatabase</code></li>
0402: * <li> read the settings data from the settings
0403: * <code>RecordStore</code></li>
0404: * <li> create the string to be scrolled across the <code>Ticker</code> on
0405: * the top of the screens and instantiate the Ticker object using that
0406: * string. That string will be constructed of the names and prices of
0407: * the stocks in our database</li>
0408: * <li> get and store the <code>MIDlet</code>'s
0409: * <code>Display</code> object</li>
0410: * <li> create and show the main menu</li>
0411: * <li> instantiate the <code>TimerTask</code> and <code>Timer</code> and
0412: * associate the two setting the refresh interval to the value of
0413: * the refresh_interval variable</li>
0414: *
0415: * @throws <code>MIDletStateChangeException</code> is thrown if the
0416: * <code>MIDlet</code> cannot start now but might be able to
0417: * start at a later time.
0418: * @see javax.microedition.midlet.MIDlet
0419: */
0420: public void startApp() {
0421: // Make the ticker
0422: stockTicker = new Ticker(makeTickerString());
0423: // set the ticker to all forms
0424: menu.setTicker(stockTicker);
0425: whatif.setTicker(stockTicker);
0426: alertList.setTicker(stockTicker);
0427: settingsList.setTicker(stockTicker);
0428: alertPriceBox.setTicker(stockTicker);
0429: updatesForm.setTicker(stockTicker);
0430: stockSymbolBox.setTicker(stockTicker);
0431:
0432: display = Display.getDisplay(this );
0433:
0434: if (firstTime) {
0435: mainMenu();
0436: firstTime = false;
0437: }
0438:
0439: // Set up and start the timer to refresh the stock quotes
0440: stockRefreshTask = new StockRefreshTask();
0441: stockRefresh = new Timer();
0442: stockRefresh.schedule(stockRefreshTask, 0, refresh_interval);
0443: }
0444:
0445: /**
0446: * <p>This method is invoked by the application management software when
0447: * the <code>MIDlet</code> no longer needs to be active. It is a stop
0448: * signal for the <code>MIDlet</code> upon which the <code>MIDlet</code>
0449: * should release any resources which can be re-acquired through the
0450: * <code>startApp<code> method which will be called upon re-activation of
0451: * the <code>MIDlet</code>. The <code>MIDlet</code> enters
0452: * the <code>Paused</code>
0453: * state upon completion of this method.</p>
0454: *
0455: * @see javax.microedition.midlet.MIDlet
0456: */
0457: public void pauseApp() {
0458: synchronized (this ) {
0459: // free memory used by these objects
0460: display = null;
0461: stockTicker = null;
0462: stockRefresh.cancel();
0463: stockRefresh = null;
0464: stockRefreshTask = null;
0465: } // synchronized
0466: }
0467:
0468: /**
0469: * <p>When the application management software has determined that the
0470: * <code>MIDlet</code> is no longer needed, or perhaps needs to make room
0471: * for a higher priority application in memory, is signals
0472: * the <code>MIDlet</code>
0473: * that it is a candidate to be destroyed by invoking the
0474: * <code>destroyApp(boolean)</code> method. In this case, we need to destroy
0475: * the <code>RecordEnumeration</code>s so that we don't waste their memory
0476: * and close the open <code>RecordStore</code>s. If the
0477: * <code>RecordStore</code>s
0478: * are empty, then we do not need them to be stored as
0479: * they will be recreated
0480: * on the next invokation of the <code>MIDlet</code> so
0481: * we should delete them.
0482: * At the end of our clean up, we must call <code>notifyDestroyed</code>
0483: * which will inform the application management software that we are done
0484: * cleaning up and have finished our execution and can
0485: * now be safely terminated and
0486: * enters the <code>Destroyed</code> state.</p>
0487: *
0488: * @param unconditional If true when this method is called,
0489: * the <code>MIDlet</code> must
0490: * cleanup and release all resources. If false the <code>MIDlet</code>
0491: * may throw <code>MIDletStateChangeException</code> to indicate it
0492: * does not want to be destroyed at this time.
0493: * @throws <code>MIDletStateChangeException</code>
0494: * is thrown if the <code>MIDlet</code> wishes to continue to
0495: * execute (Not enter the <code>Destroyed</code> state). This
0496: * exception is ignored if <code>unconditional</code> is equal
0497: * to true.
0498: * @see javax.microedition.midlet.MIDlet
0499: */
0500: public void destroyApp(boolean unconditional)
0501: throws MIDletStateChangeException {
0502: // If there is no criteria that will keep us from terminating
0503: if (unconditional) {
0504: synchronized (this ) {
0505: if (display == null) {
0506: // If display == null, we are not initialized and
0507: // we have nothing to destroy
0508: return;
0509: }
0510:
0511: stockRefresh.cancel();
0512:
0513: try {
0514: stocks.close();
0515: alerts.close();
0516:
0517: RecordStore settings = RecordStore.openRecordStore(
0518: "Settings", true);
0519:
0520: try {
0521: settings.setRecord(1, String.valueOf(
0522: refresh_interval).getBytes(), 0, String
0523: .valueOf(refresh_interval).length());
0524:
0525: // First time writing to the settings file
0526: } catch (RecordStoreException rse) {
0527: settings.addRecord(String.valueOf(
0528: refresh_interval).getBytes(), 0, String
0529: .valueOf(refresh_interval).length());
0530: }
0531:
0532: settings.closeRecordStore();
0533: } catch (Exception e) {
0534: // Ignore exception there is no place to report it
0535: }
0536:
0537: notifyDestroyed();
0538:
0539: // Something might make us not want to exit so check it
0540: // here before terminating
0541: } // synchronized
0542: }
0543: }
0544:
0545: /**
0546: * <p>Calculate the profits in a What If? scenario by the formula:</p>
0547: * <p><type> Profit = (CurrentPrice - OriginalPurchasePrice)
0548: * * NumberOfShares</type></p>
0549: * <p>First we retrieve the current price of the stock. Then parse the
0550: * original purchase price that the user enters to format it to an
0551: * integer. Next, retrieve the number of shares from the form that
0552: * the user filled in and then calculate the profits and display a nice
0553: * message (with the result) to the user onscreen.</p>
0554: */
0555: private void calc() {
0556: try {
0557: String s = stocks.search(stockSymbol);
0558: int currPrice = Stock.getPrice(s);
0559: int opp = Stock.makeInt(origPurchPriceField.getString());
0560: int numShares = Integer.valueOf(numSharesField.getString())
0561: .intValue();
0562: int profit = ((currPrice - opp) * numShares);
0563:
0564: Form answerForm = new Form(Stock.getName(s) + " "
0565: + Stock.getStringPrice(s));
0566: StringBuffer sb = new StringBuffer().append(
0567: "Net profit (loss) is ").append(
0568: (profit >= 0) ? "$" : "($").append(
0569: (profit >= 0) ? Stock.convert(profit)
0570: : ("-" + Stock.convert(profit))).append(
0571: (profit >= 0) ? "" : ")").append(" when selling ")
0572: .append(String.valueOf(numShares)).append(
0573: " shares at $").append(
0574: Stock.convert(currPrice)).append(
0575: " per share.");
0576: answerForm.append(sb.toString());
0577: answerForm.addCommand(BACK_COMMAND);
0578: answerForm.addCommand(MAIN_MENU_COMMAND);
0579: answerForm.setCommandListener(new StockCommandListener());
0580: display.setCurrent(answerForm);
0581: currentMenu = "AnswerForm";
0582: } catch (Exception e) {
0583: error("Calculation Failed", 2000);
0584: }
0585: }
0586:
0587: /**
0588: * <p>Set an alert for the selected stock at the specified price</p>
0589: *
0590: * @param Sprice String representation of the price of the stock that
0591: * the user would like an alert for
0592: */
0593: private void setAlert(String Sprice) {
0594: try {
0595: alerts.add((new StringBuffer().append(
0596: Stock.getName(stocks.search(stockSymbol))).append(
0597: ';').append(Stock.makeInt(Sprice))).toString());
0598: } catch (Exception e) {
0599: error("Failed to add alert", 2000);
0600: }
0601: }
0602:
0603: /**
0604: * <p>Generate a string (which concatenates all of the stock names and
0605: * prices) which will be used for the <code>Ticker</code>.</p>
0606: *
0607: * @return The ticker string which concatenates all of the stock symbols
0608: * and prices
0609: */
0610: private String makeTickerString() {
0611: // the ticker tape string
0612: StringBuffer tickerTape = new StringBuffer();
0613:
0614: try {
0615: RecordEnumeration re = stocks.enumerateRecords();
0616:
0617: while (re.hasNextElement()) {
0618: String theStock = new String(re.nextRecord());
0619: tickerTape.append(Stock.getName(theStock))
0620: .append(" @ ").append(
0621: Stock.getStringPrice(theStock)).append(
0622: " ");
0623: }
0624: } catch (Exception e) {
0625: return "Error Accessing Database";
0626: }
0627:
0628: return tickerTape.toString();
0629: }
0630:
0631: /**
0632: * <p>Display a message onscreen for a specified period of time</p>
0633: *
0634: * @param message The message to be displayed
0635: * @param time The delay before the message disappears
0636: */
0637: private void error(String message, int time) {
0638: if (!(display.getCurrent() instanceof Alert)) {
0639: Alert a = new Alert("Error", message, null, AlertType.ERROR);
0640: a.setTimeout(time);
0641: display.setCurrent(a, display.getCurrent());
0642: }
0643: }
0644:
0645: /**
0646: * <p>Check the alerts to see if any are registered for tkrSymbol at the
0647: * tkrSymbol's current price</p>
0648: *
0649: * @param tkrSymbol The name of the stock to check for alerts on
0650: */
0651: private void checkAlerts(String tkrSymbol) {
0652: try {
0653: int current_price = Stock
0654: .getPrice(stocks.search(tkrSymbol));
0655: RecordEnumeration re = alerts.enumerateRecords(tkrSymbol,
0656: current_price);
0657:
0658: while (re.hasNextElement()) {
0659: String alert_string = new String(re.nextRecord());
0660: int my_price = Integer.valueOf(
0661: alert_string.substring(alert_string
0662: .indexOf(';') + 1, alert_string
0663: .length())).intValue();
0664: Alert a = new Alert(tkrSymbol, "", null,
0665: AlertType.ALARM);
0666: StringBuffer sb = new StringBuffer().append(tkrSymbol)
0667: .append(" has reached your price point of $")
0668: .append(Stock.convert(my_price)).append(
0669: " and currently is trading at $")
0670: .append(
0671: Stock.getStringPrice(stocks
0672: .search(tkrSymbol)));
0673: a.setString(sb.toString());
0674: a.setTimeout(Alert.FOREVER);
0675: display.setCurrent(a);
0676: alerts.delete(alert_string);
0677: }
0678: } catch (RecordStoreNotOpenException rsno) {
0679: } catch (RecordStoreException rs) {
0680: }
0681: }
0682:
0683: /**
0684: * <p>Display the main menu of the program</p>
0685: */
0686: private void mainMenu() {
0687: display.setCurrent(menu);
0688: currentMenu = "Main";
0689: }
0690:
0691: /**
0692: * <p>Show the list of stocks to pick from and set the menu type to indicate
0693: * to the Listener what to do with the list choice</p>
0694: *
0695: * @param reload Indicates whether the list should be reloaded
0696: * which should
0697: * only happen if it is possible that the stocks have changed
0698: * since it was last shown
0699: * @param menuType Which menu is this list representing
0700: * @param type Type of Choice to display
0701: * @param prices Indicates whether or not to show prices on the list
0702: */
0703: private void chooseStock(boolean reload, String menuType, int type,
0704: boolean prices) {
0705: if (reload) {
0706: choose = new List("Choose Stocks", type);
0707: choose.setTicker(stockTicker);
0708: choose.addCommand(BACK_COMMAND);
0709:
0710: if (menuType.equals("RemoveStock")) {
0711: choose.addCommand(DONE_COMMAND);
0712: } else if (menuType.equals("WhatChoose")
0713: || menuType.equals("AddAlert")) {
0714: choose.addCommand(MAIN_MENU_COMMAND);
0715: }
0716:
0717: choose.setCommandListener(new StockCommandListener());
0718:
0719: try {
0720: RecordEnumeration re = stocks.enumerateRecords();
0721:
0722: while (re.hasNextElement()) {
0723: String theStock = new String(re.nextRecord());
0724:
0725: if (prices) {
0726: choose.append(Stock.getName(theStock) + " @ "
0727: + Stock.getStringPrice(theStock), null);
0728: } else {
0729: choose.append(Stock.getName(theStock), null);
0730: }
0731: }
0732: } catch (RecordStoreNotOpenException rsno) {
0733: } catch (RecordStoreException rs) {
0734: }
0735: }
0736:
0737: display.setCurrent(choose);
0738: currentMenu = menuType;
0739: }
0740:
0741: /**
0742: * <p>Display the stock selected on the View menu with all its
0743: * attributes shown (ie. Name, Price, etc.)</p>
0744: *
0745: * @param tkrSymbol The name of the stock to be deleted
0746: */
0747: private void displayStock(String tkrSymbol) {
0748: try {
0749: String theStock = stocks.search(tkrSymbol);
0750: Form stockInfo = new Form(Stock.getName(theStock));
0751: stockInfo.setTicker(stockTicker);
0752:
0753: StringBuffer sb = new StringBuffer().append(
0754: "Last Trade:\n ").append(
0755: Stock.getTime(theStock)).append("\n ")
0756: .append(Stock.getStringPrice(theStock)).append(
0757: "\nChange: ").append(
0758: Stock.getStringChange(theStock)).append(
0759: "\nHigh: ").append(
0760: Stock.getStringHigh(theStock)).append(
0761: "\nLow: ").append(
0762: Stock.getStringLow(theStock)).append(
0763: "\nOpen: ").append(
0764: Stock.getStringOpen(theStock)).append(
0765: "\nPrev: ").append(
0766: Stock.getStringPrevious(theStock));
0767: stockInfo.append(sb.toString());
0768: stockInfo.addCommand(BACK_COMMAND);
0769: stockInfo.addCommand(MAIN_MENU_COMMAND);
0770: stockInfo.setCommandListener(new StockCommandListener());
0771: display.setCurrent(stockInfo);
0772: currentMenu = "stockInfo";
0773: } catch (RecordStoreNotOpenException rsno) {
0774: error("Could not display stock. ", 2000);
0775: } catch (RecordStoreException rs) {
0776: error("Could not display stock. ", 2000);
0777: } catch (NullPointerException npe) {
0778: error("Could not display stock. ", 2000);
0779: }
0780: }
0781:
0782: /**
0783: * <p>Show the What If? form to investigate a hypothetical stock deal</p>
0784: *
0785: * @param tkrSymbol The name of the stock to perform the query with
0786: */
0787: private void whatIfForm(String tkrSymbol) {
0788: display.setCurrent(whatif);
0789: currentMenu = "WhatIfForm";
0790: stockSymbol = tkrSymbol;
0791: }
0792:
0793: /**
0794: * <p>Show the alert management menu</p>
0795: *
0796: * @param showAlert Indicate whether we should show an alert to indicate
0797: * that we have just successfully added an alert
0798: */
0799: private void alertMenu(boolean showAlert) {
0800: display.setCurrent(alertList);
0801: currentMenu = "AlertMenu";
0802:
0803: if (showAlert) {
0804: Alert a = new Alert("", "\n\n\n Saved!", null, null);
0805: a.setTimeout(2000);
0806: display.setCurrent(a, alertList);
0807: }
0808: }
0809:
0810: /**
0811: * <p>Show a list of all active alerts</p>
0812: */
0813: private void viewAlerts() {
0814: choose = new List("Current Alerts", Choice.MULTIPLE);
0815: choose.setTicker(stockTicker);
0816:
0817: try {
0818: // Get all the Alert records
0819: RecordEnumeration re = alerts.enumerateRecords("", 0);
0820:
0821: while (re.hasNextElement()) {
0822: String a = new String(re.nextRecord());
0823: String price = Stock.convert(Integer.valueOf(
0824: a.substring(a.indexOf(';') + 1, a.length()))
0825: .intValue());
0826: choose.append(a.substring(0, a.indexOf(';')) + " @ $"
0827: + price, null);
0828: }
0829: } catch (Exception e) {
0830: error("Error reading alerts", 2500);
0831: }
0832:
0833: choose.addCommand(BACK_COMMAND);
0834: choose.addCommand(DONE_COMMAND);
0835: choose.setCommandListener(new StockCommandListener());
0836: display.setCurrent(choose);
0837: currentMenu = "RemoveAlert";
0838: }
0839:
0840: /**
0841: * <p>Show the form to add an alert</p>
0842: *
0843: * @param tkrSymbol The ticker symbol of the stock we're adding an alert to
0844: */
0845: private void alertForm(String tkrSymbol) {
0846: display.setCurrent(alertPriceBox);
0847: currentMenu = "AlertForm";
0848: stockSymbol = tkrSymbol;
0849: }
0850:
0851: /**
0852: * <p>Remove the alert from our RecordStore referenced by index</p>
0853: *
0854: * @param choose_data A string with the symbol and price of the alert
0855: * to remove in it
0856: */
0857: private void removeAlert(String choose_data) {
0858: try {
0859: // Separate the symbol and price from the data
0860: String symbol = choose_data.substring(0, choose_data
0861: .indexOf('@') - 1);
0862: int sPrice = Stock
0863: .makeInt(choose_data.substring(choose_data
0864: .indexOf('@') + 3, choose_data.length()));
0865: System.out
0866: .println("Remove Alert: " + symbol + ";" + sPrice);
0867: // Remove the alert
0868: alerts.delete(symbol + ";" + sPrice);
0869: } catch (Exception e) {
0870: error("Failed to remove alert", 2000);
0871: }
0872: }
0873:
0874: /**
0875: * <p>Show the settings menu</p>
0876: *
0877: * @param showAlert Indicate whether we should show an alert to indicate
0878: * that we have just successfully saved changes to the
0879: * settings
0880: */
0881: private void settings(boolean showAlert) {
0882: display.setCurrent(settingsList);
0883: currentMenu = "Settings";
0884:
0885: if (showAlert) {
0886: Alert a = new Alert("", "\n\n\n Saved!", null, null);
0887: a.setTimeout(1500);
0888: display.setCurrent(a, settingsList);
0889: }
0890: }
0891:
0892: /**
0893: * <p>Show the updates choices</p>
0894: */
0895: private void updates() {
0896: display.setCurrent(updatesForm);
0897: currentMenu = "Updates";
0898: }
0899:
0900: /**
0901: * <p>Show the screen to add a stock</p>
0902: */
0903: private void addStock() {
0904: stockSymbolBox.setString("");
0905: display.setCurrent(stockSymbolBox);
0906: currentMenu = "AddStock";
0907: }
0908:
0909: /**
0910: * <p>Add the stock to the database</p>
0911: * <li> first contact the quote server to get the stock quote</li>
0912: * <li> if the stock doesn't not exist, alert the user and return to
0913: * the add screen</li>
0914: * <li> if the stock exists then add it to our list</li>
0915: * <li> if the addition of the stock was successful, return true
0916: * otherwise, return false</li>
0917: * <BR>
0918: * <p>This is the format returned by the quote.yahoo.com server in
0919: * the format
0920: * specified in the instance variable:</p>
0921: * <pre>
0922: * NAME TIME PRICE CHANGE LOW HIGH OPEN PREV
0923: * "SUNW","11:24AM - <b>79.0625</b>",-3.0625,"26.9375 - 106.75",80.5,82.125
0924: * </pre>
0925: * <BR>
0926: * <p>This is what is returned if the stock is not found:</p>
0927: * <pre>
0928: * "NAME" ,"N/A - <b>0.00</b>" ,N/A ,"N/A - N/A" ,N/A ,N/A
0929: * </pre>
0930: *
0931: * @return whether or not the addition of the stock was successful
0932: * @param tkrSymbol The ticker symbol of the stock to add
0933: */
0934: private boolean addNewStock(String tkrSymbol) {
0935: try {
0936: // When stocks.search() returns null, the stock doesn't yet exist
0937: if (stocks.search(tkrSymbol) == null) {
0938: try {
0939: stocks.add(getStockQuote(tkrSymbol));
0940: stockTicker.setString(makeTickerString());
0941: } catch (RecordStoreFullException rsf) {
0942: error("Database is full.", 2000);
0943:
0944: return false;
0945: } catch (RecordStoreException rs) {
0946: error("Failed to add " + tkrSymbol, 2000);
0947:
0948: return false;
0949: } catch (IOException ioe) {
0950: error("Failed to download stock quote for \""
0951: + tkrSymbol + "\"", 2000);
0952:
0953: return false;
0954: } catch (NumberFormatException nfe) {
0955: error(
0956: "\""
0957: + tkrSymbol
0958: + "\" not found on server, or invalid data "
0959: + "received from server", 2000);
0960:
0961: return false;
0962: } catch (Exception e) {
0963: error(e.toString(), 2000);
0964:
0965: return false;
0966: }
0967:
0968: // The stock already exists so we'll just update it
0969: } else {
0970: try {
0971: stocks.update(tkrSymbol, getStockQuote(tkrSymbol)
0972: .getBytes());
0973: stockTicker.setString(makeTickerString());
0974: } catch (RecordStoreFullException rsf) {
0975: error("Database is full.", 2000);
0976:
0977: return false;
0978: } catch (RecordStoreException rs) {
0979: error("Failed to update " + tkrSymbol, 2000);
0980:
0981: return false;
0982: } catch (IOException ioe) {
0983: error("Failed to download stock quote for "
0984: + tkrSymbol, 2000);
0985:
0986: return false;
0987: } catch (Exception e) {
0988: error(e.toString(), 2000);
0989:
0990: return false;
0991: }
0992: }
0993: } catch (RecordStoreException rs) {
0994: error("Error accessing database.", 2000);
0995:
0996: return false;
0997: }
0998:
0999: return true;
1000: }
1001:
1002: /**
1003: * <p>This method actually contacts the server, downloads and returns
1004: * the stock quote.</p>
1005: *
1006: * NOTE: If PROXY support is added to HttpConnection then switch the code
1007: * over to that as it will be pure MIDP.
1008: *
1009: * @return the stock quote
1010: * @param tkrSymbol The Stock to be requested from the server
1011: * @throws <code>IOException</code> is thrown if there is a problem
1012: * negotiating a connection with the server
1013: * @throws <code>NumberFormatException</code> is thrown if trashed data
1014: * is received from the server (or the Stock could not be found)
1015: */
1016: private String getStockQuote(String tkrSymbol) throws IOException,
1017: NumberFormatException {
1018: String quoteURL = quoteServerURL + tkrSymbol + quoteFormat;
1019:
1020: StreamConnection c = (StreamConnection) Connector.open(
1021: quoteURL, Connector.READ_WRITE);
1022: InputStream is = c.openInputStream();
1023: int ch;
1024: StringBuffer sb = new StringBuffer();
1025:
1026: while ((ch = is.read()) != -1) {
1027: sb.append((char) ch);
1028: }
1029:
1030: Stock.parse(sb.toString());
1031: is.close();
1032: c.close();
1033:
1034: return sb.toString();
1035: }
1036:
1037: /**
1038: * <p>Remove the stock from the <code>StockDatabase</code></p>
1039: *
1040: * @param tkrSymbol The ticker symbol of the stock to delete
1041: */
1042: private void deleteStock(String tkrSymbol) {
1043: try {
1044: stocks.delete(tkrSymbol);
1045: alerts.removeUselessAlerts(tkrSymbol);
1046: stockTicker.setString(makeTickerString());
1047: } catch (RecordStoreException rs) {
1048: error("Failed to delete " + tkrSymbol, 2000);
1049: }
1050: }
1051:
1052: /**
1053: * <p>This class is the Listener for ALL of the events that
1054: * take place during
1055: * life span of the <code>MIDlet</code>. It handles <code>Command</code>
1056: * events and list selections which are the only events that this
1057: * <code>MIDlet</code> will generate. In order to determine what to do,
1058: * the Listener checks the name of the command and the currently displayed
1059: * menu/list and matches them up to execute the appropriate action.</p>
1060: * <BR>
1061: * NOTE: The parseCommandString(String) is only there because the getXXX
1062: * methods are missing from the Command class. When they are added,
1063: * this method can be removed and the Command.getLabel() can be used
1064: */
1065: private class StockCommandListener implements CommandListener,
1066: Runnable {
1067: /** Current command to process. */
1068: private Command currentCommand;
1069:
1070: /** Displayable of the current command to process. */
1071: private Displayable currentDisplayable;
1072:
1073: /** The current command processing thread. */
1074: private Thread commandThread;
1075:
1076: /**
1077: * <p>The method to determine what action to take</p>
1078: *
1079: * @param c The <code>Command</code> object that has been activated
1080: * @param d The <code>Displayable</code> object that the command was
1081: * associated with
1082: */
1083: public void commandAction(Command c, Displayable d) {
1084: synchronized (this ) {
1085: if (commandThread != null) {
1086: // process only one command at a time
1087: return;
1088: }
1089:
1090: currentCommand = c;
1091: currentDisplayable = d;
1092: commandThread = new Thread(this );
1093: commandThread.start();
1094: }
1095: }
1096:
1097: /**
1098: * Perform the current command set by the method commandAction.
1099: */
1100: public void run() {
1101: String type = currentCommand.getLabel();
1102:
1103: // Main command executed, always show Main Menu regardless of
1104: // which screen is showing
1105: if (type.equals("Main")) {
1106: mainMenu();
1107:
1108: // Back command executed, check which screen is and move to
1109: // the previous one
1110: } else if (type.equals("Back")) {
1111: // Screens off the Main Menu
1112: if (currentMenu.equals("View")
1113: || currentMenu.equals("WhatChoose")
1114: || currentMenu.equals("AlertMenu")
1115: || currentMenu.equals("Settings")) {
1116: mainMenu();
1117:
1118: // Screens off the Settings menu
1119: } else if (currentMenu.equals("Add")
1120: || currentMenu.equals("Updates")
1121: || currentMenu.equals("RemoveStock")) {
1122: settings(false);
1123: } else if (currentMenu.equals("stockInfo")) {
1124: chooseStock(false, "View", Choice.IMPLICIT, true);
1125: } else if (currentMenu.equals("WhatIfForm")) {
1126: chooseStock(false, "WhatChoose", Choice.IMPLICIT,
1127: false);
1128: } else if (currentMenu.equals("AlertForm")) {
1129: chooseStock(false, "AddAlert", Choice.IMPLICIT,
1130: false);
1131: } else if (currentMenu.equals("AnswerForm")) {
1132: whatIfForm(stockSymbol);
1133:
1134: // Screens off the Alerts menu
1135: } else if (currentMenu.equals("RemoveAlert")
1136: || currentMenu.equals("AddAlert")) {
1137: alertMenu(false);
1138: } else if (currentMenu.equals("AddStock")) {
1139: settings(false);
1140: }
1141:
1142: /*
1143: * OK command executed, perform different actions
1144: * depending on which screen is showing
1145: */
1146: } else if (type.equals("Done")) {
1147: if (currentMenu.equals("AddStock")) {
1148: if ((!stockSymbolBox.getString().trim().equals(""))
1149: && (addNewStock(stockSymbolBox.getString()
1150: .trim()))) {
1151: settings(true);
1152: }
1153: } else if (currentMenu.equals("AlertForm")) {
1154: setAlert(((TextBox) currentDisplayable).getString());
1155: alertMenu(true);
1156:
1157: // Remove an alert
1158: } else if (currentMenu.equals("RemoveAlert")) {
1159: boolean[] chosen = new boolean[choose.size()];
1160: choose.getSelectedFlags(chosen);
1161:
1162: for (int i = 0; i < chosen.length; i++) {
1163: if (chosen[i]) {
1164: removeAlert(choose.getString(i));
1165: }
1166: }
1167:
1168: alertMenu(true);
1169:
1170: // Remove a Stock
1171: } else if (currentMenu.equals("RemoveStock")) {
1172: boolean[] chosen = new boolean[choose.size()];
1173: choose.getSelectedFlags(chosen);
1174:
1175: for (int i = 0; i < chosen.length; i++) {
1176: if (chosen[i]) {
1177: deleteStock(choose.getString(i));
1178: }
1179: }
1180:
1181: chosen = null;
1182: settings(true);
1183:
1184: // Set the quote update interval
1185: } else if (currentMenu.equals("Updates")) {
1186: switch (updatesChoices.getSelectedIndex()) {
1187: case 0:
1188: refresh_interval = 30000;
1189:
1190: break;
1191:
1192: case 1:
1193: refresh_interval = 900000;
1194:
1195: break;
1196:
1197: case 2:
1198: refresh_interval = 1800000;
1199:
1200: break;
1201:
1202: case 3:
1203: refresh_interval = 3600000;
1204:
1205: break;
1206:
1207: case 4:
1208: refresh_interval = 10800000;
1209:
1210: break;
1211:
1212: default:
1213: break;
1214: }
1215:
1216: stockRefreshTask.cancel();
1217: stockRefreshTask = new StockRefreshTask();
1218: stockRefresh.schedule(stockRefreshTask, 0,
1219: refresh_interval);
1220: settings(true);
1221: }
1222:
1223: // Exit command executed
1224: } else if (type.equals("Exit")) {
1225: try {
1226: destroyApp(true);
1227: } catch (MIDletStateChangeException msce) {
1228: mainMenu();
1229: }
1230:
1231: // Calc command executed
1232: } else if (type.equals("Calc")) {
1233: if (origPurchPriceField.size() == 0) {
1234: error("You must enter the price you originally "
1235: + "purchased the stock at.", 2000);
1236: } else {
1237: if (numSharesField.size() == 0) {
1238: error("You must specify the number of shares"
1239: + " to calculate with.", 2000);
1240: } else {
1241: calc();
1242: }
1243: }
1244:
1245: /*
1246: * No command button was pressed but a list selection
1247: * was made
1248: */
1249: } else {
1250: List shown = (List) display.getCurrent();
1251:
1252: /*
1253: * if it's a menu not a list of stocks then we'll
1254: * use a switch to select which action to perform
1255: */
1256: if (currentMenu.equals("Main")
1257: || currentMenu.equals("Settings")
1258: || currentMenu.equals("AlertMenu")) {
1259: switch (shown.getSelectedIndex()) {
1260: case 0:
1261:
1262: // View Stocks
1263: if (currentMenu.equals("Main")) {
1264: chooseStock(true, "View", Choice.IMPLICIT,
1265: true);
1266:
1267: // Updates
1268: } else if (currentMenu.equals("Settings")) {
1269: updates();
1270:
1271: // Add Alert
1272: } else {
1273: chooseStock(true, "AddAlert",
1274: Choice.IMPLICIT, false);
1275: }
1276:
1277: break;
1278:
1279: case 1:
1280:
1281: // What If?
1282: if (currentMenu.equals("Main")) {
1283: chooseStock(true, "WhatChoose",
1284: Choice.IMPLICIT, false);
1285:
1286: // Add Stock
1287: } else if (currentMenu.equals("Settings")) {
1288: addStock();
1289:
1290: // Remove Alert
1291: } else {
1292: viewAlerts();
1293: }
1294:
1295: break;
1296:
1297: case 2:
1298:
1299: // Alerts
1300: if (currentMenu.equals("Main")) {
1301: alertMenu(false);
1302:
1303: // Remove Stock
1304: } else if (currentMenu.equals("Settings")) {
1305: chooseStock(true, "RemoveStock",
1306: Choice.MULTIPLE, false);
1307: }
1308:
1309: break;
1310:
1311: case 3:
1312:
1313: // Settings
1314: if (currentMenu.equals("Main")) {
1315: settings(false);
1316: }
1317:
1318: break;
1319:
1320: default:
1321: break;
1322: }
1323:
1324: /*
1325: * we've now determined that it is a menu of stocks
1326: * so we have to either show the stock info (from the
1327: * View menu), add an alert (if Alert Choose screen is
1328: * showing) or perform a What If? (if the What If?
1329: * Choose screen is showing)
1330: */
1331: } else {
1332: if (currentMenu.equals("View")) {
1333: displayStock(choose.getString(
1334: choose.getSelectedIndex()).substring(
1335: 0,
1336: choose.getString(
1337: choose.getSelectedIndex())
1338: .indexOf('@') - 1));
1339: } else if (currentMenu.equals("WhatChoose")) {
1340: if (choose.getSelectedIndex() >= 0) {
1341: whatIfForm(choose.getString(choose
1342: .getSelectedIndex()));
1343: }
1344: } else if (currentMenu.equals("AddAlert")) {
1345: if (choose.getSelectedIndex() >= 0) {
1346: alertForm(choose.getString(choose
1347: .getSelectedIndex()));
1348: }
1349: }
1350: }
1351: }
1352:
1353: synchronized (this ) {
1354: // signal that another command can be processed
1355: commandThread = null;
1356: }
1357: }
1358: }
1359:
1360: /**
1361: * <p>This is an extension of the <code>TimerTask</code> class which runs
1362: * when called by <code>Timer</code>. It refreshes the stock info for
1363: * each stock from the quote server and checks to see if any of the alerts
1364: * should be fired.</p>
1365: *
1366: * @see java.util.TimerTask
1367: */
1368: private class StockRefreshTask extends TimerTask {
1369: /**
1370: * <p>Execute the Timer's Task</p>
1371: */
1372: public void run() {
1373: try {
1374: // Just return if the database is empty
1375: if (stocks.getNumRecords() == 0) {
1376: return;
1377: }
1378:
1379: // Get all the records
1380: RecordEnumeration re = stocks.enumerateRecords();
1381:
1382: while (re.hasNextElement()) {
1383: String tkrSymbol = Stock.getName(new String(re
1384: .nextRecord()));
1385:
1386: try {
1387: byte[] rec = getStockQuote(tkrSymbol)
1388: .getBytes();
1389:
1390: // Update the record and check for any alerts that
1391: // may need to be executed
1392: stocks.update(tkrSymbol, rec);
1393: checkAlerts(tkrSymbol);
1394: } catch (NumberFormatException nfe) {
1395: error(
1396: "\""
1397: + tkrSymbol
1398: + "\" not found on server, or invalid data "
1399: + "received from server", 2000);
1400: }
1401: }
1402: } catch (Exception e) {
1403: error("Update Failed\n\nStocks were not updated", 2000);
1404: }
1405: }
1406: }
1407: }
|