📄 browser.java
字号:
/* * Fire (Flexible Interface Rendering Engine) is a set of graphics widgets for creating GUIs for j2me applications. * Copyright (C) 2006-2008 Bluevibe (www.bluevibe.net) * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * *//** * */package gr.fire.browser;import gr.fire.browser.util.Cookie;import gr.fire.browser.util.Form;import gr.fire.browser.util.PageMetaData;import gr.fire.browser.util.Request;import gr.fire.browser.util.XmlPullParser;import gr.fire.core.CommandListener;import gr.fire.core.Component;import gr.fire.core.Container;import gr.fire.core.FireScreen;import gr.fire.core.Panel;import gr.fire.test.Console;import gr.fire.ui.Gauge;import gr.fire.ui.TransitionAnimation;import gr.fire.util.FireIO;import gr.fire.util.Log;import gr.fire.util.StringUtil;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.UnsupportedEncodingException;import java.util.Date;import java.util.Enumeration;import java.util.Hashtable;import java.util.Vector;import javax.microedition.io.Connector;import javax.microedition.io.HttpConnection;import javax.microedition.io.HttpsConnection;import javax.microedition.lcdui.Command;import javax.microedition.lcdui.Displayable;/** * The Browser Container parses HTML from a given stream and renders it. * It has some basic rendering rules that are choosen to improve the * readability and usability of a page when rendered for a small screen. * * * The default width of the browser is the width of the screen * * * TextElement width is set as the width of the Browser unless noted otherwise * by the style of the tag. * * * TextElement height is the height of the text calculated using the width * (according to the rules above) and the font of the text. * * * ImageElement width is the width of the image unless noted otherwise by the style of the tag. * * * @author padeler * */public class Browser implements CommandListener{ public static final String[][] header = new String[][] { {"User-Agent","Fire v2.0 alpha Mozilla/4.0 (compatible; MSIE 6.0)"}, {"Accept","text/xml, application/xhtml+xml, text/css, multipart/mixed"}, {"Accept-Language","en-us,en;q=0.5"}, {"Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7"}, {"Connection","keep-alive"} }; private Hashtable knownTags = new Hashtable(); private Console console; private Command consoleCmd; private int viewportWidth=0; private Vector savedCookies=new Vector(); /* ****** HTML Parsing support variables ******* */ private Vector tagStack = new Vector(); private Form openForm=null; private Container pageContainer=null;// the page of the browser private Request currentPage=null; private PageMetaData pageMetaData=null; public Browser() { console = new Console(); consoleCmd = new Command("Console",Command.OK,1); Log.addLogDestination(console); } public void registerTag(String name,Class cl) { if(name!=null && cl!=null) knownTags.put(name,cl); else throw new NullPointerException("Tag name and class cannot be null"); } public Container requestPage(String url,String method,Hashtable requestParameters,byte []data) throws UnsupportedEncodingException,IOException,Exception { /* ******************************** clean old page stuff here **************************** */ tagStack.removeAllElements(); pageContainer = null; openForm = null; pageMetaData = new PageMetaData(url); if(viewportWidth<=0) { viewportWidth = FireScreen.getScreen().getWidth(); } /* ********** Display a gauge *************** */ Gauge g = new Gauge(url+"...",0,0); g.showGauge(); /* ************************** Request the resource **************************** */ Request nextPage =requestResource(url,method,requestParameters,data); if(nextPage==null) { Log.logWarn("Failed to get resource from "+url); return null; } InputStream in = nextPage.getInputStream(); if (in == null) { Log.logWarn("Failed to read data from "+url); return null; } currentPage = nextPage; Log.logInfo("Base URL is: "+currentPage.getBaseURL()); InputStreamReader reader=null; try{ String encoding= currentPage.getEncoding(); Log.logInfo("Using Encoding: "+encoding); reader = new InputStreamReader(in, encoding); XmlPullParser parser = new XmlPullParser(reader, false,true); int type; /* ********** Main XML parsing loop **************** */ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT) { if(type==XmlPullParser.START_TAG) /* **** Handle Opening TAGs ***** */ { String name = parser.getName().toLowerCase();; Class tagClass = (Class)knownTags.get(name); if(tagClass!=null) { try { Tag t = (Tag)tagClass.newInstance(); t.handleTagStart(this,parser); pushTag(t); }catch(InstantiationException e) { Log.logError("Failed to instantiate a Tag class for tag name "+name+".",e); } catch (Exception e) { Log.logError("Exception while handling tag start "+name,e); } } else Log.logWarn("Unknown Opening TAG "+name); } else if(type==XmlPullParser.END_TAG) /* **** Handle Closing TAGs ***** */ { String name = parser.getName().toLowerCase();; Tag t = (Tag)topTag(); if(t!=null && name.equals(t.getName())) { t.handleTagEnd(this,parser); popTag(); } else Log.logWarn("Unknown Closing TAG "+name); } else if(type==XmlPullParser.TEXT) /* **** Handle Text inside a TAG ***** */ { Tag top = (Tag)topTag(); String txt = parser.getText(); if(top!=null && txt.length()>0) { top.handleText(top,txt); } } else /* **** Default action, just log the unknown type and continue **** */ { Log.logWarn("Unknown tag "+parser.getName() +" type " + type); } } Log.logInfo("=>End Of Document<="); }catch(Throwable e){ Log.logError("Failed to request page "+url+".",e); }finally{ if(reader!=null) reader.close(); if(currentPage!=null) currentPage.close(); } return pageContainer; } /** * When a Tag is pushed it is considered to be inside the last one pushed. * The Tag implementation is responsible for doing so. * * @param node */ private void pushTag(Tag node) { tagStack.addElement(node); } public Tag topTag() { if(tagStack.size()>0) return (Tag)tagStack.lastElement(); // else return null; } private Tag popTag() { int size = tagStack.size(); if(size>0) { Tag tc = (Tag)tagStack.lastElement(); tagStack.removeElementAt(size-1); return tc; } return null; } public Request requestResource(String url, String requestMethod, Hashtable requestProperties, byte[]data) throws IOException, Exception { if(url==null) throw new IllegalArgumentException("Request for resource to null url?"); url = StringUtil.proccessUrl(url); if(url.indexOf("://")==-1 && currentPage!=null) // url path is relevative to current. { if(url.startsWith("/")) url = currentPage.getBaseURL()+url; else { String base = currentPage.getBaseURL(); String file = currentPage.getFile(); int idx =file.lastIndexOf('/'); if(idx==-1) url = base+url; else url = base+file.substring(0,idx)+"/"+url; } } int idx = url.indexOf("://"); if(idx==-1) throw new IllegalArgumentException("URL must start with the protocol."); String protocol = url.substring(0,idx); try{ if(protocol.equals("file")) { String file = url.substring(6); InputStream in = this.getClass().getResourceAsStream(file); if(in==null) { return new Request("","file",file,Request.defaultEncoding,FireIO.read(file)); } return new Request("","file",file,Request.defaultEncoding,in); } // the resource is located remotelly. Try to retrieve it. Log.logInfo(requestMethod+" ["+url+"]"); int mode = Connector.READ; if(HttpConnection.POST.equals(requestMethod) && data!=null) mode=Connector.READ_WRITE; HttpConnection conn = (HttpConnection)Connector.open(url,mode); conn.setRequestMethod(requestMethod); for(int i=0;i<header.length;++i) { conn.setRequestProperty(header[i][0], header[i][1]); } if(requestProperties!=null) { Enumeration propKeys = requestProperties.keys(); while(propKeys.hasMoreElements()) { String key = (String)propKeys.nextElement(); String val = (String)requestProperties.get(key); conn.setRequestProperty(key,val); } } // add cookies String cookies = getCookies(conn,conn.getFile()); if(cookies.length()>0) { conn.setRequestProperty("cookie",cookies); } // add referer if(currentPage!=null && currentPage.getProtocol().equals("file")==false) { String q = currentPage.getQuery(); if(q!=null) q = "?"+q; else q=""; String referer = currentPage.getBaseURL()+currentPage.getFile()+q; conn.setRequestProperty("referer",referer); } if(mode==Connector.READ_WRITE) // exoume na grapsoume kiolas. { conn.setRequestProperty("content-length",""+data.length); OutputStream out = conn.openOutputStream(); out.write(data); } int code = conn.getResponseCode(); Log.logInfo("Responce "+code+" "+conn.getResponseMessage()); for(int i=0;i<100;++i) { String key = conn.getHeaderFieldKey(i); if(key==null) break; Log.logInfo(key+": "+conn.getHeaderField(i)); if(key.toLowerCase().equals("set-cookie")) { // the cookieStr may be more than one cookies seperated by commas. // First we split the cookies and then we parse them. // this is a bit tricky since a cookie may contain commas as well...(who thought of that delimiter anyway?) Vector cookieStrings = Cookie.splitCookies(conn.getHeaderField(i)); for(int j=0;j<cookieStrings.size();++j) saveCookie((String)cookieStrings.elementAt(j),conn.getHost()); } } if(code==HttpConnection.HTTP_OK) { return new Request(conn); } else if(code==HttpConnection.HTTP_MOVED_PERM) // 301 { String redirect = conn.getHeaderField("location"); Log.logInfo("Redirect "+code+" to Location: " +redirect); conn.close(); return requestResource(redirect,requestMethod,requestProperties,data); } else if(code==HttpConnection.HTTP_MOVED_TEMP || code==HttpConnection.HTTP_SEE_OTHER) {// must redirect using the GET method (see protocol) String redirect = conn.getHeaderField("Location"); Log.logInfo("Redirect "+code+" to Location: " +redirect); conn.close(); return requestResource(redirect,HttpConnection.GET,requestProperties,data); } // unknown responce... Log.logWarn("Server Responded with error: "+code); }catch(Throwable ex) { Log.logError("Request to "+url+" failed.",ex); } return null; } /* (non-Javadoc) * @see gr.fire.core.CommandListener#commandAction(javax.microedition.lcdui.Command, gr.fire.core.Component) */ public void commandAction(Command cmd, Component c) { if(cmd==consoleCmd){ FireScreen.getScreen().setCurrent(console); return; } String url = cmd.getLabel(); displayPage(url,HttpConnection.GET,null,null); } public void displayPage(final String url,final String method,final Hashtable requestParameters,final byte []data) { final Browser b = this; Thread th = new Thread() { public void run() { try { Container cnt = requestPage(url,method,requestParameters,data); if(cnt!=null) { PageMetaData pageMeta = pageMetaData; Log.logInfo("Loaded Page ["+pageMeta.getPageTitle()+"]"); Panel panel = new Panel(cnt,Panel.VERTICAL_SCROLLBAR|Panel.HORIZONTAL_SCROLLBAR,true); FireScreen.getScreen().setCurrent(panel,TransitionAnimation.TRANSITION_SCROLL|TransitionAnimation.TRANSITION_LEFT); panel.setLeftSoftKeyCommand(consoleCmd); panel.setCommandListener(b); if(pageMeta.getRefresh()!=null) { int seconds = pageMeta.getRefreshSeconds(); if(seconds>0) try{Thread.sleep(seconds*1000);}catch(InterruptedException e){} displayPage(pageMeta.getRefresh(),HttpConnection.GET,null,null); } } } catch (Throwable e) { Log.logError("Request to URL ["+url+"] failed",e); } } }; th.start(); } private void saveCookie(String cookieStr,String host) { try{ Cookie newCookie = new Cookie(cookieStr,host); int idx = savedCookies.indexOf(newCookie); // check to see if cookie already exists Date expires = newCookie.getExpires(); if(idx>-1) { savedCookies.removeElementAt(idx); } if(expires==null || System.currentTimeMillis()< (expires.getTime())) { savedCookies.addElement(newCookie); Log.logInfo("Saved Cookie "+newCookie.getName()); } else // expiration date is in the past. Log.logInfo("Removed (expired: "+expires+") Cookie: "+newCookie.getName()); }catch(Exception e) { Log.logWarn("Failed to save cookie: "+cookieStr,e); } } private String getCookies(HttpConnection req,String path) { String host = req.getHost(); boolean includeSecure = req instanceof HttpsConnection; String res = ""; for(int i =0 ;i<savedCookies.size();++i) { Cookie c = (Cookie)savedCookies.elementAt(i); if(c.match(host,path) && (includeSecure || !c.isSecure())) { res += c.toString()+"; "; Log.logInfo("Sending Cookie: "+c.getName()); } } return res; } /* (non-Javadoc) * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command, javax.microedition.lcdui.Displayable) */ public void commandAction(Command cmd, Displayable d) { // TODO Auto-generated method stub } public int getViewportWidth() { return viewportWidth; } public void setViewportWidth(int viewportWidth) { this.viewportWidth = viewportWidth; } public Container getPageContainer() { return pageContainer; } public void setPageContainer(Container pageContainer) { this.pageContainer = pageContainer; } public Form getOpenForm() { return openForm; } public void setOpenForm(Form openForm) { this.openForm = openForm; } public PageMetaData getPageMetaData() { return pageMetaData; } public void setPageMetaData(PageMetaData pageMetaData) { this.pageMetaData = pageMetaData; }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -