f5785c2726527178cb515fa8a73f0c93c337a1d6
[org.ibex.core.git] / src / org / xwt / XWT.java
1 // FIXME
2 // Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
3 package org.xwt;
4
5 import java.io.*;
6 import java.net.*;
7 import java.text.*;
8 import java.util.*;
9 import org.xwt.js.*;
10 import org.xwt.util.*;
11 import org.xwt.translators.*;
12 import org.bouncycastle.util.encoders.Base64;
13
14 /** Singleton class that provides all functionality in the xwt.* namespace */
15 public final class XWT extends JS.Cloneable {
16
17     private final JS rr;
18     public XWT(Stream rr) { this.rr = bless(rr); }
19
20     private Cache subCache = new Cache(20);
21     private Sub getSub(String s) {
22         Sub ret = (Sub)subCache.get(s);
23         if (ret == null) subCache.put(s, ret = new Sub(s));
24         return ret;
25     }
26
27     /** lets us put multi-level get/put/call keys all in the same method */
28     private class Sub extends JS {
29         String key;
30         Sub(String key) { this.key = key; }
31         public void put(Object key, Object val) throws JSExn { XWT.this.put(this.key + "." + key, val); }
32         public Object get(Object key) throws JSExn { return XWT.this.get(this.key + "." + key); }
33         public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
34             return XWT.this.callMethod(this.key, a0, a1, a2, rest, nargs);
35         }
36         public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
37             return XWT.this.callMethod(this.key + "." + method, a0, a1, a2, rest, nargs);
38         }
39     }
40
41     public Object get(Object name) throws JSExn {
42         if (name instanceof String && ((String)name).length() == 0) return rr;
43         //#switch(name)
44         case "math": return xwtMath;
45         case "string": return xwtString;
46         case "date": return METHOD;
47         case "box": return new Box();
48         case "apply": return METHOD;
49         case "graft": return METHOD;
50         case "clone": return METHOD;
51         case "bless": return METHOD;
52         case "regexp": return METHOD;
53         case "ui.font": return getSub("ui.font");
54         case "ui.font.sansserif": return Main.builtin.get("fonts/vera/Vera.ttf");
55         case "ui.font.monospace": return Main.builtin.get("fonts/vera/VeraMono.ttf");
56         case "ui.font.serif": return Main.builtin.get("fonts/vera/VeraSe.ttf");
57         case "ui": return getSub("ui");
58         case "ui.browser": return METHOD;
59         case "ui.mouse": return getSub("ui.mouse");
60         case "ui.mouse.button":
61             if (Surface.button1 && !Surface.button2 && !Surface.button3) return N(1);
62             else if (!Surface.button1 && Surface.button2 && !Surface.button3) return N(2);
63             else if (!Surface.button1 && !Surface.button2 && Surface.button3) return N(3);
64             else return ZERO;
65         case "ui.key": return getSub("ui.key");
66         case "ui.key.name": return getSub("ui.key.name");
67         case "ui.key.name.alt": return Platform.altKeyName();
68         case "ui.key.alt": return Surface.alt ? T : F;
69         case "ui.key.control": return Surface.control ? T : F;
70         case "ui.key.shift": return Surface.shift ? T : F;
71         case "ui.clipboard": return Platform.getClipBoard();
72         case "ui.maxdim": return N(Short.MAX_VALUE);
73         case "ui.screen": return getSub("ui.screen");
74         case "ui.screen.width": return N(Platform.getScreenWidth());
75         case "ui.screen.height": return N(Platform.getScreenHeight());
76         case "undocumented": return getSub("undocumented");
77         case "undocumented.initialOrigin": return Main.origin;
78         case "thread": return getSub("thread");
79         case "thread.yield": return METHOD;
80         case "thread.sleep": return METHOD;
81         case "stream": return getSub("stream");
82         case "stream.homedir": return url2res("file:" + System.getProperty("user.home"));
83         case "stream.tempdir": return url2res("file:" + System.getProperty("java.io.tempdir"));
84         case "stream.watch": return METHOD;
85         case "stream.unzip": return METHOD;
86         case "stream.uncab": return METHOD;
87         case "stream.cache": return METHOD;
88         case "stream.url": return METHOD;
89         case "stream.parse.html": return METHOD;
90         case "stream.parse.xml": return METHOD;
91         case "stream.parse.utf8": return METHOD;
92         case "net": return getSub("net");
93         case "net.rpc": return getSub("net.rpc");
94         case "net.rpc.xml": return METHOD;
95         case "net.rpc.soap": return METHOD;
96         case "log": return getSub("log");
97         case "log.debug": return METHOD;
98         case "log.info": return METHOD;
99         case "log.warn": return METHOD;
100         case "log.error": return METHOD;
101         case "crypto": return getSub("crypto");
102         case "crypto.rsa": return METHOD;
103         case "crypto.md5": return METHOD;
104         case "crypto.sha1": return METHOD;
105         case "crypto.rc4": return METHOD;
106         //#end
107         return super.get(name);
108     }
109
110     public void put(Object name, final Object value) throws JSExn {
111         //#switch(name)
112         case "thread": Scheduler.add((Scheduler.Task)value); return;
113         case "ui.clipboard": Platform.setClipBoard((String)value); return;
114         case "ui.frame":
115             Box b = (Box)value;
116             Surface s = Platform.createSurface(b,true, true);
117             if(b.get("titlebar") != null) s.setTitleBarText(JS.toString(b.get("titlebar")));
118             return;
119         case "ui.window": Platform.createSurface((Box)value, false, true); return;
120         case "undocumented.proxyAuthorization":
121             HTTP.Proxy.Authorization.authorization = value.toString();
122             HTTP.Proxy.Authorization.waitingForUser.release(); return;
123         //#end
124
125         throw new JSExn("attempted to put unknown property: xwt."+name);
126     }
127
128     public Object callMethod(Object name, Object a, Object b, Object c, Object[] rest, int nargs) throws JSExn {
129         try {
130             //#switch(name)
131             case "date": return new JSDate(a, b, c, rest, nargs);
132             case "net.rpc.soap": return new SOAP((String)a, "", (String)b, (String)c);
133             //#end
134  
135             switch (nargs) {
136                 case 0:
137                     //#switch(name)
138                     case "thread.yield": sleep(0); return null;
139                     //#end
140                     break;
141                 case 1:
142                     //#switch(name)
143                     case "clone":
144                         if (!(a instanceof JS.Cloneable)) throw new JSExn("cannot clone a " + a.getClass().getName());
145                         return ((JS.Cloneable)a).jsclone();
146                     case "bless": return bless((JS)a);
147                     case "ui.browser": Platform.newBrowserWindow((String)a); return null;
148                     case "stream.unzip": return new Stream.Zip((Stream)a);
149                     case "stream.uncab": return new Stream.Cab((Stream)a);
150                     case "stream.cache": try { return new Stream.CachedStream((Stream)a, "resources", true); }
151                                       catch (Stream.NotCacheableException e) { throw new JSExn("this resource cannot be cached"); }
152                     case "stream.url": {
153                         String url = (String)a;
154                         if (url.startsWith("http://")) return new Stream.HTTP(url);
155                         else if (url.startsWith("https://")) return new Stream.HTTP(url);
156                         else if (url.startsWith("data:")) return new Stream.ByteArray(Base64.decode(url.substring(5)), null);
157                         else if (url.startsWith("utf8:")) return new Stream.ByteArray(url.substring(5).getBytes(), null);
158                         throw new JSExn("invalid resource specifier " + url);
159                     }
160                     case "thread.sleep": sleep(JS.toInt(a)); return null;
161                     case "log.debug":   JS.log(this, a== null ? "**null**" : a.toString()); return null;
162                     case "log.info":   JS.log(this, a== null ? "**null**" : a.toString()); return null;
163                     case "log.warn":   JS.log(this, a== null ? "**null**" : a.toString()); return null;
164                     case "log.error":   JS.log(this, a== null ? "**null**" : a.toString()); return null;
165                     case "regexp": return new JSRegexp(a, null);
166                     case "rpc.xml": return new XMLRPC((String)a, "");
167                     case "crypto.rsa": /* FEATURE */ return null;
168                     case "crypto.md5": /* FEATURE */ return null;
169                     case "crypto.sha1": /* FEATURE */ return null;
170                     case "crypto.rc4": /* FEATURE */ return null;
171                     case "stream.parse.html": throw new JSExn("not implemented yet"); //return null;
172                     case "stream.parse.xml": new XMLHelper((JS)b).doParse((JS)a); return null;
173                     case "stream.parse.utf8": 
174                         //return new String(InputStreamToByteArray.convert(((Stream)a).getInputStream()));
175                     return null;
176                     //#end
177                     break;
178                 case 2:
179                     //#switch(name)
180                     case "stream.watch": return new Stream.ProgressWatcher((Stream)a, (JS)b);
181                     case "regexp": return new JSRegexp(a, b);
182                     //#end
183                     break;
184             }
185         } catch (RuntimeException e) {
186             // FIXME: maybe JSExn should take a second argument, Exception
187             Log.info(this, "xwt."+name+"() threw: " + e);
188             throw new JSExn("invalid argument for xwt object method "+name+"()");
189         }
190
191         throw new JSExn("invalid number of arguments for xwt object method "+name+"()");
192     }
193
194     public Stream url2res(String url) throws JSExn {
195         if (url.startsWith("http://")) return new Stream.HTTP(url);
196         else if (url.startsWith("https://")) return new Stream.HTTP(url);
197         else if (url.startsWith("data:")) return new Stream.ByteArray(Base64.decode(url.substring(5)), null);
198         else if (url.startsWith("utf8:")) return new Stream.ByteArray(url.substring(5).getBytes(), null);
199         else throw new JSExn("invalid resource specifier " + url);
200         // FIXME support file:// via dialog boxes
201     }
202
203     public static void sleep(final int i) throws JSExn {
204         try {
205             final JS.UnpauseCallback callback = JS.pause();
206             final long currentTime = System.currentTimeMillis();
207             new Thread() { public void run() {
208                 try { Thread.sleep(i); } catch (InterruptedException e) { }
209                 Scheduler.add(callback);
210             } }.start();
211         } catch (JS.NotPauseableException npe) {
212             throw new JSExn("you cannot sleep or yield in the foreground thread");
213         }
214     }
215     
216     public static final JSMath xwtMath = new JSMath() {
217             private JS gs = new JSScope.Global();
218             public String toString() { return "XWTMATH"; }
219             public Object get(Object key) throws JSExn {
220                 //#switch(key)
221                 case "isNaN": return gs.get("isNaN");
222                 case "isFinite": return gs.get("isFinite");
223                 case "NaN": return gs.get("NaN");
224                 case "Infinity": return gs.get("Infinity");
225                 //#end
226                 return super.get(key);
227             }
228         };
229
230     public static final JS xwtString = new JS() {
231             private JS gs = new JSScope.Global();
232             public void put(Object key, Object val) { }
233             public Object get(Object key) throws JSExn {
234                 //#switch(key)
235                 case "parseInt": return gs.get("parseInt");
236                 case "parseFloat": return gs.get("parseFloat");
237                 case "decodeURI": return gs.get("decodeURI");
238                 case "decodeURIComponent": return gs.get("decodeURIComponent");
239                 case "encodeURI": return gs.get("encodeURI");
240                 case "encodeURIComponent": return gs.get("encodeURIComponent");
241                 case "escape": return gs.get("escape");
242                 case "unescape": return gs.get("unescape");
243                 case "fromCharCode": return gs.get("stringFromCharCode");
244                 //#end
245                 return null;
246             }
247         };
248
249     private class XMLHelper extends XML {
250         private JS characters, whitespace, endElement, startElement;
251         public XMLHelper(JS b) throws JSExn {
252             super(BUFFER_SIZE);
253             // FIXME: trigger traps?
254             startElement = (JS)b.get("startElement");
255             endElement = (JS)b.get("endElement");
256             characters = (JS)b.get("characters");
257             whitespace = (JS)b.get("whitespace");
258         }
259         private class XMLJSWrapper extends XML.Exn {
260             public JSExn wrapee; public XMLJSWrapper(JSExn jse) { super(""); wrapee = jse; } }
261         public void startElement(XML.Element c) throws XML.Exn {
262             try {
263                 JS attrs = new JS();
264                 for(int i=0; i<c.getAttrLen(); i++) attrs.put(c.getAttrKey(i), c.getAttrVal(i));   // FIXME attribute URIs?
265                 startElement.call(c.getLocalName(), attrs, c.getUri(), null, 3);
266             } catch (JSExn jse) {
267                 throw new XMLJSWrapper(jse);
268             }
269         }
270         public void endElement(XML.Element c) throws XML.Exn {
271             try {
272                 endElement.call(c.getLocalName(), c.getUri(), null, null, 2);
273             } catch (JSExn jse) {
274                 throw new XMLJSWrapper(jse);
275             }
276         }
277         public void characters(char[] ch, int start, int length) throws XML.Exn {
278             try {
279                 characters.call(new String(ch, start, length), null, null, null, 1);
280             } catch (JSExn jse) {
281                 throw new XMLJSWrapper(jse);
282             }
283         }
284         public void whitespace(char[] ch, int start, int length) throws XML.Exn {
285             try {
286                 whitespace.call(new String(ch, start, length), null, null, null, 1);
287             } catch (JSExn jse) {
288                 throw new XMLJSWrapper(jse);
289             }
290         }
291         public void doParse(JS s) throws JSExn {
292             try { 
293                 parse(new BufferedReader(new InputStreamReader(Stream.getInputStream(s))));
294             } catch (XMLJSWrapper e) {
295                 throw e.wrapee;
296             } catch (XML.Exn e) {
297                 throw new JSExn("error parsing XML: " + e.toString());
298             } catch (IOException e) {
299                 if (Log.on) Log.info(this, "IO Exception while reading from file");
300                 if (Log.on) Log.info(this, e);
301                 throw new JSExn("error reading from Resource");
302             }
303         }
304     }
305
306     public Blessing bless(JS b) { return new XWT.Blessing((JS.Cloneable)b, this, null, null); }
307     public static class Blessing extends JS.Clone {
308         private XWT xwt;
309         private Template t = null;
310         private Object parentkey = null;
311         private Blessing parent = null;
312         public Blessing(JS.Cloneable clonee, XWT xwt, Blessing parent, Object parentkey) {
313             super(clonee); this.xwt = xwt; this.parentkey = parentkey; this.parent = parent; }
314         public Object get(Object key) throws JSExn {
315             return key.equals("") ? ((Object)getStatic()) : (new Blessing((JS.Cloneable)clonee.get(key), xwt, this, key));
316         }
317         public InputStream getImage() throws JSExn {
318             InputStream in = null;
319             try {
320                 in = Stream.getInputStream(this);
321                 if (in != null) return in;
322             } catch (IOException e) { /* DELIBERATE */ in = null; }
323             String[] exts = new String[] { ".png", ".jpeg", ".gif" };
324             for (int i=0; i < exts.length && in == null; i++) {
325                 try {
326                     in = Stream.getInputStream(parent.get(parentkey + exts[i]));
327                     if (in != null) return in;
328                 } catch (IOException f) { in = null; }
329             }
330             return null;
331         }
332         public JSScope getStatic() throws JSExn {
333             try {
334                 // FIXME background?
335                 if (t == null) t = new Template(Stream.getInputStream(parent.get(parentkey + ".xwt")), xwt);
336                 return t.getStatic();
337             } catch (Exception e) {
338                 Log.error(this, e);
339                 return null;
340             }
341         }
342         public Object call(Object a, Object b, Object c, Object[] rest, int nargs) throws JSExn {
343             if (nargs != 1) throw new JSExn("FIXME can only call with one arg");
344             getStatic();
345             t.apply((Box)a);
346             return a;
347         }
348     }
349
350 }