/* * Copyright 2011 Chad Retz * * Licensed 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.gwtnode.dev.debug; import java.util.HashMap; import java.util.Map; import org.gwtnode.core.JavaScriptReturningFunction; import org.gwtnode.core.JavaScriptUtils; import org.gwtnode.core.node.Global; import org.gwtnode.core.node.NodeJsError; import org.gwtnode.core.node.vm.Vm; import org.gwtnode.dev.debug.message.FreeValueMessage; import org.gwtnode.dev.debug.message.SpecialMethod; import org.gwtnode.dev.debug.message.Value; import org.gwtnode.dev.debug.message.ValueType; import com.google.gwt.core.client.JavaScriptException; import com.google.gwt.core.client.JavaScriptObject; /** * OOHPM session handler * * @author Chad Retz */ class SessionHandler { private final Vm vm = Vm.get(); private final Map<Integer, JavaScriptReturningFunction<?>> javaInvokeFunctions = new HashMap<Integer, JavaScriptReturningFunction<?>>(); private final Map<Integer, JavaScriptReturningFunction<?>> tearOffs = new HashMap<Integer, JavaScriptReturningFunction<?>>(); private final ObjectCache objectCache = new ObjectCache(); private final HostChannel channel; private final DebugLog log; public SessionHandler(HostChannel channel, DebugLog log) { this.channel = channel; this.log = log; //could start a REPL here too... //Repl.get().start(); initContext(); } public DebugLog getLog() { return log; } @SuppressWarnings("unchecked") private <T extends JavaScriptObject> T runInContext(String code) { log.debug("Executing code in context:\n%s", code); return (T) vm.runInThisContext(code); } private void initContext() { try { initGlobalContext(); initJavaInvokeMethods(); } catch (Exception e) { log.debug("Error initializing"); throw new DebugRuntimeException("Error initializing context", e); } } private native void initGlobalContext() /*-{ global['window'] = global; }-*/; private native void initJavaInvokeMethods() /*-{ //TODO: fix this...I hate this global['__gwtnode_sessionHandler'] = this; global['__gwt_makeJavaInvoke'] = function(paramCount) { var thisRef = global['__gwtnode_sessionHandler']; return thisRef.@org.gwtnode.dev.debug.SessionHandler::makeJavaInvoke(I)(paramCount); }; global['__gwt_makeTearOff'] = function(proxy, dispId, paramCount) { var thisRef = global['__gwtnode_sessionHandler']; return thisRef.@org.gwtnode.dev.debug.SessionHandler::makeTearOff(II)(dispId, paramCount); }; }-*/; @SuppressWarnings("rawtypes") private JavaScriptReturningFunction<?> makeJavaInvoke(int paramCount) { JavaScriptReturningFunction<?> ret = javaInvokeFunctions.get(paramCount); if (ret == null) { ret = new JavaInvoker(channel, this, paramCount).getNativeFunction(); javaInvokeFunctions.put(paramCount, ret); } return ret; } @SuppressWarnings("rawtypes") private JavaScriptReturningFunction<?> makeTearOff( int dispId, int paramCount) { JavaScriptReturningFunction<?> ret = tearOffs.get(paramCount); if (ret == null) { ret = new TearOff(channel, this, paramCount).getNativeFunction(); tearOffs.put(paramCount, ret); } return ret; } public void end() { channel.disconnectFromHost(); } public void disconnectDetected() { try { runInContext("__gwt_disconnected();"); } catch (Exception e) { throw new DebugRuntimeException("Error disconnecting", e); } } public void fatalError(String message) { log.error("Fatal error: " + message); } public void freeJavaObject(int id) { objectCache.freeJavaObject(id); } public void sendFreeValues() { channel.sendMessage(new FreeValueMessage(objectCache.getJavaObjectsToFree().toArray(new Integer[0]))); objectCache.clearJavaObjectsToFree(); } public void freeValues(Integer... ids) { for (int id : ids) { objectCache.freeJavaScriptObject(id); } } Value<?> getValueFromJavaScriptObject(Object jsObject) { ValueType type = ValueType.values()[getValueTypeOrdinalFromJavaScriptObject(jsObject)]; if (type == ValueType.JAVA_OBJECT) { Integer val = objectCache.getJavaObjectId((JavaScriptObject) jsObject); if (val != null) { return type.createValueFromObject(val); } else { return ValueType.JAVA_SCRIPT_OBJECT.createValueFromObject( objectCache.getJavaScriptObjectId((JavaScriptObject) jsObject, true)); } } else { return type.createValueFromObject(jsObject); } } Object getJavaScriptObjectFromValue(Value<?> val) { switch (val.getType()) { case JAVA_OBJECT: log.debug("Getting Java object reference"); return objectCache.getJavaObjectReference((Integer) val.getValue(), true); case JAVA_SCRIPT_OBJECT: log.debug("Getting JavaScript object reference"); return objectCache.getJavaScriptObject((Integer) val.getValue()); default: return val.getValue(); } } private native int getValueTypeOrdinalFromJavaScriptObject(Object obj) /*-{ if (obj === undefined) { return 12; } else if (obj === null) { return 0; } else { var typ = typeof obj; switch(typ) { case 'boolean': return 1; case 'string': return 9; case 'number': if (obj.toString() != obj.toFixed(0).toString()) { return 8; } else { return 5; } case 'object': case 'function': return 10; default: throw new Error('Unrecognized type: ' + typ); } } }-*/; public InvokeResult invoke(String methodName, Value<?> thisObj, Value<?>... args) { JavaScriptReturningFunction<Object> function; log.debug("Getting 'this'"); Object nativeThis = getJavaScriptObjectFromValue(thisObj); if (nativeThis != null) { function = JavaScriptUtils.getProperty((JavaScriptObject) nativeThis, methodName).cast(); } else { log.debug("Using global object"); function = JavaScriptUtils.getProperty(Global.get(), methodName).cast(); } log.debug("Got object"); log.debug("Got function: ", function); Object result; boolean exception = false; try { log.debug("Loading %d native arguments", args.length); Object[] nativeArgs = new Object[args.length]; for (int i = 0; i < args.length; i++) { nativeArgs[i] = getJavaScriptObjectFromValue(args[i]); } log.debug("Getting make invoke"); log.debug("Invoking function"); result = function.apply(nativeArgs); log.debug("Invocation complete: " + result); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("Exception occurred %s", JavaScriptUtils. appendException(e, new StringBuilder())); } if (e instanceof JavaScriptException) { result = ((JavaScriptException) e).getException(); } else { result = NodeJsError.create("Ahh!"); } exception = true; } return new InvokeResult(getValueFromJavaScriptObject(result), exception); } public InvokeResult invokeSpecial(SpecialMethod specialMethod, Value<?>... args) { throw new UnsupportedOperationException("InvokeSpecial not implemented"); } public void loadJsni(String jsCode) { try { runInContext(jsCode); } catch (Exception e) { throw new DebugRuntimeException("Error loading JSNI", e); } } public static class InvokeResult { private final Value<?> value; private final boolean exception; private InvokeResult(Value<?> value, boolean exception) { this.value = value; this.exception = exception; } public Value<?> getValue() { return value; } public boolean isException() { return exception; } } }