001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.shell.mac;
017:
018: import com.google.gwt.dev.shell.LowLevel;
019: import com.google.gwt.util.tools.Utility;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.io.PrintWriter;
024: import java.io.StringWriter;
025: import java.util.Stack;
026:
027: /**
028: * Various low-level helper methods for dealing with Safari.
029: *
030: * The basic rule is that any JSValue passed to Java code from native code will
031: * always be GC-protected in the native code and Java will always unprotect it
032: * when the value is finalized. It should always be stored in a JsValue object
033: * immediately to make sure it is cleaned up properly when it is no longer
034: * needed. This approach is required to avoid a race condition where the value
035: * is allocated in JNI code but could be garbage collected before Java takes
036: * ownership of the value. Java values passed into JavaScript store a GlobalRef
037: * of a WebKitDispatchAdapter or MethodDispatch objects, which are freed when
038: * the JS value is finalized.
039: */
040: public class LowLevelSaf {
041: /**
042: * Flag to enable tracking of object creation sites. Package-protected to
043: * allow JsValueSaf to use it as well.
044: */
045: static final boolean debugObjectCreation = false;
046:
047: /**
048: * Provides interface for methods to be exposed on JavaScript side.
049: */
050: public interface DispatchMethod {
051: int invoke(int execState, int jsthis , int[] jsargs);
052: }
053:
054: /**
055: * Provides interface for objects to be exposed on JavaScript side.
056: */
057: public interface DispatchObject {
058: int getField(String name);
059:
060: Object getTarget();
061:
062: void setField(String name, int value);
063: }
064:
065: private static boolean sInitialized = false;
066:
067: private static ThreadLocal<Stack<Integer>> stateStack = new ThreadLocal<Stack<Integer>>();
068:
069: public static boolean coerceToBoolean(int execState, int jsval) {
070: boolean[] rval = new boolean[1];
071: if (!_coerceToBoolean(execState, jsval, rval)) {
072: throw new RuntimeException(
073: "Failed to coerce to boolean value.");
074: }
075: return rval[0];
076: }
077:
078: public static byte coerceToByte(int execState, int jsval) {
079: double[] rval = new double[1];
080: if (!_coerceToDouble(execState, jsval, rval)) {
081: throw new RuntimeException("Failed to coerce to byte value");
082: }
083: return (byte) rval[0];
084: }
085:
086: public static char coerceToChar(int execState, int jsval) {
087: double[] rval = new double[1];
088: if (!_coerceToDouble(execState, jsval, rval)) {
089: throw new RuntimeException("Failed to coerce to char value");
090: }
091: return (char) rval[0];
092: }
093:
094: public static double coerceToDouble(int execState, int jsval) {
095: double[] rval = new double[1];
096: if (!_coerceToDouble(execState, jsval, rval)) {
097: throw new RuntimeException(
098: "Failed to coerce to double value");
099: }
100: return rval[0];
101: }
102:
103: public static float coerceToFloat(int execState, int jsval) {
104: double[] rval = new double[1];
105: if (!_coerceToDouble(execState, jsval, rval)) {
106: throw new RuntimeException(
107: "Failed to coerce to double value");
108: }
109: return (float) rval[0];
110: }
111:
112: public static int coerceToInt(int execState, int jsval) {
113: double[] rval = new double[1];
114: if (!_coerceToDouble(execState, jsval, rval)) {
115: throw new RuntimeException("Failed to coerce to int value");
116: }
117: return (int) rval[0];
118: }
119:
120: public static long coerceToLong(int execState, int jsval) {
121: double[] rval = new double[1];
122: if (!_coerceToDouble(execState, jsval, rval)) {
123: throw new RuntimeException("Failed to coerce to long value");
124: }
125: return (long) rval[0];
126: }
127:
128: public static short coerceToShort(int execState, int jsval) {
129: double[] rval = new double[1];
130: if (!_coerceToDouble(execState, jsval, rval)) {
131: throw new RuntimeException(
132: "Failed to coerce to short value");
133: }
134: return (short) rval[0];
135: }
136:
137: public static String coerceToString(int execState, int jsval) {
138: String[] rval = new String[1];
139: if (!_coerceToString(execState, jsval, rval)) {
140: throw new RuntimeException(
141: "Failed to coerce to String value");
142: }
143: return rval[0];
144: }
145:
146: public static int convertBoolean(boolean v) {
147: int[] rval = new int[1];
148: if (!_convertBoolean(v, rval)) {
149: throw new RuntimeException(
150: "Failed to convert Boolean value: "
151: + String.valueOf(v));
152: }
153: return rval[0];
154: }
155:
156: public static int convertDouble(double v) {
157: int[] rval = new int[1];
158: if (!_convertDouble(v, rval)) {
159: throw new RuntimeException(
160: "Failed to convert Double value: "
161: + String.valueOf(v));
162: }
163: return rval[0];
164: }
165:
166: public static int convertString(String v) {
167: int[] rval = new int[1];
168: if (!_convertString(v, rval)) {
169: throw new RuntimeException(
170: "Failed to convert String value: "
171: + String.valueOf(v));
172: }
173: return rval[0];
174: }
175:
176: /**
177: * Executes JavaScript code.
178: *
179: * @param execState An opaque handle to the script frame window
180: * @param code The JavaScript code to execute
181: */
182: public static void executeScript(int execState, String code) {
183: if (!_executeScript(execState, code)) {
184: throw new RuntimeException("Failed to execute script: "
185: + code);
186: }
187: }
188:
189: /**
190: * Executes JavaScript code, retaining file and line information.
191: *
192: * @param execState An opaque handle to the script frame window
193: * @param code The JavaScript code to execute
194: * @param file A file name associated with the code
195: * @param line A line number associated with the code.
196: */
197: public static void executeScriptWithInfo(int execState,
198: String code, String file, int line) {
199: if (!_executeScriptWithInfo(execState, code, file, line)) {
200: throw new RuntimeException(file + "(" + line
201: + "): Failed to execute script: " + code);
202: }
203: }
204:
205: public static void gcLock(int jsval) {
206: _gcLock(jsval);
207: }
208:
209: public static void gcUnlock(int jsval, Throwable creationStackTrace) {
210: String str = null;
211: if (debugObjectCreation) {
212: if (creationStackTrace == null) {
213: creationStackTrace = new Throwable();
214: creationStackTrace.fillInStackTrace();
215: }
216: StringWriter sWriter = new StringWriter();
217: PrintWriter pWriter = new PrintWriter(sWriter);
218: creationStackTrace.printStackTrace(pWriter);
219: str = sWriter.toString();
220: // remove the header line, keep the first 5 lines of the stack trace
221: int begin = str.indexOf("\n") + 1;
222: int nextNL = begin - 1;
223: // loop precondition: nextNL points to a newline or 0 if there is none
224: for (int i = 0; i < 5; ++i) {
225: nextNL = str.indexOf("\n", nextNL + 1);
226: if (nextNL < 0) {
227: break;
228: }
229: }
230: // loop postcondition: nextNL points to the fifth newline or is -1
231: // if there are not 5 newlines
232: if (nextNL < 0) {
233: str = str.substring(begin);
234: } else {
235: str = str.substring(begin, nextNL);
236: }
237: }
238: _gcUnlock(jsval, str);
239: }
240:
241: public static int getExecState() {
242: Stack<Integer> stack = stateStack.get();
243: if (stack == null) {
244: throw new RuntimeException(
245: "No thread local execState stack!");
246: }
247: Integer top = stack.peek();
248: return top.intValue();
249: }
250:
251: public static int getGlobalExecState(int scriptObject) {
252: int[] rval = new int[1];
253: if (!_getGlobalExecState(scriptObject, rval)) {
254: throw new RuntimeException("Failed to getGlobalExecState.");
255: }
256: return rval[0];
257: }
258:
259: public static String[] getProcessArgs() {
260: int argc = _getArgc();
261: String[] result = new String[argc];
262: for (int i = 0; i < argc; ++i) {
263: result[i] = _getArgv(i);
264: }
265: return result;
266: }
267:
268: public static native String getTypeString(int jsval);
269:
270: public static synchronized void init() {
271: // Force LowLevel initialization to load gwt-ll
272: LowLevel.init();
273: String libName = "gwt-webkit";
274: if (!sInitialized) {
275: try {
276: String installPath = Utility.getInstallPath();
277: try {
278: // try to make absolute
279: installPath = new File(installPath)
280: .getCanonicalPath();
281: } catch (IOException e) {
282: // ignore problems, failures will occur when the libs try to load
283: }
284:
285: System.load(installPath + '/'
286: + System.mapLibraryName(libName));
287: if (!_initNative(DispatchObject.class,
288: DispatchMethod.class)) {
289: throw new RuntimeException("Unable to initialize "
290: + libName);
291: }
292: } catch (UnsatisfiedLinkError e) {
293: StringBuffer sb = new StringBuffer();
294: sb.append("Unable to load required native library '"
295: + libName + "'");
296: sb.append("\n\tYour GWT installation may be corrupt");
297: System.err.println(sb.toString());
298: throw new UnsatisfiedLinkError(sb.toString());
299: }
300: sInitialized = true;
301: }
302: }
303:
304: /**
305: * Invokes a method implemented in JavaScript.
306: *
307: * @param execState an opaque handle to the script frame window
308: * @param methodName the method name on jsthis to call
309: * @param jsthis a wrapped java object as a jsval
310: * @param jsargs the arguments to pass to the method
311: * @return the result of the invocation
312: */
313: public static int invoke(int execState, int scriptObject,
314: String methodName, int jsthis , int[] jsargs) {
315: int[] rval = new int[1];
316: if (!_invoke(execState, scriptObject, methodName, jsthis ,
317: jsargs.length, jsargs, rval)) {
318: throw new RuntimeException(
319: "Failed to invoke native method: " + methodName
320: + " with " + jsargs.length + " arguments.");
321: }
322: return rval[0];
323: }
324:
325: /**
326: * @param jsval the js value in question
327: * @return <code>true</code> if the value is a boolean value
328: */
329: public static native boolean isBoolean(int jsval);
330:
331: /**
332: * @param jsval the js value in question
333: * @return <code>true</code> if the value is the null value
334: */
335: public static native boolean isNull(int jsval);
336:
337: /**
338: * @param jsval the js value in question
339: * @return <code>true</code> if the value is a boolean value
340: */
341: public static native boolean isNumber(int jsval);
342:
343: /**
344: * Is the jsval a JSObject?
345: *
346: * @param jsval the value
347: * @return true if jsval is a JSObject
348: */
349: public static boolean isObject(int jsval) {
350: return _isObject(jsval);
351: }
352:
353: /**
354: * Is the jsval a string primitive?
355: *
356: * @param jsval the value
357: * @return true if the jsval is a string primitive
358: */
359: public static boolean isString(int jsval) {
360: return _isString(jsval);
361: }
362:
363: /**
364: * @param jsval the js value in question
365: * @return <code>true</code> if the value is the undefined value
366: */
367: public static native boolean isUndefined(int jsval);
368:
369: /**
370: * Is the jsval JSObject a wrapped DispatchObject?
371: *
372: * @param jsval the value
373: * @return true if the JSObject is a wrapped DispatchObject
374: */
375: public static boolean isWrappedDispatch(int jsval) {
376: boolean[] rval = new boolean[1];
377: if (!_isWrappedDispatch(jsval, rval)) {
378: throw new RuntimeException("Failed isWrappedDispatch.");
379: }
380: return rval[0];
381: }
382:
383: /**
384: * Locks the JavaScript interpreter into this thread; prevents the garbage
385: * collector from running. DON'T CALL THIS THREAD WITHOUT PUTTING A CALL TO
386: * JSUNLOCK INSIDE OF A FINALLY BLOCK OR YOU WILL LOCK THE BROWSER.
387: */
388: public static void jsLock() {
389: _jsLock();
390: }
391:
392: /**
393: * @return the null value
394: */
395: public static native int jsNull();
396:
397: /**
398: * @return the undefined value
399: */
400: public static native int jsUndefined();
401:
402: /**
403: * Unlocks the JavaScript interpreter. Call this method from a finally block
404: * whenever you call jsLock.
405: */
406: public static void jsUnlock() {
407: _jsUnlock();
408: }
409:
410: public static void popExecState(int execState) {
411: Stack<Integer> stack = stateStack.get();
412: if (stack == null) {
413: throw new RuntimeException(
414: "No thread local execState stack!");
415: }
416: Integer old = stack.pop();
417: if (old.intValue() != execState) {
418: throw new RuntimeException(
419: "The wrong execState was popped.");
420: }
421: }
422:
423: public static void pushExecState(int execState) {
424: Stack<Integer> stack = stateStack.get();
425: if (stack == null) {
426: stack = new Stack<Integer>();
427: stateStack.set(stack);
428: }
429: stack.push(new Integer(execState));
430: }
431:
432: /**
433: * Call this to raise an exception in JavaScript before returning control.
434: *
435: * @param execState An opaque handle to the script frame window
436: */
437: public static void raiseJavaScriptException(int execState, int jsval) {
438: if (!_raiseJavaScriptException(execState, jsval)) {
439: throw new RuntimeException(
440: "Failed to raise Java Exception into JavaScript.");
441: }
442: }
443:
444: /**
445: * Unwraps a wrapped DispatchObject.
446: *
447: * @param jsval a value previously returned from wrapDispatch
448: * @return the original DispatchObject
449: */
450: public static DispatchObject unwrapDispatch(int jsval) {
451: DispatchObject[] rval = new DispatchObject[1];
452: if (!_unwrapDispatch(jsval, rval)) {
453: throw new RuntimeException("Failed to unwrapDispatch.");
454: }
455: return rval[0];
456: }
457:
458: /**
459: * @param dispObj the DispatchObject to wrap
460: * @return the wrapped object as a jsval JSObject
461: */
462: public static int wrapDispatch(DispatchObject dispObj) {
463: int[] rval = new int[1];
464: if (!_wrapDispatch(dispObj, rval)) {
465: throw new RuntimeException("Failed to wrapDispatch.");
466: }
467: return rval[0];
468: }
469:
470: /**
471: * @param name method name.
472: * @param dispMeth the DispatchMethod to wrap
473: * @return the wrapped method as a jsval JSObject
474: */
475: public static int wrapFunction(String name, DispatchMethod dispMeth) {
476: int[] rval = new int[1];
477: if (!_wrapFunction(name, dispMeth, rval)) {
478: throw new RuntimeException("Failed to wrapFunction.");
479: }
480: return rval[0];
481: }
482:
483: /**
484: * Called from native code to do tracing.
485: *
486: * @param s the string to trace
487: */
488: protected static void trace(String s) {
489: System.out.println(s);
490: System.out.flush();
491: }
492:
493: // CHECKSTYLE_NAMING_OFF
494: private static native boolean _coerceToBoolean(int execState,
495: int jsval, boolean[] rval);
496:
497: private static native boolean _coerceToDouble(int execState,
498: int jsval, double[] rval);
499:
500: private static native boolean _coerceToString(int execState,
501: int jsval, String[] rval);
502:
503: private static native boolean _convertBoolean(boolean v, int[] rval);
504:
505: private static native boolean _convertDouble(double v, int[] rval);
506:
507: private static native boolean _convertString(String v, int[] rval);
508:
509: private static native boolean _executeScript(int execState,
510: String code);
511:
512: private static native boolean _executeScriptWithInfo(int execState,
513: String newScript, String file, int line);
514:
515: private static native void _gcLock(int jsval);
516:
517: private static native void _gcUnlock(int jsval, String trace);
518:
519: private static native int _getArgc();
520:
521: private static native String _getArgv(int i);
522:
523: private static native boolean _getGlobalExecState(int scriptObject,
524: int[] rval);
525:
526: private static native boolean _initNative(
527: Class<DispatchObject> dispObjClass,
528: Class<DispatchMethod> dispMethClass);
529:
530: private static native boolean _invoke(int execState,
531: int scriptObject, String methodName, int jsthis ,
532: int jsargCount, int[] jsargs, int[] rval);
533:
534: private static native boolean _isObject(int jsval);
535:
536: private static native boolean _isString(int jsval);
537:
538: private static native boolean _isWrappedDispatch(int jsval,
539: boolean[] rval);
540:
541: private static native void _jsLock();
542:
543: private static native void _jsUnlock();
544:
545: private static native boolean _raiseJavaScriptException(
546: int execState, int jsval);
547:
548: private static native boolean _unwrapDispatch(int jsval,
549: DispatchObject[] rval);
550:
551: private static native boolean _wrapDispatch(DispatchObject dispObj,
552: int[] rval);
553:
554: private static native boolean _wrapFunction(String name,
555: DispatchMethod dispMeth, int[] rval);
556:
557: // CHECKSTYLE_NAMING_OFF
558:
559: /**
560: * Not instantiable.
561: */
562: private LowLevelSaf() {
563: }
564:
565: }
|