| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.html.presenters.spi; |
| |
| import com.dukescript.api.strings.Texts; |
| import java.io.Flushable; |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.lang.ref.PhantomReference; |
| import java.lang.ref.Reference; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NavigableSet; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import org.netbeans.html.boot.spi.Fn; |
| |
| abstract class Generic implements Fn.Presenter, Fn.KeepAlive, Flushable { |
| private StringBuilder msg; |
| /** @GuardedBy("this") */ |
| private int callCounter; |
| /** @GuardedBy("this") */ |
| private Item call; |
| private final NavigableSet<Exported> exported; |
| private final int key; |
| private final boolean synchronous; |
| private final boolean evalJS; |
| private final String type; |
| private final String app; |
| private final CountDownLatch initialized = new CountDownLatch(1); |
| |
| Generic( |
| boolean synchronous, boolean evalJS, String type, String app |
| ) { |
| this.exported = new TreeSet<Exported>(); |
| this.key = (int)(System.currentTimeMillis() / 777) % 1000; |
| this.synchronous = synchronous; |
| this.evalJS = evalJS; |
| this.type = type; |
| this.app = app; |
| } |
| |
| final Object lock() { |
| return initialized; |
| } |
| |
| final void log(Level level, String msg, Object... args) { |
| StringBuilder sb = this.msg; |
| if (sb != null) { |
| for (int i = 0; i < args.length; i++) { |
| String txt = args[i] == null ? "null" : args[i].toString(); |
| msg = msg.replace("{" + i + "}", txt); |
| } |
| synchronized (lock()) { |
| sb.append('[').append(level).append("] "); |
| sb.append(msg); |
| sb.append('\n'); |
| } |
| } |
| handleLog(level, msg, args); |
| } |
| abstract void handleLog(Level level, String msg, Object... args); |
| |
| @Texts({ |
| "begin=try {\n" |
| + " @1('r', -1, 'OK', 'Connected', null);\n" |
| + "} catch (e) {\n" |
| + " console.warn(e);\n" |
| + "}\n", |
| |
| "init=(function(global) {" |
| + "\n var fncns = new Array();" |
| + "\n var js2j = new Array();" |
| + "\n function jobject(id,value) {" |
| + "\n Object.defineProperty(this, 'id', { value : id });" |
| + "\n Object.defineProperty(this, 'v', { value : value });" |
| + "\n return this;" |
| + "\n };" |
| + "\n Object.defineProperty(jobject.prototype, 'native', { value : true });" |
| + "\n Object.defineProperty(jobject.prototype, 'valueOf', { value : function() { return this.v ? this.v : '[jobject ' + this.id + ']'; } });" |
| + "\n Object.defineProperty(jobject.prototype, 'toString', { value : jobject.prototype.valueOf });" |
| + "\n var toVM = global['@2'];" |
| + "\n delete global['@2'];" |
| + "\n if (typeof toVM !== 'function') {" |
| + "\n throw 'toVM should be a function: ' + toVM;" |
| + "\n }" |
| + "\n function toJava(method, id, r) {" |
| + "\n var t = typeof r;" |
| + "\n if (t === 'function') t = 'object';" |
| + "\n if (t === 'undefined' || r === null) {" |
| + "\n t = 'null';" |
| + "\n r = null;" |
| + "\n } else if (t === 'object') {" |
| + "\n if (r['native']) {" |
| + "\n t = 'java';" |
| + "\n r = r.id;" |
| + "\n } else if (Object.prototype.toString.call(r) === '[object Array]') {" |
| + "\n t = 'array';" |
| + "\n var l = r.length + ':';" |
| + "\n for (var i = 0; i < r.length; i++) {" |
| + "\n var toObj = toJava(null, id, r[i]);" |
| + "\n l += toObj.length + ':' + toObj;" |
| + "\n }" |
| + "\n r = l;" |
| + "\n } else {" |
| + "\n var size = js2j.length;" |
| + "\n js2j.push(r);" |
| + "\n r = size;" |
| + "\n }" |
| + "\n }" |
| + "\n if (method !== null) toVM(method, id, t, r, null);" |
| + "\n else return t + ':' + r;" |
| + "\n }" |
| + "\n var impl = {};" |
| + "\n impl.key = @1;" |
| + "\n global.ds = function(key) {" |
| + "\n if (key != impl.key) {" |
| + "\n impl = null;" |
| + "\n console.warn('Surprising access to Java with ' + key);" |
| + "\n }" |
| + "\n return impl;" |
| + "\n };" |
| + "\n impl.toJava = toJava;" |
| + "\n impl.rg = function(id, fn) {" |
| + "\n fncns[id] = fn;" |
| + "\n };" |
| + "\n impl.fn = function(index, n, id, self) {" |
| + "\n var args = Array.prototype.slice.call(arguments, 4);" |
| + "\n try {" |
| + "\n var fn = fncns[index];" |
| + "\n if (typeof fn !== 'function') throw 'Cannot find function at index: ' + index + ' in ' + fn + ' apply: ' + (fn ? fn.apply : undefined);" |
| + "\n var r = fn.apply(self, args);" |
| + "\n if (n) toJava('r', id, r);" |
| + "\n } catch (err) {" |
| + "\n if (typeof console !== 'undefined') console.warn('Error ' + err + ' at:\\n' + err.stack);" |
| + "\n if (n) toVM('r', id, 'error', '' + err + ' at:\\n' + err.stack, null, null);" |
| + "\n }" |
| + "\n };" |
| + "\n impl.o = function(i) {" |
| + "\n return js2j[i];" |
| + "\n };" |
| + "\n impl.j = function(n,v) {" |
| + "\n var r = new jobject(n,v);" |
| + "\n if (arguments.length > 2) {" |
| + "\n for (var i = 2; i < arguments.length; i++) {" |
| + "\n r[i - 2] = arguments[i];" |
| + "\n }" |
| + "\n r.length = arguments.length - 2;" |
| + "\n }" |
| + "\n return r;" |
| + "\n };" |
| + "\n impl.v = function(i) {" |
| + "\n return fncns[i];" |
| + "\n };" |
| + "\n impl.toVM = toVM;" |
| + "\n impl.toVM('r', -1, 'OK', 'Initialized', null);" |
| + "\n})(this);", |
| "initializationProtocol=--- Initialization protocol ---\n", |
| "error=Cannot initialize DukeScript: @1", |
| "version=$version" |
| }) |
| final void init() { |
| if (initialized.getCount() == 0) { |
| return; |
| } |
| synchronized (lock()) { |
| if (initialized.getCount() == 0) { |
| return; |
| } |
| if (msg == null) { |
| this.msg = new StringBuilder(Strings.initializationProtocol()); |
| callbackFn(new ProtoPresenterBuilder.OnPrepared() { |
| @Override |
| public void callbackIsPrepared(String clbk) { |
| log(Level.FINE, "callbackReady with {0}", clbk); |
| loadJS(Strings.begin(clbk).toString()); |
| log(Level.FINE, "checking OK state"); |
| loadJS(Strings.init(key, clbk).toString()); |
| } |
| }); |
| } |
| } |
| for (int counter = 0;; counter++) { |
| try { |
| handleLog(Level.FINE, "Awaiting as of {0}", counter); |
| if (initialized.await(10, TimeUnit.SECONDS)) { |
| handleLog(Level.FINE, "Waiting is over"); |
| return; |
| } |
| handleLog(Level.INFO, msg.toString()); |
| } catch (InterruptedException ex) { |
| handleLog(Level.INFO, "Interrupt", ex); |
| } |
| } |
| } |
| |
| /** @return the name of the callback function */ |
| abstract void callbackFn(ProtoPresenterBuilder.OnPrepared onReady); |
| abstract void loadJS(String js); |
| |
| public final String js2java(String method, |
| String a1, String a2, String a3, String a4 |
| ) throws Exception { |
| if ("r".equals(method)) { |
| result(a1, a2, a3); |
| return null; |
| } else if ("c".equals(method)) { |
| return javacall(a1, a2, a3, a4); |
| } else if ("jr".equals(method)) { |
| return javaresult(); |
| } else { |
| throw new IllegalArgumentException(method); |
| } |
| } |
| |
| abstract void dispatch(Runnable r); |
| |
| /** Makes sure all pending calls into JavaScript are immediately |
| * performed. |
| * |
| * @throws IOException if something goes wrong |
| */ |
| @Override |
| public void flush() throws IOException { |
| if (initialized.getCount() == 0) { |
| flushImpl(); |
| } |
| } |
| |
| @Override |
| public Fn defineFn(String code, String[] names, boolean[] keepAlive) { |
| init(); |
| return new GFn(code, names, keepAlive); |
| } |
| |
| @Override |
| public Fn defineFn(String code, String... names) { |
| init(); |
| return new GFn(code, names, null); |
| } |
| |
| private static final class Key extends WeakReference<Object> { |
| private int hash; |
| |
| public Key(Object obj) { |
| super(obj); |
| this.hash = System.identityHashCode(obj); |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 7; |
| hash = 47 * hash + this.hash; |
| return hash; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof Key) { |
| Key other = (Key)obj; |
| if (hash != other.hash) { |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| private Map<Key,Integer> ids = new HashMap<Key, Integer>(); |
| int identityHashCode(Object o) { |
| Key k = new Key(o); |
| Integer val = ids.get(k); |
| if (val == null) { |
| int s = ids.size(); |
| ids.put(k, s); |
| return s; |
| } |
| return val; |
| } |
| |
| final int registerObject(Object o, boolean weak, boolean[] justAdded, String[] valueOf) { |
| if (o instanceof Enum && valueOf != null) { |
| valueOf[0] = o.toString(); |
| } |
| int id = identityHashCode(o); |
| for (;;) { |
| Object exp = findObject(id); |
| if (o == exp) { |
| return id; |
| } |
| if (exp == null) { |
| if (justAdded != null) { |
| justAdded[0] = true; |
| } |
| exported.add(new Exported(id, weak, o)); |
| return id; |
| } |
| throw new IllegalStateException("Collision!"); |
| } |
| } |
| |
| final Object findObject(int id) { |
| Exported obj = exported.floor(new Exported(id, false, null)); |
| return obj == null || obj.id != id ? null : obj.get(); |
| } |
| |
| @Texts({ |
| "fnHead=var jsvm = {};\n", |
| "fnName=jsvm.@1 = function(", |
| "fnThiz=thiz", |
| "fnNoThiz= var thiz = null;\n", |
| "fnSep=,", |
| "fnParam=p@1", |
| "fnClose=) {\n", |
| "fnBegin= var encParams = ds(@1).toJava(null, -1, [", |
| "fnPPar=@2 p@1", |
| "fnBody=]);\n" + |
| " var v = ds(@3).toVM('c', '@1', '@2', thiz ? thiz.id : null, encParams);\n" + |
| " while (v !== null && v.indexOf && v.indexOf('javascript:') === 0) {\n" + |
| " var script = v.substring(11);\n" + |
| " try {\n" + |
| " var r = eval.call(null, script);\n" + |
| " } catch (e) { console.warn('error: ' + e + ' executing: ' + script + ' at:\\n' + e.stack); }\n" + |
| " v = ds(@3).toVM('jr', null, null, null, null);" + |
| " }\n" + |
| " return @4 ? eval('(' + v + ')') : v;\n" + |
| "};\n", |
| |
| "fnFoot=ds(@2).rg(@1, jsvm);\n" |
| }) |
| final Integer exportVm(Object vm) { |
| int jNumber = registerObject(vm, false, null, null); |
| int vmNumber = COUNTER.getAndIncrement(); |
| StringBuilder sb = new StringBuilder(); |
| sb.append(Strings.fnHead()); |
| for (Method m : vm.getClass().getMethods()) { |
| if (m.getDeclaringClass() == Object.class) { |
| continue; |
| } |
| final Class<?>[] types = m.getParameterTypes(); |
| boolean instanceMethod = |
| types.length > 0 && |
| m.getName().startsWith(types[0].getName().replace('.', '_') + "$"); |
| int params = instanceMethod ? types.length - 1 : types.length; |
| sb.append(Strings.fnName(m.getName())); |
| String sep; |
| if (instanceMethod) { |
| sb.append(Strings.fnThiz()); |
| sep = Strings.fnSep(); |
| } else { |
| sep = ""; |
| } |
| for (int i = 0; i < params; i++) { |
| sb.append(sep); |
| sb.append(Strings.fnParam(i)); |
| sep = Strings.fnSep(); |
| } |
| sb.append(Strings.fnClose()); |
| if (!instanceMethod) { |
| sb.append(Strings.fnNoThiz()); |
| } |
| sb.append(Strings.fnBegin(key)); |
| for (int i = 0; i < params; i++) { |
| sb.append(Strings.fnPPar(i, i == 0 ? "" : ",")); |
| } |
| sb.append(Strings.fnBody(jNumber, m.getName(), key, evalJS)); |
| } |
| sb.append(Strings.fnFoot(vmNumber, key)); |
| deferExec(sb); |
| return vmNumber; |
| } |
| |
| @Texts({ |
| "v_null=null", |
| "v_number=number", |
| "v_java=java", |
| "v_object=object", |
| "v_array=array", |
| "v_boolean=boolean", |
| "v_error=error" |
| }) |
| final Object valueOf(String typeof, String res) { |
| if (Strings.v_null().equals(typeof)) { // NOI18N |
| return null; |
| } |
| if (Strings.v_number().equals(typeof)) { // NOI18N |
| return Double.valueOf(res); |
| } |
| if (Strings.v_java().equals(typeof)) { // NOI18N |
| return findObject(Integer.parseInt(res)); |
| } |
| if (Strings.v_object().equals(typeof)) { // NOI18N |
| return new JSObject(Integer.parseInt(res)); |
| } |
| if (Strings.v_array().equals(typeof)) { // NOI18N |
| int at = res.indexOf(':'); |
| int size = Integer.parseInt(res.substring(0, at)); |
| Object[] arr = new Object[size]; |
| at++; |
| for (int i = 0; i < size; i++) { |
| int next = res.indexOf(':', at); |
| int length = Integer.parseInt(res.substring(at, next)); |
| at = next + 1 + length; |
| arr[i] = valueOf(res.substring(next + 1, at)); |
| } |
| return arr; |
| } |
| if (Strings.v_boolean().equals(typeof)) { // NOI18N |
| return Boolean.valueOf(res); |
| } |
| if (Strings.v_error().equals(typeof)) { // NOI18N |
| throw new IllegalStateException(res); |
| } |
| return res; |
| } |
| |
| final Object valueOf(String typeAndValue) { |
| int colon = typeAndValue.indexOf(':'); |
| return valueOf(typeAndValue.substring(0, colon), typeAndValue.substring(colon + 1)); |
| } |
| |
| final void encodeObject(Object a, boolean weak, StringBuilder sb, int[] vmId) { |
| if (a == null) { |
| sb.append(Strings.v_null()); |
| } else if (a.getClass().isArray()) { |
| int len = Array.getLength(a); |
| sb.append('['); |
| String sep = ""; |
| for (int i = 0; i < len; i++) { |
| Object o = Array.get(a, i); |
| sb.append(sep); |
| encodeObject(o, weak, sb, null); |
| sep = ","; |
| } |
| sb.append(']'); |
| } else if (a instanceof Number) { |
| sb.append(a.toString()); |
| } else if (a instanceof String) { |
| sb.append('"'); |
| String s = (String)a; |
| int len = s.length(); |
| for (int i = 0; i < len; i++) { |
| char ch = s.charAt(i); |
| switch (ch) { |
| case '\\': sb.append("\\\\"); break; |
| case '\n': sb.append("\\n"); break; |
| case '\"': sb.append("\\\""); break; |
| default: |
| sb.append(ch); |
| break; |
| } |
| } |
| sb.append('"'); |
| } else if (a instanceof Boolean) { |
| sb.append(a.toString()); |
| } else if (a instanceof Character) { |
| sb.append((int)(Character)a); |
| } else if (a instanceof JSObject) { |
| sb.append("ds(").append(key).append(").o(").append(((JSObject) a).index).append(")"); |
| } else { |
| if (vmId != null) { |
| sb.append("ds(").append(key).append(").v(").append(vmId[0]).append(")"); |
| } else { |
| String[] valueOf = { null }; |
| sb.append("ds(").append(key).append(").j(").append(registerObject(a, weak, null, valueOf)); |
| sb.append(","); |
| encodeObject(valueOf[0], weak, sb, null); |
| if (a instanceof Object[]) { |
| for (Object n : ((Object[])a)) { |
| sb.append(","); |
| encodeObject(n, weak, sb, null); |
| } |
| } |
| sb.append(")"); |
| } |
| } |
| } |
| |
| interface OnReady { |
| void callbackReady(String name); |
| } |
| |
| private class Item implements Runnable { |
| final int id; |
| final Item prev; |
| Boolean done; |
| |
| final Method method; |
| final Object thiz; |
| final Object[] params; |
| Object result; |
| |
| Item(int id, Item prev, Method method, Object thiz, Object[] params) { |
| this.id = id; |
| this.prev = prev; |
| this.method = method; |
| this.thiz = thiz; |
| this.params = adaptParams(method, Arrays.asList(params)); |
| this.toExec = null; |
| } |
| |
| |
| protected final String inJavaScript(boolean[] finished) { |
| if (this.method != null) { |
| return js(finished); |
| } else { |
| return sj(finished); |
| } |
| } |
| protected final void inJava() { |
| if (this.method == null) { |
| return; |
| } |
| if (done == null) { |
| done = false; |
| try { |
| log(Level.FINE, "Calling {0}", method); |
| result = method.invoke(thiz, params); |
| } catch (Exception ex) { |
| log(Level.SEVERE, "Cannot invoke " + method + " on " + thiz + " with " + Arrays.toString(params), ex); |
| } finally { |
| done = true; |
| log(Level.FINE, "Result: {0}", result); |
| } |
| } |
| } |
| |
| @Override public void run() { |
| synchronized (lock()) { |
| log(Level.FINE, "run: {0}", this); |
| inJava(); |
| lock().notifyAll(); |
| } |
| } |
| |
| |
| protected String js(boolean[] finished) { |
| if (Boolean.TRUE.equals(done)) { |
| StringBuilder sb = new StringBuilder(); |
| encodeObject(result, false, sb, null); |
| finished[0] = true; |
| return sb.toString(); |
| } |
| return null; |
| } |
| |
| private final String toExec; |
| private String typeof; |
| |
| Item(int id, Item prev, String toExec) { |
| this.id = id; |
| this.prev = prev; |
| this.toExec = toExec; |
| |
| this.method = null; |
| this.params = null; |
| this.thiz = null; |
| } |
| |
| protected String sj(boolean[] finished) { |
| finished[0] = false; |
| if (Boolean.TRUE.equals(done)) { |
| return null; |
| } |
| done = true; |
| return "javascript:" + toExec; |
| } |
| |
| protected final void result(String typeof, String result) { |
| if (this.method != null) { |
| throw new UnsupportedOperationException(); |
| } |
| this.typeof = typeof; |
| this.result = result; |
| log(Level.FINE, "result ({0}): {1} for {2}", typeof, result, toExec); |
| } |
| } // end of Item |
| |
| final void result(String counterId, String typeof, String res) { |
| log(Level.FINE, "result#{2}@{0}: {1}", typeof, res, counterId); |
| synchronized (lock()) { |
| if ("OK".equals(typeof)) { |
| log(Level.FINE, "init: {0}", res); |
| lock().notifyAll(); |
| if ("Initialized".equals(res)) { |
| log(Level.FINE, "callbackReady: countingDown"); |
| handleLog(Level.FINE, msg.toString()); |
| msg = null; |
| initialized.countDown(); |
| } |
| return; |
| } |
| final int id = Integer.parseInt(counterId); |
| final Item top = topMostCall(); |
| if (top.id == id) { |
| top.result(typeof, res); |
| registerCall(top.prev); |
| return; |
| } |
| Item it = top; |
| while (it != null) { |
| Item process = it.prev; |
| if (process.id == id) { |
| process.result(typeof, res); |
| return; |
| } |
| it = process; |
| } |
| throw new IllegalStateException("Cannot find " + id + " for " + typeof + " res: " + res); |
| } |
| } |
| |
| final String javacall( |
| String vmNumber, String fnName, String thizId, String encParams |
| ) throws Exception { |
| synchronized (lock()) { |
| Object vm = findObject(Integer.parseInt(vmNumber)); |
| assert vm != null; |
| final Object obj = thizId == null || "null".equals(thizId) |
| ? null : valueOf("java", thizId); |
| Method method = null; |
| for (Method m : vm.getClass().getMethods()) { |
| if (m.getName().equals(fnName)) { |
| method = m; |
| break; |
| } |
| } |
| assert method != null; |
| List<Object> params = new ArrayList<Object>(); |
| if (obj != null) { |
| params.add(obj); |
| } |
| final Object args = valueOf(encParams); |
| if (!(args instanceof Object[])) { |
| throw new IllegalStateException("Expecting array: " + args); |
| } |
| params.addAll(Arrays.asList((Object[]) args)); |
| Object[] converted = adaptParams(method, params); |
| Item top = topMostCall(); |
| boolean first = top == null; |
| log(Level.FINE, "jc: {0}@{1}args: {2} is first: {3}, now: {4}", new Object[]{method.getName(), vm, params, first, topMostCall()}); |
| Item newItem = registerCall(new Item(nextCallId(), top, method, vm, converted)); |
| if (first || synchronous) { |
| dispatch(newItem); |
| } |
| return javaresult(); |
| } |
| } |
| |
| final String javaresult() throws IllegalStateException, InterruptedException { |
| synchronized (lock()) { |
| boolean[] finished = {false}; |
| for (;;) { |
| if (deferred != null) { |
| deferred.insert(0, "javascript:"); |
| String ret = deferred.toString(); |
| deferred = null; |
| return ret; |
| } |
| finished[0] = false; |
| final Item top = topMostCall(); |
| String jsToExec = top.inJavaScript(finished); |
| log(Level.FINE, "jr: {0} jsToExec: {1} finished: {2}", new Object[]{topMostCall(), jsToExec, finished[0]}); |
| if (jsToExec != null) { |
| if (finished[0]) { |
| registerCall(top.prev); |
| } |
| return jsToExec; |
| } |
| lock().wait(); |
| } |
| } |
| } |
| |
| private StringBuilder deferred; |
| private Collection<Object> arguments = new LinkedList<Object>(); |
| |
| public final void loadScript(final Reader reader) throws Exception { |
| StringBuilder sb = new StringBuilder(); |
| char[] arr = new char[4092]; |
| for (;;) { |
| int len = reader.read(arr); |
| if (len == -1) { |
| break; |
| } |
| sb.append(arr, 0, len); |
| } |
| deferExec(sb); |
| } |
| |
| |
| final void deferExec(StringBuilder sb) { |
| synchronized (lock()) { |
| log(Level.FINE, "deferExec: {0} empty: {1}, call: {2}", new Object[]{sb, deferred == null, topMostCall()}); |
| if (deferred == null) { |
| deferred = sb; |
| } else { |
| deferred.append(sb); |
| } |
| } |
| } |
| |
| @Texts({ |
| "flushExec=\n\nds(@1).toJava('r', '@2', null);\n" |
| }) |
| void flushImpl() { |
| synchronized (lock()) { |
| if (deferred != null) { |
| final int id = nextCallId(); |
| log(Level.FINE, "flush#{1}: {0}", deferred, id); |
| exec(id, Strings.flushExec(key, id).toString()); |
| } |
| } |
| } |
| |
| final Object exec(int id, String fn) { |
| Object ret; |
| boolean first; |
| synchronized (lock()) { |
| if (deferred != null) { |
| deferred.append(fn); |
| fn = deferred.toString(); |
| deferred = null; |
| log(Level.FINE, "Flushing {0}", fn); |
| } |
| |
| Item myCall; |
| boolean load; |
| final Item top = topMostCall(); |
| if (top != null) { |
| myCall = registerCall(new Item(id, top, fn)); |
| load = synchronous; |
| first = false; |
| } else { |
| myCall = registerCall(new Item(id, null, null)); |
| load = true; |
| first = true; |
| } |
| if (load) { |
| loadJS(fn); |
| } |
| for (;;) { |
| if (myCall.typeof != null) { |
| break; |
| } |
| try { |
| lock().wait(); |
| } catch (InterruptedException ex) { |
| log(Level.SEVERE, null, ex); |
| } |
| Item c = topMostCall(); |
| if (c != null) { |
| c.inJava(); |
| } |
| lock().notifyAll(); |
| } |
| ret = valueOf(myCall.typeof, (String) myCall.result); |
| } |
| if (first) { |
| arguments.clear(); |
| } |
| return ret; |
| } |
| |
| private static Object[] adaptParams(Method toCall, List<Object> args) { |
| final Object[] arr = new Object[args.size()]; |
| final Class<?>[] types = toCall.getParameterTypes(); |
| for (int i = 0; i < arr.length; i++) { |
| arr[i] = adaptType(types[i], args.get(i)); |
| } |
| return arr; |
| } |
| |
| private static Object adaptType(Class<?> type, Object value) { |
| if (type.isPrimitive() && value instanceof Number) { |
| final Number n = (Number)value; |
| if (type == Byte.TYPE) return n.byteValue(); |
| if (type == Short.TYPE) return n.shortValue(); |
| if (type == Integer.TYPE) return n.intValue(); |
| if (type == Long.TYPE) return n.longValue(); |
| if (type == Float.TYPE) return n.floatValue(); |
| if (type == Double.TYPE) return n.doubleValue(); |
| if (type == Character.TYPE) return (char)n.intValue(); |
| } |
| return value; |
| } |
| |
| private static final class JSObject { |
| private final int index; |
| |
| public JSObject(int index) { |
| this.index = index; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 37 * this.index; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| final JSObject other = (JSObject) obj; |
| return this.index == other.index; |
| } |
| |
| @Texts({ |
| "jsObject=[jsobject-@1]" |
| }) |
| @Override |
| public String toString() { |
| return Strings.jsObject(index).toString(); |
| } |
| |
| } // end of JSObject |
| |
| static final AtomicInteger COUNTER = new AtomicInteger(0); |
| @Texts({ |
| "registerFn=ds(@2).rg(@1, function(", |
| "registerCode=) {\n@1\n});", |
| "v_vm=vm" |
| }) |
| private final class GFn extends Fn { |
| private final int id; |
| private final int[] vmId; |
| private final boolean[] keepAlive; |
| |
| public GFn(String code, String[] names, boolean[] ka) { |
| super(Generic.this); |
| this.id = COUNTER.getAndIncrement(); |
| this.keepAlive = ka; |
| |
| StringBuilder sb = new StringBuilder(1024); |
| sb.append(Strings.registerFn(id, key)); |
| String sep = ""; |
| boolean isVm = false; |
| for (String n : names) { |
| sb.append(sep).append(n); |
| sep = ","; |
| isVm = false; |
| if (Strings.v_vm().equals(n)) { |
| isVm = true; |
| } |
| } |
| sb.append(Strings.registerCode(code)); |
| this.vmId = isVm ? new int[] { -1 } : null; |
| deferExec(sb); |
| } |
| |
| @Override |
| public Object invoke(Object thiz, Object... args) throws Exception { |
| return invokeImpl(true, thiz, args); |
| } |
| |
| @Override |
| public void invokeLater(Object thiz, Object... args) throws Exception { |
| invokeImpl(false, thiz, args); |
| } |
| |
| @Texts({ |
| "invokeImplFn=ds(@3).fn(@1, @2, @4, " |
| }) |
| private Object invokeImpl(boolean wait4js, Object thiz, Object... args) throws Exception { |
| if (vmId != null && vmId[0] < 0) { |
| vmId[0] = exportVm(args[args.length - 1]); |
| } |
| |
| StringBuilder sb = new StringBuilder(256); |
| encodeObject(thiz, false, sb, null); |
| for (int i = 0; i < args.length; i++) { |
| sb.append(", "); |
| boolean weak = keepAlive != null && !keepAlive[i]; |
| encodeObject(args[i], weak, sb, i == args.length - 1 ? vmId : null); |
| } |
| sb.append(");"); |
| |
| arguments.add(thiz); |
| arguments.add(args); |
| |
| synchronized (lock()) { |
| int callId = nextCallId(); |
| sb.insert(0, Strings.invokeImplFn(id, wait4js, key, callId)); |
| if (wait4js) { |
| return exec(callId, sb.toString()); |
| } else { |
| deferExec(sb); |
| return null; |
| } |
| } |
| } |
| } |
| |
| private static final class Exported implements Comparable<Exported> { |
| private final int id; |
| private final Object obj; |
| private final boolean ref; |
| |
| Exported(int id, boolean ref, Object obj) { |
| this.id = id; |
| this.obj = ref ? createReferenceFor(obj) : obj; |
| this.ref = ref; |
| WeakHolder.clean(); |
| } |
| |
| protected Object get() { |
| if (ref) { |
| return ((Reference<?>)obj).get(); |
| } else { |
| return obj; |
| } |
| } |
| |
| @Override |
| public int compareTo(Exported o) { |
| return id - o.id; |
| } |
| |
| private static Object createReferenceFor(Object obj) { |
| Reference<Object> ref = new WeakReference<Object>(obj); |
| if (obj instanceof Reference) { |
| Reference<?> myRef = (Reference<?>) obj; |
| if (obj.getClass().getName().equals("org.netbeans.html.ko4j.Knockout")) { |
| // workaround for #255677 |
| WeakHolder h = new WeakHolder(myRef.get(), obj); |
| h.register(); |
| } |
| } |
| return ref; |
| } |
| } |
| |
| private static final class WeakHolder extends PhantomReference<Object> { |
| private static final ReferenceQueue QUEUE = new ReferenceQueue(); |
| private static final Set<WeakHolder> active = new HashSet<WeakHolder>(); |
| private final Object knockout; |
| |
| public WeakHolder(Object referent, Object knockout) { |
| super(referent, QUEUE); |
| this.knockout = knockout; |
| } |
| |
| static void clean() { |
| for (;;) { |
| WeakHolder h = (WeakHolder) QUEUE.poll(); |
| if (h == null) { |
| break; |
| } |
| active.remove(h); |
| } |
| } |
| |
| void register() { |
| active.add(this); |
| } |
| } |
| |
| private Item topMostCall() { |
| assert Thread.holdsLock(lock()); |
| return call; |
| } |
| |
| private Item registerCall(Item call) { |
| assert Thread.holdsLock(lock()); |
| this.call = call; |
| lock().notifyAll(); |
| return call; |
| } |
| |
| private int nextCallId() { |
| assert Thread.holdsLock(lock()); |
| return ++callCounter; |
| } |
| } |