001: /*
002: * Copyright (c) 2007, Sun Microsystems, Inc.
003: *
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * * Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: * * Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: * * Neither the name of Sun Microsystems, Inc. nor the names of its
017: * contributors may be used to endorse or promote products derived
018: * from this software without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
023: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
024: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package example.mmademo;
033:
034: import java.io.*;
035: import java.util.*;
036: import javax.microedition.midlet.*;
037: import javax.microedition.lcdui.*;
038: import javax.microedition.io.*;
039: import javax.microedition.rms.*;
040: import javax.microedition.media.*;
041:
042: /**
043: * An http link browser. Used in SimplePlayer
044: *
045: * @version 1.2
046: */
047: public class SimpleHttpBrowser extends List implements CommandListener,
048: Utils.ContentHandler, Utils.QueryListener {
049:
050: private static final boolean MASS_TEST = false;
051:
052: private Command exitCommand = new Command("Exit Browser",
053: Command.EXIT, 1);
054: private Command backCommand = new Command("Back", Command.BACK, 1);
055: private Command openCommand = new Command("Open", Command.ITEM, 1);
056: private Command selectCommand = new Command("Select", Command.ITEM,
057: 1);
058: private Command saveCommand = new Command("Save in RMS",
059: Command.ITEM, 1);
060: private Command menuCommand = new Command("Menu", Command.ITEM, 1);
061: private Command refreshCommand = new Command("Refresh",
062: Command.ITEM, 1);
063:
064: private Command allCommands[] = { exitCommand, backCommand,
065: openCommand, selectCommand, saveCommand, menuCommand,
066: refreshCommand };
067:
068: private Vector names = new Vector();
069: private Vector urls = new Vector();
070: private Stack history = new Stack();
071: private Stack historyIndex = new Stack();
072: private String currURL = null;
073:
074: // "global" variables for user queries
075: private String queryDefault;
076: private String queryInputURL;
077:
078: private Utils.BreadCrumbTrail parent;
079:
080: private List menuList;
081:
082: public SimpleHttpBrowser(String title, Utils.BreadCrumbTrail parent) {
083: super (title, Choice.IMPLICIT);
084: this .parent = parent;
085: }
086:
087: public void displayHTML(String url, int selectedIndex) {
088: boolean error = false;
089: currURL = url;
090: clearLists();
091: try {
092: readHTML(url);
093: } catch (Exception e) {
094: append("[" + Utils.friendlyException(e) + "]", null);
095: error = true;
096: }
097: addCommand(backCommand);
098: setCommandListener(this );
099: if (!error) {
100: for (int i = 0; i < names.size(); i++) {
101: append((String) names.elementAt(i), null);
102: }
103: setListIndex(selectedIndex);
104: addCommand(menuCommand);
105: } else {
106: addCommand(refreshCommand);
107: }
108: if (MASS_TEST) {
109: massTest();
110: }
111: }
112:
113: private void exit() {
114: currURL = null;
115: clearLists();
116: parent.goBack();
117: }
118:
119: private static void internalError(Throwable t, String desc) {
120: if (Utils.DEBUG)
121: if (desc != "")
122: System.out.println(desc);
123: if (Utils.DEBUG)
124: t.printStackTrace();
125: }
126:
127: private void clearLists() {
128: names.setSize(0);
129: urls.setSize(0);
130: for (int i = size() - 1; i >= 0; i--) {
131: delete(i);
132: }
133: // remove commands, just to be sure that they
134: // don't appear erroneously
135: removeCommand(refreshCommand);
136: removeCommand(menuCommand);
137: // good moment to collect garbage
138: System.gc();
139: }
140:
141: // interface Utils.ContentHandler
142: public void close() {
143: clearLists();
144: history.setSize(0);
145: historyIndex.setSize(0);
146: }
147:
148: // interface Utils.ContentHandler
149: public boolean canHandle(String url) {
150: return isHTML(url);
151: }
152:
153: // interface Utils.ContentHandler
154: public void handle(String name, String url) {
155: Utils.debugOut("SimpleHttpBrowser: handle " + url);
156: displayHTML(url, 0);
157: }
158:
159: /**
160: * Respond to commands
161: */
162: public void commandAction(Command c, Displayable s) {
163: try {
164: // respond to menu items
165: if ((s == menuList) && (menuList != null)
166: && menuList.isShown()) {
167: int selIndex = menuList.getSelectedIndex();
168: parent.goBack();
169: if (c == backCommand) {
170: return;
171: }
172: if (c == List.SELECT_COMMAND || c == selectCommand) {
173: c = menuItem2Command(selIndex);
174: // fall through - the commands will then be handled as if
175: // they came from the main list
176: }
177: }
178: if ((c == List.SELECT_COMMAND && isShown())
179: || (c == openCommand)) {
180: gotoURL(getSelectedIndex());
181: } else if (c == saveCommand) {
182: // save to RMS must be called in a different thread !
183: saveToRms((String) urls.elementAt(getSelectedIndex()));
184: } else if (c == backCommand) {
185: goBack();
186: } else if (c == refreshCommand) {
187: refresh();
188: } else if (c == menuCommand) {
189: showMenu(getSelectedIndex());
190: } else if (c == exitCommand) {
191: exit();
192: }
193: } catch (Throwable t) {
194: internalError(t, "in commandAction");
195: }
196: }
197:
198: private Command menuItem2Command(int index) {
199: // go through all commands and test if their label
200: // matches this menuList item's string
201: if ((index < 0) || (index >= menuList.size())) {
202: return null;
203: }
204: String menuStr = menuList.getString(index);
205: for (int i = 0; i < allCommands.length; i++) {
206: if (allCommands[i].getLabel().equals(menuStr)) {
207: return allCommands[i];
208: }
209: }
210: return null;
211: }
212:
213: private void showMenu(int index) {
214: String url = (String) urls.elementAt(index);
215: boolean html = isHTML(url);
216: menuList = new List("Menu", Choice.IMPLICIT);
217: menuList.setCommandListener(this );
218: menuList.append(openCommand.getLabel(), null);
219: menuList.append(refreshCommand.getLabel(), null);
220: if (!html) {
221: // do not show "Save to RMS" for html/directory links
222: menuList.append(saveCommand.getLabel(), null);
223: }
224: menuList.append(exitCommand.getLabel(), null);
225: menuList.addCommand(backCommand);
226: menuList.addCommand(selectCommand);
227: parent.go(menuList);
228: }
229:
230: private void gotoURL(int index) {
231: String url = (String) urls.elementAt(index);
232: gotoURL(url, index);
233: }
234:
235: private void gotoURL(String url, int index) {
236: try {
237: if (index >= names.size() || isHTML(url)) {
238: if (currURL != null) {
239: history.push(currURL);
240: historyIndex.push(new Integer(index));
241: }
242: displayHTML(url, 0);
243: } else {
244: parent.handle((String) names.elementAt(index), url);
245: }
246: } catch (Exception e) {
247: Utils.error(e, parent);
248: }
249: if (Utils.DEBUG) {
250: Utils
251: .debugOut("SimpleHttpBrowser: after gotoURL. History contains "
252: + history.size() + " entries.");
253: for (int i = history.size() - 1; i >= 0; i--) {
254: Utils.debugOut(" " + i + ": "
255: + ((String) history.elementAt(i)));
256: }
257: }
258: }
259:
260: private void goBack() {
261: if (Utils.DEBUG) {
262: Utils
263: .debugOut("SimpleHttpBrowser: before goBack. History contains "
264: + history.size() + " entries.");
265: for (int i = history.size() - 1; i >= 0; i--) {
266: Utils.debugOut(" " + i + ": "
267: + ((String) history.elementAt(i)) + " #"
268: + ((Integer) historyIndex.elementAt(i)));
269: }
270: }
271: if (!history.empty()) {
272: String url = (String) history.pop();
273: int index = ((Integer) historyIndex.pop()).intValue();
274: displayHTML(url, index);
275: } else {
276: exit();
277: }
278: }
279:
280: private void refresh() {
281: int selIndex = getSelectedIndex();
282: Utils.debugOut("SimpleHttpBrowser.Refresh: index " + selIndex);
283: displayHTML(currURL, selIndex);
284: }
285:
286: // somehow this doesn't work if there was a screen switch before !
287: private void setListIndex(int index) {
288: if (index >= 0 && index < size()) {
289: setSelectedIndex(index, true);
290: }
291: }
292:
293: ////////////////////// interface Utils.QueryListener /////////////////////
294: public void queryOK(String text) {
295: try {
296: boolean queryAgain = true;
297: // if text is null, then queryOK was called in order
298: // to display the query for the first time.
299: if (text != null) {
300: if (text.indexOf("/") >= 0 || text.indexOf(":") >= 0) {
301: Utils.error(
302: "record store name cannot contain / or :",
303: parent);
304: } else if (text.length() > 32) {
305: Utils
306: .error(
307: "record store name cannot exceed 32 characters",
308: parent);
309: } else if (text.length() == 0) {
310: Utils
311: .error(
312: "record store name empty. please try again",
313: parent);
314: } else {
315: queryAgain = false;
316: }
317: }
318: if (queryAgain) {
319: Utils.query("Enter record store name:", queryDefault,
320: 32, this , parent);
321: } else {
322: int id = saveToRms(queryInputURL, "rms:/" + text);
323: Utils.FYI(
324: "The file was saved successfully in RMS. The record ID is "
325: + id + ".", parent);
326: }
327: } catch (Throwable t) {
328: Utils.error(t, parent);
329: }
330: }
331:
332: public void queryCancelled() {
333: // don't do anything if query is cancelled.
334: // the previous visible Displayable will be
335: // displayed automatically
336: }
337:
338: private void saveToRms(String inputURL) {
339: try {
340: String[] splitInputURL = Utils.splitURL(inputURL);
341: queryDefault = ""; // instance variable, to be used in queryOK handler
342: for (int i = 4; i > 0; i--) {
343: queryDefault = splitInputURL[i];
344: if (queryDefault != "") {
345: break;
346: }
347: }
348: queryInputURL = inputURL; // instance variable, to be used in queryOK handler
349: // call queryOK with null, so that it only displays the prompt
350: queryOK(null);
351: } catch (Throwable t) {
352: Utils.error(t, parent);
353: }
354: }
355:
356: private static int saveToRms(String inputURL, String outputURL)
357: throws IOException, RecordStoreException, Exception {
358: InputStream is = Connector.openInputStream(inputURL);
359: return SimpleRmsBrowser.saveToRecordStore(is, outputURL);
360: }
361:
362: // main parsing function
363: private void readHTML(String source) throws Exception {
364: InputStream in = Connector.openInputStream(source);
365: try {
366: String url;
367: String name;
368: String[] base = Utils.splitURL(source);
369: Utils.debugOut("readHTML: source=" + Utils.mergeURL(base));
370: while ((url = readHref(in)) != null) {
371: String[] splitU = joinURLs(base, url);
372: url = Utils.mergeURL(splitU);
373: // do not include those sort links in file listings.
374: if (splitU[4].indexOf('?') != 0) {
375: name = readHrefName(in);
376: //if (TRACE) System.out.println("Read name=\""+name+"\" with url=\""+url+"\"");
377: names.addElement(name);
378: urls.addElement(url);
379: }
380: }
381: if (names.size() == 0) {
382: throw new Exception("No links found in " + source);
383: }
384: } finally {
385: in.close();
386: }
387: }
388:
389: // URL methods
390: private static boolean isHTML(String url) {
391: try {
392: String[] sURL = Utils.splitURL(url);
393: return sURL[0].equals("http")
394: && (sURL[4] == "" // no filename part
395: || sURL[4].indexOf(".html") == sURL[4]
396: .length() - 5 || sURL[4]
397: .indexOf(".htm") == sURL[4].length() - 4);
398: } catch (Exception e) {
399: internalError(e, "isHTML()");
400: return false;
401: }
402: }
403:
404: private String[] joinURLs(String[] url, String relPath)
405: throws Exception {
406: String[] rel = Utils.splitURL(relPath);
407: String[] result = new String[6];
408: result[0] = (rel[0] == "") ? url[0] : rel[0];
409: result[1] = (rel[1] == "") ? url[1] : rel[1];
410: result[2] = (rel[2] == "") ? url[2] : rel[2];
411: if (rel[3].length() > 0) {
412: if (rel[3].charAt(0) == '/') {
413: // absolute path given
414: result[3] = rel[3];
415: } else {
416: result[3] = url[3] + '/' + rel[3];
417: }
418: } else {
419: result[3] = url[3];
420: }
421: result[4] = (rel[4] == "") ? url[4] : rel[4];
422: result[5] = (rel[5] == "") ? url[5] : rel[5];
423: return result;
424: }
425:
426: // beware: highly optimized HTML parsing code ahead !
427:
428: private boolean charEquals(char char1, char char2,
429: boolean caseSensitive) {
430: boolean equal = (char1 == char2);
431: if (!equal
432: && !caseSensitive
433: && ((char1 >= 0x41 && char1 <= 0x5A) || (char1 >= 0x41 && char1 <= 0x5A))) {
434: equal = ((char1 ^ 0x20) == char2);
435: }
436: return equal;
437: }
438:
439: private boolean skip(InputStream is, String until,
440: boolean onlyWhiteSpace, boolean caseSensitive)
441: throws Exception {
442: //if (TRACE) System.out.println("skip(is, \""+until+"\", onlyWhiteSpace="+onlyWhiteSpace+", caseSensitive="+caseSensitive+")");
443: int len = until.length();
444: int found = 0;
445: int v = is.read();
446: while (v > 0) {
447: if (v == 0) {
448: // binary data
449: throw new Exception("no html file");
450: }
451: boolean equal = charEquals((char) v, until.charAt(found),
452: caseSensitive);
453: //if (TRACE) System.out.println("Read '"+((char) v)+"' found="+found+" equal="+equal);
454: if (!equal) {
455: if (onlyWhiteSpace && v > 32) {
456: throw new Exception("incorrect data format");
457: }
458: if (found > 0) {
459: found = 0;
460: continue;
461: }
462: } else {
463: found++;
464: }
465: if (found == len) {
466: return true;
467: }
468: v = is.read();
469: }
470: return false;
471: }
472:
473: // if a character other than white space is found, it is returned
474: private int findDelimiter(InputStream is) throws Exception {
475: //if (TRACE) System.out.println("findDelimiter(is)");
476: while (true) {
477: int v = is.read();
478: if (v == -1) {
479: return -1;
480: }
481: if (v == 0) {
482: // binary data
483: throw new Exception("no html file");
484: }
485: if (v > 32) {
486: return v;
487: }
488: }
489: }
490:
491: private String readString(InputStream is, char firstChar,
492: String delim, boolean delimCaseSensitive) throws Exception {
493: //if (TRACE) System.out.println(">readString(is, "
494: // +firstChar+", delim=\""
495: // +delim+"\", delimCaseSensitive="+delimCaseSensitive+")");
496: StringBuffer sb = new StringBuffer();
497: boolean lastWhiteSpace = false;
498: if (firstChar != 0) {
499: sb.append(firstChar);
500: lastWhiteSpace = (firstChar <= 32);
501: }
502: int v;
503: boolean inTag = false;
504: int found = 0;
505: int len = delim.length();
506: int appendedInDelim = 0;
507: while (true) {
508: v = is.read();
509: if (v == -1) {
510: throw new Exception("unterminated string");
511: }
512: if (v <= 32) {
513: // whitespace
514: if (lastWhiteSpace) {
515: continue;
516: }
517: v = 32;
518: lastWhiteSpace = true;
519: } else {
520: lastWhiteSpace = false;
521: if (v == '<') {
522: inTag = true;
523: }
524: }
525: boolean equal = charEquals((char) v, delim.charAt(found),
526: delimCaseSensitive);
527: //if (TRACE) System.out.println("ReadString '"+((char) v)+"' found="+found+" equal="+equal);
528: if (!equal) {
529: if (found > 0) {
530: found = 0;
531: appendedInDelim = 0;
532: equal = charEquals((char) v, delim.charAt(found),
533: delimCaseSensitive);
534: }
535: }
536: if (equal) {
537: found++;
538: if (found == len) {
539: if (appendedInDelim > 0) {
540: sb.setLength(sb.length() - appendedInDelim);
541: }
542: break;
543: }
544: }
545: if (!inTag) {
546: sb.append((char) v);
547: // when we are inside the delimiter, we want to get rid of the delimiter later
548: // so track it
549: if (found > 0) {
550: appendedInDelim++;
551: }
552: } else if (v == '>') {
553: inTag = false;
554: }
555: }
556: //if (TRACE) System.out.println("<readString()=\""+sb.toString()+"\"");
557: return sb.toString();
558: }
559:
560: /**
561: * Simplified parser to find xyz of a <a href="xyz">blablabla</a> statement
562: */
563: private String readHref(InputStream is) throws Exception {
564: //if (TRACE) System.out.println(">readHref()");
565: // first skip everything until "<a"
566: if (!skip(is, "<a", false, false)) {
567: return null;
568: }
569: // read "href"
570: if (!skip(is, "href", false, false)) {
571: return null;
572: }
573: // read until "="
574: if (!skip(is, "=", true, true)) {
575: return null;
576: }
577: // wait for " or ' or nothing
578: int delim = findDelimiter(is);
579: char endDelim = (char) delim;
580: char firstChar = 0;
581: if (delim != '"' && delim != '\'') {
582: // url not enclosed in quotes
583: endDelim = '>';
584: firstChar = (char) delim;
585: }
586: String ret = readString(is, firstChar, "" + endDelim, true);
587: if (firstChar == 0) {
588: if (!skip(is, ">", true, true)) {
589: return null;
590: }
591: }
592: //if (TRACE) System.out.println("<readHref()="+ret);
593: return ret;
594: }
595:
596: /**
597: * Simplified parser to find blabla of a <a href="xyz">blablabla</a> statement
598: */
599: private String readHrefName(InputStream is) throws Exception {
600: // the stream is at first char after >. We just read the string until we find "</a>"
601: String ret = readString(is, (char) 0, "</a>", false);
602: return ret;
603: }
604:
605: // debugging: open all files in current directory
606: private void massTest() {
607: if (MASS_TEST) {
608: String name = "";
609: for (int i = 0; i < urls.size(); i++) {
610: try {
611: String url = (String) urls.elementAt(i);
612: name = (String) names.elementAt(i);
613: if (!isHTML(url)) {
614: System.out.print(name + "...");
615: Player p = Manager.createPlayer(url);
616: try {
617: System.out.print("realize...");
618: p.realize();
619: System.out.print("prefetch...");
620: p.prefetch();
621: } finally {
622: System.out.print("close...");
623: p.close();
624: System.out.print("deallocate...");
625: p.deallocate();
626: System.out.println("done");
627: }
628: }
629: } catch (IOException ioe) {
630: System.out.println("IOException: " + name);
631: System.out.println(ioe.toString());
632: } catch (MediaException me) {
633: System.out.println("MediaException: " + name);
634: System.out.println(me.toString());
635: } catch (Throwable t) {
636: System.out.println("...Throwable: " + name);
637: System.out.println(t.toString());
638: }
639: }
640: }
641: }
642:
643: // for debugging
644: public String toString() {
645: return "SimpleHttpBrowser";
646: }
647: }
|