diff --git a/.gitignore b/.gitignore
index 6b988ccac..e2f378cce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,6 @@ hs_err*.log
*.ipr
*.iws
.idea
+
+# Build output.
+node
\ No newline at end of file
diff --git a/src/main/java/com/eclipsesource/v8/ConcurrentV8.java b/src/main/java/com/eclipsesource/v8/ConcurrentV8.java
new file mode 100644
index 000000000..68f877fd1
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+/**
+ * Wrapper class for an {@link com.eclipsesource.v8.V8} instance that allows
+ * a V8 instance to be invoked from across threads without explicitly acquiring
+ * or releasing locks.
+ *
+ * This class does not guarantee the safety of any objects stored in or accessed
+ * from the wrapped V8 instance; it only enables callers to interact with a V8
+ * instance from any thread. The V8 instance represented by this class should
+ * still be treated with thread safety in mind
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+public final class ConcurrentV8 {
+//Private//////////////////////////////////////////////////////////////////////
+
+ // Wrapped V8 instance, initialized by the default runnable.
+ private V8 v8 = null;
+
+//Protected////////////////////////////////////////////////////////////////////
+
+ // Release the V8 runtime when this class is finalized.
+ @Override protected void finalize() {
+ release();
+ }
+
+//Public///////////////////////////////////////////////////////////////////////
+
+ public ConcurrentV8() {
+ v8 = V8.createV8Runtime();
+ v8.getLocker().release();
+ }
+
+ /**
+ * Calls {@link #run(ConcurrentV8Runnable)}, quietly handling any thrown
+ * exceptions.
+ *
+ * @see {@link #run(ConcurrentV8Runnable)}.
+ */
+ public void runQuietly(ConcurrentV8Runnable runny) {
+ try {
+ run(runny);
+ } catch (Throwable t) { }
+ }
+
+ /**
+ * Runs an {@link ConcurrentV8Runnable} on the V8 thread.
+ *
+ * Note: This method executes synchronously, not asynchronously;
+ * it will not return until the passed {@link ConcurrentV8Runnable} is done
+ * executing.
+ *
+ * @param runny {@link ConcurrentV8Runnable} to run.
+ *
+ * @throws Exception If the passed runnable throws an exception, this
+ * method will throw that exact exception.
+ */
+ public synchronized void run(ConcurrentV8Runnable runny) throws Exception {
+ try {
+ v8.getLocker().acquire();
+
+ try {
+ runny.run(v8);
+ } catch (Throwable t) {
+ v8.getLocker().release();
+ if (t instanceof Exception) {
+ throw (Exception) t;
+ } else {
+ throw new Exception(t);
+ }
+ }
+
+ v8.getLocker().release();
+ } catch (Throwable t) {
+ if (v8 != null && v8.getLocker() != null && v8.getLocker().hasLock()) {
+ v8.getLocker().release();
+ }
+
+ if (t instanceof Exception) {
+ throw (Exception) t;
+ } else {
+ throw new Exception(t);
+ }
+ }
+ }
+
+ /**
+ * Releases the underlying {@link V8} instance.
+ *
+ * This method should be invoked once you're done using this object,
+ * otherwise a large amount of garbage could be left on the JVM due to
+ * native resources.
+ *
+ * Note: If this method has already been called once, it
+ * will do nothing.
+ */
+ public void release() {
+ if (v8 != null && !v8.isReleased()) {
+ // Release the V8 instance from the V8 thread context.
+ runQuietly(new ConcurrentV8Runnable() {
+ @Override
+ public void run(V8 v8) {
+ if (v8 != null && !v8.isReleased()) {
+ v8.release();
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java b/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java
new file mode 100644
index 000000000..3d9463196
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+/**
+ * Simple runnable for use with an {@link ConcurrentV8} instance.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+public interface ConcurrentV8Runnable {
+ void run(final V8 v8) throws Exception;
+}
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java b/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java
new file mode 100644
index 000000000..2813ef1b2
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Utilities for adapting Java classes and objects into a V8 runtime.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+public final class V8JavaAdapter {
+
+ /**
+ * Injects an existing Java object into V8 as a variable.
+ *
+ * If the passed object represents a primitive array (e.g., String[], Object[], int[]),
+ * the array will be unwrapped and injected into the V8 context as an ArrayList. Any
+ * modifications made to the injected list will not be passed back up to the Java runtime.
+ *
+ * This method will immediately invoke {@link #injectClass(String, Class, V8Object)}
+ * before injecting the object, causing the object's class to be automatically
+ * injected into the V8 Object if it wasn't already.
+ *
+ * NOTE: If you wish to use an interceptor for the class of an injected object,
+ * you must explicitly invoke {@link #injectClass(Class, V8JavaClassInterceptor, V8Object)} or
+ * {@link #injectClass(String, Class, V8JavaClassInterceptor, V8Object)}. This method will
+ * NOT specify an interceptor automatically for the injected object.
+ *
+ * @param name Name of the variable to assign the Java object to. If this value is null,
+ * a UUID will be automatically generated and used as the name of the variable.
+ * @param object Java object to inject.
+ * @param rootObject {@link V8Object} to inject the Java object into.
+ *
+ * @return String identifier of the injected object.
+ */
+ public static String injectObject(String name, Object object, V8Object rootObject) {
+ //TODO: Add special handlers for N-dimensional and primitive arrays.
+ //TODO: This should inject arrays as JS arrays, not lists. Meh.
+ //TODO: This will bypass interceptors in some cases.
+ //TODO: This is terrible.
+ if (object.getClass().isArray()) {
+ Object[] rawArray = (Object[]) object;
+ List injectedArray = new ArrayList(rawArray.length);
+ for (Object obj : rawArray) {
+ injectedArray.add(obj);
+ }
+ return injectObject(name, injectedArray, rootObject);
+ } else {
+ injectClass("".equals(object.getClass().getSimpleName()) ?
+ object.getClass().getName().replaceAll("\\.+", "_") :
+ object.getClass().getSimpleName(),
+ object.getClass(),
+ rootObject);
+ }
+
+ if (name == null) {
+ name = "TEMP" + UUID.randomUUID().toString().replaceAll("-", "");
+ }
+
+ //Build an empty object instance.
+ V8JavaClassProxy proxy = V8JavaCache.cachedV8JavaClasses.get(object.getClass());
+ StringBuilder script = new StringBuilder();
+ script.append("var ").append(name).append(" = new function() {");
+ if (proxy.getInterceptor() != null) script.append(proxy.getInterceptor().getConstructorScriptBody());
+ script.append("\n}; ").append(name).append(";");
+
+ V8Object other = V8JavaObjectUtils.getRuntimeSarcastically(rootObject).executeObjectScript(script.toString());
+ String id = proxy.attachJavaObjectToJsObject(object, other);
+ other.release();
+ return id;
+ }
+
+ /**
+ * Injects a Java class into a V8 object as a prototype.
+ *
+ * The injected "class" will be equivalent to a Java Script prototype with
+ * a name identical to the one specified when invoking this function. For
+ * example, the java class {@code com.foo.Bar} could be new'd from the Java Script
+ * context by invoking {@code new Bar()} if {@code "Bar"} was passed as the
+ * name use when injecting the class.
+ *
+ * @param name Name to use when injecting the class into the V8 object.
+ * @param classy Java class to inject.
+ * @param interceptor {@link V8JavaClassInterceptor} to use with this class. Pass null if no interceptor is desired.
+ * @param rootObject {@link V8Object} to inject the Java class into.
+ */
+ public static void injectClass(String name, Class> classy, V8JavaClassInterceptor interceptor, V8Object rootObject) {
+ //Calculate V8-friendly full class names.
+ String v8FriendlyClassname = classy.getName().replaceAll("\\.+", "_");
+
+ //Register the class proxy.
+ V8JavaClassProxy proxy;
+ if (V8JavaCache.cachedV8JavaClasses.containsKey(classy)) {
+ proxy = V8JavaCache.cachedV8JavaClasses.get(classy);
+ } else {
+ proxy = new V8JavaClassProxy(classy, interceptor);
+ V8JavaCache.cachedV8JavaClasses.put(classy, proxy);
+ }
+
+ //Check if the root object already has a constructor.
+ //TODO: Is this faster or slower than checking if a specific V8Value is "undefined"?
+ if (!Arrays.asList(rootObject.getKeys()).contains("v8ConstructJavaClass" + v8FriendlyClassname)) {
+ rootObject.registerJavaMethod(proxy, "v8ConstructJavaClass" + v8FriendlyClassname);
+
+ //Build up the constructor script.
+ StringBuilder script = new StringBuilder();
+ script.append("this.").append(name).append(" = function() {");
+ script.append("v8ConstructJavaClass").append(v8FriendlyClassname).append(".apply(this, arguments);");
+
+ if (proxy.getInterceptor() != null) {
+ script.append(proxy.getInterceptor().getConstructorScriptBody());
+ }
+
+ script.append("\n};");
+
+ //Evaluate the script to create a new constructor function.
+ V8JavaObjectUtils.getRuntimeSarcastically(rootObject).executeVoidScript(script.toString());
+
+ //Build up static methods if needed.
+ if (proxy.getInterceptor() == null) {
+ V8Object constructorFunction = (V8Object) rootObject.get(name);
+ for (V8JavaStaticMethodProxy method : proxy.getStaticMethods()) {
+ constructorFunction.registerJavaMethod(method, method.getMethodName());
+ }
+
+ //Clean up after ourselves.
+ constructorFunction.release();
+ }
+ }
+ }
+
+ public static void injectClass(Class> classy, V8JavaClassInterceptor interceptor, V8Object object) {
+ injectClass(classy.getSimpleName(), classy, interceptor, object);
+ }
+
+ public static void injectClass(String name, Class> classy, V8Object object) {
+ injectClass(name, classy, null, object);
+ }
+
+ public static void injectClass(Class> classy, V8Object object) {
+ injectClass(classy.getSimpleName(), classy, null, object);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaCache.java b/src/main/java/com/eclipsesource/v8/V8JavaCache.java
new file mode 100644
index 000000000..a1d903af5
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaCache.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Centralized cache for resources created via the {@link V8JavaAdapter}. This class
+ * is not meant to be used directly by API consumers; any actions should be performed
+ * via the {@link V8JavaAdapter} class.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+final class V8JavaCache {
+ /**
+ * Cache of Java classes injected into V8 via the {@link V8JavaAdapter}.
+ */
+ public static final Map, V8JavaClassProxy> cachedV8JavaClasses = new HashMap, V8JavaClassProxy>();
+
+ /**
+ * Cache of Java objects created through V8 via a {@link V8JavaClassProxy}.
+ *
+ * TODO: This cache is shared across V8 runtimes, theoretically allowing cross-runtime sharing of Java objects.
+ * Is this a "feature" or a "bug"?
+ */
+ public static final Map identifierToV8ObjectMap = new HashMap();
+ public static final Map v8ObjectToIdentifierMap = new WeakHashMap();
+
+ /**
+ * Removes any Java objects that have been garbage collected from the object cache.
+ */
+ public static void removeGarbageCollectedJavaObjects() {
+ Iterator> it = identifierToV8ObjectMap.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = it.next();
+ if (entry.getValue().get() == null) {
+ it.remove();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java
new file mode 100644
index 000000000..7a4e2ed1f
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+/**
+ * Represents a class that intercepts a Java object of a known type
+ * and translates it into a set of key-value pairs before injecting
+ * it into a V8 context.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+public interface V8JavaClassInterceptor {
+
+ /**
+ * Returns the body of the JS constructor function that this
+ * interceptor works with.
+ *
+ * All constructor scripts should declare two functions, {@code onJ2V8Inject} and
+ * {@code onJ2V8Withdraw}, which accept a single variable, {@code context}. These
+ * functions will be called when a Java object is injected and extracted from V8,
+ * respectively. The {@code context} variable will contain all of the top-level
+ * properties that were set onto the {@code context} variable provided to this
+ * class's {@link #onInject(V8JavaClassInterceptorContext, Object)} and
+ * {@link #onExtract(V8JavaClassInterceptorContext, Object)} methods.
+ *
+ * @return Body of a JS constructor function that this interceptor
+ * will be used with.
+ */
+ String getConstructorScriptBody();
+
+ /**
+ * Called when an intercepted class is injected into the V8 context.
+ *
+ * Use this method to extract data from the passed object and inject it
+ * into the V8 context; do not worry about translating values, as J2V8
+ * will handle that for you.
+ *
+ * @param context Context variable to set all injected properties onto.
+ * @param object Object being injected.
+ */
+ void onInject(V8JavaClassInterceptorContext context, T object);
+
+ /**
+ * Called when an intercepted class is extracted from the V8 context.
+ *
+ * Use this method to extract data from the V8 context and inject it
+ * into the passed object; do not worry about translation values, as
+ * J2V8 will have already handled that for you.
+ *
+ * @param context Context variable to extract properties from.
+ * @param object Object to inject all properties into.
+ */
+ void onExtract(V8JavaClassInterceptorContext context, T object);
+}
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java
new file mode 100644
index 000000000..88e1a8b01
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Lightweight class for handling contexts for {@link V8JavaClassInterceptor}s.
+ *
+ * TODO: Maybe we could make it so that instead of a map, intercepted contexts
+ * have a predefined schema? This would reduce garbage by a good deal.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+public final class V8JavaClassInterceptorContext {
+//Private//////////////////////////////////////////////////////////////////////
+
+ private final Map internalContext = new HashMap();
+
+//Public///////////////////////////////////////////////////////////////////////
+
+ /**
+ * Sets a property on this intercepted context.
+ *
+ * @param name Name of the property to set.
+ * @param value Value to set it to.
+ */
+ public void set(String name, Object value) {
+ internalContext.put(name, value);
+ }
+
+ /**
+ * Gets a property from this intercepted context.
+ *
+ * @param name Name of the property to get.
+ *
+ * @return Value of the property, or null if it was never set.
+ */
+ public Object get(String name) {
+ return internalContext.get(name);
+ }
+}
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java
new file mode 100644
index 000000000..6ee9fd41d
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java
@@ -0,0 +1,323 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+/**
+ * Represents a proxy of a Java class for use within a V8 javascript context.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+final class V8JavaClassProxy implements JavaCallback {
+//Private//////////////////////////////////////////////////////////////////////
+
+ //Class represented by this proxy.
+ private final Class> classy;
+ private final V8JavaClassInterceptor interceptor;
+
+ //Intercepted contexts owned by this proxy.
+ private final Map interceptContexts = new HashMap();
+
+ //Methods owned by this proxy.
+ private final Map staticMethods = new HashMap();
+ private final Map instanceMethods = new HashMap();
+
+ //Instances of this proxy created from JS. Used to control garbage collection.
+ private final List jsObjects = new ArrayList(); {
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override public void run() {
+ // When the application exits, make sure no instances remained.
+ if (jsObjects.size() > 0) {
+ System.err.println(jsObjects.size() + " instance(s) of " + classy.getName() +
+ " were created from JavaScript and not released via $release.");
+ }
+ }
+ }));
+ }
+
+//Protected////////////////////////////////////////////////////////////////////
+
+ /**
+ * @return All static methods associated with the class this proxy represents.
+ */
+ List getStaticMethods() {
+ return new ArrayList(staticMethods.values());
+ }
+
+//Public///////////////////////////////////////////////////////////////////////
+
+ public V8JavaClassProxy(Class> classy, V8JavaClassInterceptor interceptor) {
+ this.classy = classy;
+ this.interceptor = interceptor;
+
+ // TODO: Do we want to cache methods from non-final classes to reduce
+ // the memory footprint of multiple classes with a common base?
+
+ // Get all public methods for the given class.
+ for (Method m : classy.getMethods()) {
+ // We want to ignore any methods from the base object class for now since that
+ // will take up excess memory for potentially unused features.
+ if (!m.getDeclaringClass().equals(Object.class)) {
+ if (Modifier.isStatic(m.getModifiers())) {
+ if (staticMethods.containsKey(m.getName())) {
+ staticMethods.get(m.getName()).addMethodSignature(m);
+ } else {
+ V8JavaStaticMethodProxy methodProxy = new V8JavaStaticMethodProxy(m.getName());
+ methodProxy.addMethodSignature(m);
+ staticMethods.put(m.getName(), methodProxy);
+ }
+ } else {
+ if (instanceMethods.containsKey(m.getName())) {
+ instanceMethods.get(m.getName()).addMethodSignature(m);
+ } else {
+ V8JavaInstanceMethodProxy methodProxy = new V8JavaInstanceMethodProxy(m.getName());
+ methodProxy.addMethodSignature(m);
+ instanceMethods.put(m.getName(), methodProxy);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link V8JavaClassInterceptor} associated with this class.
+ *
+ * @return The {@link V8JavaClassInterceptor} associated with this class,
+ * or null if none exists.
+ */
+ public V8JavaClassInterceptor getInterceptor() {
+ return interceptor;
+ }
+
+ /**
+ * Updates an {@link V8Object}'s state to match that of it's associated
+ * {@link V8JavaClassInterceptor}.
+ *
+ * This method will do nothing if the specified V8Object does not have a
+ * Java object handle or Java class interceptor handle.
+ *
+ * @param jsObject V8Object to restore from Java.
+ */
+ public void writeInjectedInterceptor(V8Object jsObject) {
+ Object obj = jsObject.get(V8JavaObjectUtils.JAVA_CLASS_INTERCEPTOR_CONTEXT_HANDLE_ID);
+ if (obj instanceof V8Value && ((V8Value) obj).isUndefined()) {
+ ((V8Value) obj).release();
+ return;
+ }
+ String interceptorAddress = String.valueOf(obj);
+
+ obj = jsObject.get(V8JavaObjectUtils.JAVA_OBJECT_HANDLE_ID);
+ if (obj instanceof V8Value && ((V8Value) obj).isUndefined()) {
+ ((V8Value) obj).release();
+ return;
+ }
+ String objectAddress = String.valueOf(obj);
+
+ Object javaObject = V8JavaCache.identifierToV8ObjectMap.get(objectAddress).get();
+ V8JavaClassInterceptorContext context = interceptContexts.get(interceptorAddress);
+
+ if (javaObject != null && context != null) {
+ // Invoke the injection callback if present.
+ Object function = jsObject.get("onJ2V8Inject");
+ if (function instanceof V8Function) {
+ // Despite being unchecked, we can guarantee that this is correct so long as the provided
+ // interceptor is of the correct type. TODO: Maybe we could add an assert on the interceptor type?
+ try {
+ interceptor.onInject(context, classy.cast(javaObject));
+ } catch (Exception e) {
+ e.printStackTrace();
+ if (function instanceof V8Value) {
+ ((V8Value) function).release();
+ }
+ return;
+ }
+ V8Array args = V8JavaObjectUtils.translateJavaArgumentsToJavascript(new Object[] {context}, V8JavaObjectUtils.getRuntimeSarcastically(jsObject));
+ ((V8Function) function).call(jsObject, args);
+ args.release();
+ }
+
+ // Clean up.
+ if (function instanceof V8Value) {
+ ((V8Value) function).release();
+ }
+ } else {
+ System.err.println("omigod");
+ }
+ }
+
+ /**
+ * Restores the Java state of a {@link V8Object} that was intercepted
+ * by an {@link V8JavaClassInterceptor}.
+ *
+ * This method will do nothing if the specified V8Object does not have a
+ * Java object handle or Java class interceptor handle.
+ *
+ * @param jsObject V8Object to restore to Java.
+ */
+ public void readInjectedInterceptor(V8Object jsObject) {
+ Object obj = jsObject.get(V8JavaObjectUtils.JAVA_CLASS_INTERCEPTOR_CONTEXT_HANDLE_ID);
+ if (obj instanceof V8Value && ((V8Value) obj).isUndefined()) {
+ ((V8Value) obj).release();
+ return;
+ }
+ String interceptorAddress = String.valueOf(obj);
+
+ obj = jsObject.get(V8JavaObjectUtils.JAVA_OBJECT_HANDLE_ID);
+ if (obj instanceof V8Value && ((V8Value) obj).isUndefined()) {
+ ((V8Value) obj).release();
+ return;
+ }
+ String objectAddress = String.valueOf(obj);
+
+ Object javaObject = V8JavaCache.identifierToV8ObjectMap.get(objectAddress).get();
+ V8JavaClassInterceptorContext context = interceptContexts.get(interceptorAddress);
+
+ if (javaObject != null && context != null) {
+ // Invoke the injection callback if present.
+ Object function = jsObject.get("onJ2V8Extract");
+ if (function instanceof V8Function) {
+ V8Array args = V8JavaObjectUtils.translateJavaArgumentsToJavascript(new Object[] {context}, V8JavaObjectUtils.getRuntimeSarcastically(jsObject));
+ ((V8Function) function).call(jsObject, args);
+ args.release();
+
+ // Despite being unchecked, we can guarantee that this is correct so long as the provided
+ // interceptor is of the correct type. TODO: Maybe we could add an assert on the interceptor type?
+ try {
+ interceptor.onExtract(context, classy.cast(javaObject));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Clean up.
+ if (function instanceof V8Value) {
+ ((V8Value) function).release();
+ }
+ } else {
+ System.err.println("omigod");
+ }
+ }
+
+ /**
+ * Attaches a Java object to a JS object, treating the JS object as if it
+ * were a proxy for the Java object.
+ *
+ * @param javaObject Java object to attach.
+ * @param jsObject JS object to attach to.
+ *
+ * @return String identifier for the final java script object.
+ *
+ * @throws IllegalArgumentException If the passed object is not an instance of the class this proxy represents.
+ */
+ public String attachJavaObjectToJsObject(Object javaObject, V8Object jsObject) throws IllegalArgumentException {
+ if (javaObject.getClass().equals(classy)) {
+ // Register its methods as properties on itself if it doesn't have an interceptor.
+ if (interceptor == null) {
+ for (String m : instanceMethods.keySet()) {
+ jsObject.registerJavaMethod(instanceMethods.get(m).getCallbackForInstance(javaObject), m);
+ }
+
+ // Otherwise, register the interceptor's callback information.
+ } else {
+ String interceptorAddress = "CICHID" + UUID.randomUUID().toString().replaceAll("-", "");
+ jsObject.add(V8JavaObjectUtils.JAVA_CLASS_INTERCEPTOR_CONTEXT_HANDLE_ID, interceptorAddress);
+ V8JavaClassInterceptorContext context = new V8JavaClassInterceptorContext();
+ interceptContexts.put(interceptorAddress, context);
+
+ // Invoke the injection callback if present.
+ Object function = jsObject.get("onJ2V8Inject");
+ if (function instanceof V8Function) {
+ // Despite being unchecked, we can guarantee that this is correct so long as the provided
+ // interceptor is of the correct type. TODO: Maybe we could add an assert on the interceptor type?
+ interceptor.onInject(context, classy.cast(javaObject));
+ V8Array args = V8JavaObjectUtils.translateJavaArgumentsToJavascript(new Object[] {context}, V8JavaObjectUtils.getRuntimeSarcastically(jsObject));
+ ((V8Function) function).call(jsObject, args);
+ args.release();
+ }
+
+ // Clean up.
+ if (function instanceof V8Value) {
+ ((V8Value) function).release();
+ }
+ }
+
+ //Register the object's handle.
+ String instanceAddress = "OHID" + UUID.randomUUID().toString().replaceAll("-", "");
+ jsObject.add(V8JavaObjectUtils.JAVA_OBJECT_HANDLE_ID, instanceAddress);
+ WeakReference reference = new WeakReference(javaObject);
+ V8JavaCache.identifierToV8ObjectMap.put(instanceAddress, reference);
+ V8JavaCache.v8ObjectToIdentifierMap.put(javaObject, instanceAddress);
+
+ //Add a handle to the object on the V8 context.
+ V8JavaObjectUtils.getRuntimeSarcastically(jsObject).add(instanceAddress, jsObject);
+
+ return instanceAddress;
+ } else {
+ throw new IllegalArgumentException(String.format("Cannot attach Java object of type [%s] using proxy for type [%s]",
+ javaObject.getClass().getName(), classy.getName()));
+ }
+ }
+
+ /**
+ * Creates a new Java object representing the type associated with this proxy.
+ *
+ * @param receiver Java Script object that will represent the Java object.
+ * @param parameters Parameters to use when constructing the Java object.
+ */
+ @Override public Object invoke(V8Object receiver, V8Array parameters) {
+ //Attempt to discover a matching constructor for the arguments we've been passed.
+ Object[] coercedArguments = null;
+ Constructor coercedConstructor = null;
+ for (Constructor constructor : classy.getConstructors()) {
+ try {
+ coercedArguments = V8JavaObjectUtils.translateJavascriptArgumentsToJava(constructor.isVarArgs(), constructor.getParameterTypes(), parameters, receiver);
+ coercedConstructor = constructor;
+ break;
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ if (coercedArguments == null) {
+ throw new IllegalArgumentException("No constructor exists for " + classy.getName() + " with specified arguments.");
+ }
+
+ try {
+ final Object instance = coercedConstructor.newInstance(coercedArguments);
+ attachJavaObjectToJsObject(instance, receiver);
+
+ // TODO: Is this the best way to handle cleanup of Java objects for garbage collection?
+ // Give it the ability to release itself.
+ jsObjects.add(instance);
+ receiver.registerJavaMethod(new JavaCallback() {
+ @Override public Object invoke(V8Object receiver, V8Array parameters) {
+ // Dispose of any references to allow for garbage collection.
+ jsObjects.remove(instance);
+ return receiver;
+ }
+ }, "$release");
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException("Constructor received invalid arguments!");
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Constructor received invalid arguments!");
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException("Constructor received invalid arguments!");
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java
new file mode 100644
index 000000000..384f5564a
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Proxies an instance method of a Java class and makes it callable from the V8 context.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+final class V8JavaInstanceMethodProxy extends V8JavaMethodProxy {
+ public V8JavaInstanceMethodProxy(String name) {
+ super(name);
+ }
+
+ public JavaCallback getCallbackForInstance(final Object o) {
+ return new JavaCallback() {
+ @Override public Object invoke(V8Object receiver, V8Array parameters) {
+ //See if a method exists.
+ Object[] coercedArguments = null;
+ Method coercedMethod = null;
+ for (Method method : getMethodSignatures()) {
+ try {
+ coercedArguments = V8JavaObjectUtils.translateJavascriptArgumentsToJava(method.isVarArgs(), method.getParameterTypes(), parameters, receiver);
+ coercedMethod = method;
+ break;
+ } catch (IllegalArgumentException e) {
+ //TODO: Exception to manage flow here is abysmal. Some critical information is being ignored which is unacceptable.
+ }
+ }
+
+ if (coercedArguments == null) {
+ StringBuilder errorMessage = new StringBuilder("No signature exists for ");
+ errorMessage.append(getMethodName());
+ errorMessage.append(" with parameters [");
+ for (int i = 0; i < parameters.length(); i++) {
+ Object obj = parameters.get(i);
+ errorMessage.append(String.valueOf(parameters.get(i))).append(", ");
+ if (obj instanceof V8Value) {
+ ((V8Value) obj).release();
+ }
+ }
+ errorMessage.append("].");
+ throw new IllegalArgumentException(errorMessage.toString());
+ }
+
+ //Invoke the method.
+ try {
+ return V8JavaObjectUtils.translateJavaArgumentToJavascript(coercedMethod.invoke(o, coercedArguments), V8JavaObjectUtils.getRuntimeSarcastically(receiver));
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Method received invalid arguments [" + e.getMessage() + "]!");
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java
new file mode 100644
index 000000000..49e9ab82c
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Generic base for proxying static and instance Java methods.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+abstract class V8JavaMethodProxy {
+//Private//////////////////////////////////////////////////////////////////////
+
+ private final String name;
+ private final List methodSignatures = new ArrayList();
+
+//Public///////////////////////////////////////////////////////////////////////
+
+ public V8JavaMethodProxy(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Associates a new Java method signature with this proxy.
+ *
+ * @param method Method signature to add.
+ */
+ public void addMethodSignature(Method method) {
+ methodSignatures.add(method);
+ }
+
+ /**
+ * @return Unmodifiable list of all possible argument signatures (methods)
+ * for the Java method represented by this proxy.
+ */
+ public List getMethodSignatures() {
+ return Collections.unmodifiableList(methodSignatures);
+ }
+
+ /**
+ * @return Name of the Java method represented by this proxy.
+ */
+ public String getMethodName() {
+ return name;
+ }
+}
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java b/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java
new file mode 100644
index 000000000..9bc790822
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java
@@ -0,0 +1,575 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * Utilities for translating individual Java objects to and from V8.
+ *
+ * This class differs from {@link com.eclipsesource.v8.utils.V8ObjectUtils}
+ * in that it bridges individual Java objects to and from a V8 runtime,
+ * not entire lists or arrays of objects.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+public final class V8JavaObjectUtils {
+//Private//////////////////////////////////////////////////////////////////////
+
+ /**
+ * Super hax0r map used when comparing primitives and their boxed
+ * counterparts.
+ */
+ private static final Map, Class>> BOXED_PRIMITIVE_MAP = new HashMap, Class>>() {
+ @Override public Class> get(Object classy) {
+ if (containsKey(classy)) {
+ return super.get(classy);
+ } else {
+ return (Class>) classy;
+ }
+ }
+ }; static {
+ BOXED_PRIMITIVE_MAP.put(boolean.class, Boolean.class);
+ BOXED_PRIMITIVE_MAP.put(short.class, Short.class);
+ BOXED_PRIMITIVE_MAP.put(int.class, Integer.class);
+ BOXED_PRIMITIVE_MAP.put(long.class, Long.class);
+ BOXED_PRIMITIVE_MAP.put(float.class, Float.class);
+ BOXED_PRIMITIVE_MAP.put(double.class, Double.class);
+ }
+
+ /**
+ * Returns true if the passed object is primitive in respect to V8.
+ */
+ private static boolean isBasicallyPrimitive(Object object) {
+ return object instanceof V8Value ||
+ object instanceof String ||
+ object instanceof Boolean ||
+ object instanceof Short ||
+ object instanceof Integer ||
+ object instanceof Long ||
+ object instanceof Float ||
+ object instanceof Double;
+ }
+
+ /**
+ * List of {@link V8Value}s held by this class or one of its delegates.
+ */
+ private static final List> v8Resources = new ArrayList>();
+
+ /**
+ * Lightweight invocation handler for translating certain V8 functions to
+ * Java functional interfaces.
+ */
+ private static class V8FunctionInvocationHandler implements InvocationHandler {
+ private final V8Object receiver;
+ private final V8Function function;
+
+ @Override protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable t) { }
+
+ if (!receiver.isReleased()) {
+ receiver.release();
+ }
+
+ if (!function.isReleased()) {
+ function.release();
+ }
+ }
+
+ public V8FunctionInvocationHandler(V8Object receiver, V8Function function) {
+ this.receiver = receiver.twin();
+ this.function = function.twin();
+ v8Resources.add(new WeakReference(this.receiver));
+ v8Resources.add(new WeakReference(this.function));
+ }
+
+ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ V8Array v8Args = translateJavaArgumentsToJavascript(args, V8JavaObjectUtils.getRuntimeSarcastically(receiver));
+ Object obj = function.call(receiver, v8Args);
+ if (!v8Args.isReleased()) {
+ v8Args.release();
+ }
+
+ if (obj instanceof V8Object) {
+ V8Object v8Obj = ((V8Object) obj);
+ if (!v8Obj.isUndefined()) {
+ Object ret = V8JavaCache.identifierToV8ObjectMap.get(v8Obj.get(JAVA_OBJECT_HANDLE_ID).toString()).get();
+ v8Obj.release();
+ return ret;
+ } else {
+ v8Obj.release();
+ return null;
+ }
+ } else {
+ return obj;
+ }
+ } catch (Throwable t) {
+ throw t;
+ }
+ }
+
+ public String toString() {
+ return function.toString();
+ }
+ }
+
+//Public///////////////////////////////////////////////////////////////////////
+
+ /**
+ * Variable name used when attaching a Java object ID to a JS object.
+ */
+ public static final String JAVA_OBJECT_HANDLE_ID = "____JavaObjectHandleID____";
+
+ /**
+ * Variable name used when attaching an interceptor context to a JS object.
+ */
+ public static final String JAVA_CLASS_INTERCEPTOR_CONTEXT_HANDLE_ID = "____JavaClassInterceptorContextHandleID____";
+
+ /**
+ * Attempts to convert the given array into it's primitive counterpart.
+ *
+ * @param array Array to convert.
+ * @param type Boxed type of the array to convert.
+ *
+ * @return Primitive version of the given array, or the original array
+ * if no primitive type matched the passed type.
+ */
+ public static Object toPrimitiveArray(Object[] array, Class> type) {
+ if (Boolean.class.equals(type)) {
+ boolean[] ret = new boolean[array.length];
+ for (int i = 0; i < array.length; i++) {
+ ret[i] = (Boolean) array[i];
+ }
+ return ret;
+ } else if (Byte.class.equals(type)) {
+ byte[] ret = new byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ ret[i] = (Byte) array[i];
+ }
+ return ret;
+ } else if (Short.class.equals(type)) {
+ short[] ret = new short[array.length];
+ for (int i = 0; i < array.length; i++) {
+ ret[i] = (Short) array[i];
+ }
+ return ret;
+ } else if (Integer.class.equals(type)) {
+ int[] ret = new int[array.length];
+ for (int i = 0; i < array.length; i++) {
+ ret[i] = (Integer) array[i];
+ }
+ return ret;
+ } else if (Long.class.equals(type)) {
+ long[] ret = new long[array.length];
+ for (int i = 0; i < array.length; i++) {
+ ret[i] = (Long) array[i];
+ }
+ return ret;
+ } else if (Float.class.equals(type)) {
+ float[] ret = new float[array.length];
+ for (int i = 0; i < array.length; i++) {
+ ret[i] = (Float) array[i];
+ }
+ return ret;
+ } else if (Double.class.equals(type)) {
+ double[] ret = new double[array.length];
+ for (int i = 0; i < array.length; i++) {
+ ret[i] = (Double) array[i];
+ }
+ return ret;
+ }
+
+ return array;
+ }
+
+ /**
+ * Attempts to widen a given number to work with the specified class.
+ *
+ * TODO: Surely there's a cleaner way to write this!
+ *
+ * @param from Number to widen.
+ * @param to Class to widen to.
+ *
+ * @return A widened version of the passed number, or null if no
+ * possible solutions existed for widening.
+ */
+ @SuppressWarnings("unchecked")
+ public static T widenNumber(Object from, Class to) {
+ if (from.getClass().equals(to)) {
+ return (T) from;
+ }
+
+ if (from instanceof Short) {
+ if (to == Short.class || to == short.class) {
+ return (T) from;
+ } else if (to == Integer.class || to == int.class) {
+ return (T) new Integer((Short) from);
+ } else if (to == Long.class || to == long.class) {
+ return (T) new Long((Short) from);
+ } else if (to == Float.class || to == float.class) {
+ return (T) new Float((Short) from);
+ } else if (to == Double.class || to == double.class) {
+ return (T) new Double((Short) from);
+ }
+ } else if (from instanceof Integer) {
+ if (to == Integer.class || to == int.class) {
+ return (T) from;
+ } else if (to == Long.class || to == long.class) {
+ return (T) new Long((Integer) from);
+ } else if (to == Float.class || to == float.class) {
+ return (T) new Float((Integer) from);
+ } else if (to == Double.class || to == double.class) {
+ return (T) new Double((Integer) from);
+ }
+ } else if (from instanceof Long) {
+ if (to == Long.class || to == long.class) {
+ return (T) from;
+ } else if (to == Float.class || to == float.class) {
+ return (T) new Float((Long) from);
+ } else if (to == Double.class || to == double.class) {
+ return (T) new Double((Long) from);
+ }
+ } else if (from instanceof Float) {
+ if (to == Float.class || to == float.class) {
+ return (T) from;
+ } else if (to == Double.class || to == double.class) {
+ return (T) new Double((Float) from);
+ }
+ } else if (from instanceof Double) {
+ if (to == Double.class || to == double.class) {
+ return (T) from;
+ }
+ }
+
+ // Welp, find a default.
+ return null;
+ }
+
+ /**
+ * Ultimate hack method to work around a typo in the V8 libraries for
+ * Android.
+ *
+ * TODO: Report upstream and stop using this dorky method.
+ */
+ public static final V8 getRuntimeSarcastically(V8Value value) {
+ try {
+ return value.getRuntime();
+ } catch (Throwable t) {
+ try {
+ return (V8) value.getClass().getMethod("getRutime", new Class[0]).invoke(value);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Releases all V8 resources held by this class for a particular runtime.
+ *
+ * This method should only be called right before a V8 runtime is being
+ * released, or else some resources created by this utility class will
+ * fail to keep working.
+ *
+ * @param v8 V8 instance to release resources for.
+ *
+ * @return Number of resources that were released.
+ */
+ public static int releaseV8Resources(V8 v8) {
+ int released = 0;
+
+ for (Iterator> iterator = v8Resources.iterator(); iterator.hasNext();) {
+ V8Value resource = iterator.next().get();
+ if (resource != null) {
+ if (V8JavaObjectUtils.getRuntimeSarcastically(resource) == v8) {
+ resource.release();
+ iterator.remove();
+ released++;
+ }
+ } else {
+ iterator.remove();
+ }
+ }
+
+ return released;
+ }
+
+ /**
+ * Translates a single Java object into an equivalent V8Value.
+ *
+ * @param javaArgument Java argument to translate.
+ * @param v8 V8 runtime that will be receiving the translated Java argument.
+ *
+ * @return Translated object.
+ */
+ public static Object translateJavaArgumentToJavascript(Object javaArgument, V8 v8) {
+ if (javaArgument != null) {
+ if (isBasicallyPrimitive(javaArgument)) {
+ return javaArgument;
+ } else {
+ String key = V8JavaCache.v8ObjectToIdentifierMap.get(javaArgument);
+ if (key != null) {
+ V8Object object = (V8Object) v8.get(key);
+ V8JavaCache.cachedV8JavaClasses.get(javaArgument.getClass()).writeInjectedInterceptor(object);
+ return object;
+ } else {
+ key = V8JavaAdapter.injectObject(null, javaArgument, v8);
+ return v8.get(key);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Translates an array of Java arguments to a V8Array.
+ *
+ * @param javaArguments Java arguments to translate.
+ * @param v8 V8 runtime that will be receiving the translated Java arguments.
+ *
+ * @return Translated array.
+ */
+ public static V8Array translateJavaArgumentsToJavascript(Object[] javaArguments, V8 v8) {
+ V8Array v8Args = new V8Array(v8);
+ for (Object argument : javaArguments) {
+ if (argument instanceof V8Value) {
+ v8Args.push((V8Value) argument);
+ } else if (argument instanceof String) {
+ v8Args.push((String) argument);
+ } else if (argument instanceof Boolean) {
+ v8Args.push((Boolean) argument);
+ } else if (argument instanceof Short) {
+ v8Args.push((Short) argument);
+ } else if (argument instanceof Integer) {
+ v8Args.push((Integer) argument);
+ } else if (argument instanceof Long) {
+ v8Args.push((Long) argument);
+ } else if (argument instanceof Float) {
+ v8Args.push((Float) argument);
+ } else if (argument instanceof Double) {
+ v8Args.push((Double) argument);
+ } else {
+ V8Value translatedJavaArgument = (V8Value) translateJavaArgumentToJavascript(argument, v8);
+ v8Args.push(translatedJavaArgument);
+ translatedJavaArgument.release();
+ }
+ }
+
+ return v8Args;
+ }
+
+ /**
+ * Translates a single element from a V8Array to an Object based on a given Java argument type.
+ *
+ * It is the responsibility of the caller of this method to invoke {@link V8Value#release()} on
+ * any objects passed to this method; this method will not make an effort to release them.
+ *
+ * @param javaArgumentType Java type that the argument must match.
+ * @param argument Argument to translate to Java.
+ * @param receiver V8Object receiver that any functional arguments should be tied to.
+ *
+ * @return Translated Object based on the passed Java types and and Javascript value.
+ *
+ * @throws IllegalArgumentException if the Javascript value could not be coerced in the types
+ * specified by te passed array of java argument types.
+ */
+ public static Object translateJavascriptArgumentToJava(Class> javaArgumentType, Object argument, V8Object receiver) throws IllegalArgumentException {
+ if (argument instanceof V8Value) {
+ if (argument instanceof V8Function) {
+ if (javaArgumentType.isInterface() && javaArgumentType.getDeclaredMethods().length == 1) {
+ //Create a proxy class for the functional interface that wraps this V8Function.
+ V8FunctionInvocationHandler handler = new V8FunctionInvocationHandler(receiver, (V8Function) argument);
+ return Proxy.newProxyInstance(javaArgumentType.getClassLoader(), new Class[] { javaArgumentType }, handler);
+ } else {
+ throw new IllegalArgumentException(
+ "Method was passed V8Function but does not accept a functional interface.");
+ }
+ } else if (argument instanceof V8Array) {
+ if (javaArgumentType.isArray()) {
+ // Perform a single cast up front.
+ V8Array v8Array = (V8Array) argument;
+
+ // TODO: This logic is almost identical to the varargs manipulation logic. Maybe we can reuse it?
+ Class> originalArrayType = javaArgumentType.getComponentType();
+ Class> arrayType = originalArrayType;
+ if (BOXED_PRIMITIVE_MAP.containsKey(arrayType)) {
+ arrayType = BOXED_PRIMITIVE_MAP.get(arrayType);
+ }
+ Object[] array = (Object[]) Array.newInstance(arrayType, v8Array.length());
+
+ for (int i = 0; i < array.length; i++) {
+ // We have to release the value immediately after using it if it's a V8Value.
+ Object arrayElement = v8Array.get(i);
+ try {
+ array[i] = translateJavascriptArgumentToJava(javaArgumentType.getComponentType(),
+ arrayElement, receiver);
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } finally {
+ if (arrayElement instanceof V8Value) {
+ ((V8Value) arrayElement).release();
+ }
+ }
+ }
+
+ if (BOXED_PRIMITIVE_MAP.containsKey(originalArrayType) && BOXED_PRIMITIVE_MAP.containsValue(arrayType)) {
+ return toPrimitiveArray(array, arrayType);
+ } else {
+ return array;
+ }
+ } else {
+ throw new IllegalArgumentException("Method was passed a V8Array but does not accept arrays.");
+ }
+ } else if (argument instanceof V8Object) {
+ try {
+ //Attempt to retrieve a Java object handle.
+ String javaHandle = (String) ((V8Object) argument).get(JAVA_OBJECT_HANDLE_ID);
+ Object javaObject = V8JavaCache.identifierToV8ObjectMap.get(javaHandle).get();
+
+ if (javaObject != null) {
+ if (javaArgumentType.isAssignableFrom(javaObject.getClass())) {
+ // Check if it's intercepted.
+ V8JavaCache.cachedV8JavaClasses.get(javaObject.getClass()).readInjectedInterceptor(
+ (V8Object) argument);
+ return javaObject;
+ } else {
+ throw new IllegalArgumentException(
+ "Argument is Java type but does not match signature for this method.");
+ }
+ } else {
+ V8JavaCache.removeGarbageCollectedJavaObjects();
+ throw new IllegalArgumentException(
+ "Argument has invalid Java object handle or object referenced by handle has aged out.");
+ }
+ } catch (NullPointerException e) {
+ throw new IllegalArgumentException(
+ "Argument has invalid Java object handle or object referenced by handle has aged out.");
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Complex objects can only be passed to Java if they represent Java objects.");
+ }
+ } else {
+ //TODO: Add support for arrays.
+ throw new IllegalArgumentException(
+ "Translation of JS to Java arguments is only supported for primitives, objects, arrays and functions.");
+ }
+ } else {
+ if (javaArgumentType.isAssignableFrom(argument.getClass()) ||
+ BOXED_PRIMITIVE_MAP.get(argument.getClass())
+ .isAssignableFrom(BOXED_PRIMITIVE_MAP.get(javaArgumentType))) {
+ return argument;
+ } else {
+ Object widened = widenNumber(argument, javaArgumentType);
+ if (widened != null) {
+ return widened;
+ } else {
+ throw new IllegalArgumentException(
+ "Primitive argument cannot be coerced to expected parameter type.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Translates a V8Array of arguments to an Object array based on a set of Java argument types.
+ *
+ * @param isVarArgs Whether or not the Java parameters list ends in a varargs array.
+ * @param javaArgumentTypes Java types that the arguments must match.
+ * @param javascriptArguments Arguments to translate to Java.
+ * @param receiver V8Object receiver that any functional arguments should be tied to.
+ *
+ * @return Translated Object array of arguments based on the passed Java types and V8Array.
+ *
+ * @throws IllegalArgumentException if the V8Array could not be coerced into the types specified
+ * by the passed array of Java argument types.
+ */
+ public static Object[] translateJavascriptArgumentsToJava(boolean isVarArgs, Class>[] javaArgumentTypes, V8Array javascriptArguments, V8Object receiver) throws IllegalArgumentException {
+ // Varargs handling.
+ if (isVarArgs && javaArgumentTypes.length > 0 &&
+ javaArgumentTypes[javaArgumentTypes.length - 1].isArray() &&
+ javascriptArguments.length() >= javaArgumentTypes.length - 1) {
+ Class> originalVarargsType = javaArgumentTypes[javaArgumentTypes.length - 1].getComponentType();
+ Class> varargsType = originalVarargsType;
+ if (BOXED_PRIMITIVE_MAP.containsKey(varargsType)) {
+ varargsType = BOXED_PRIMITIVE_MAP.get(varargsType);
+ }
+ Object[] varargs = (Object[]) Array.newInstance(varargsType, javascriptArguments.length() - javaArgumentTypes.length + 1);
+ Object[] returnedArgumentValues = new Object[javaArgumentTypes.length];
+
+ for (int i = 0; i < javascriptArguments.length(); i++) {
+ Object argument = javascriptArguments.get(i);
+
+ try {
+ // If we haven't hit the varargs yet, insert normally.
+ if (returnedArgumentValues.length - 1 > i) {
+ returnedArgumentValues[i] =
+ translateJavascriptArgumentToJava(javaArgumentTypes[i],
+ argument, receiver);
+
+ // Otherwise insert into the varargs.
+ } else {
+ varargs[i - (returnedArgumentValues.length - 1)] =
+ translateJavascriptArgumentToJava(varargsType, argument, receiver);
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } finally {
+ if (argument instanceof V8Value) {
+ ((V8Value) argument).release();
+ }
+ }
+ }
+
+ // Convert any boxed primitives to actual primitives IF the original varargs type was a primitive..
+ if (BOXED_PRIMITIVE_MAP.containsKey(originalVarargsType) && BOXED_PRIMITIVE_MAP.containsValue(varargsType)) {
+ returnedArgumentValues[returnedArgumentValues.length - 1] = toPrimitiveArray(varargs, varargsType);
+ } else {
+ returnedArgumentValues[returnedArgumentValues.length - 1] = varargs;
+ }
+
+ return returnedArgumentValues;
+
+ // Typical handling.
+ } else if (javaArgumentTypes.length == javascriptArguments.length()) {
+ Object[] returnedArgumentValues = new Object[javaArgumentTypes.length];
+
+ for (int i = 0; i < javascriptArguments.length(); i++) {
+ Object argument = javascriptArguments.get(i);
+ try {
+ returnedArgumentValues[i] = translateJavascriptArgumentToJava(javaArgumentTypes[i], argument,
+ receiver);
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } finally {
+ if (argument instanceof V8Value) {
+ ((V8Value) argument).release();
+ }
+ }
+ }
+
+ return returnedArgumentValues;
+ } else {
+ throw new IllegalArgumentException(
+ "Method arguments size and passed arguments size do not match.");
+ }
+ }
+}
diff --git a/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java
new file mode 100644
index 000000000..29a14717a
--- /dev/null
+++ b/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Proxies a static method of a Java class and makes it available to the V8 runtime.
+ *
+ * @author Brandon Sanders [brandon@alicorn.io]
+ */
+class V8JavaStaticMethodProxy extends V8JavaMethodProxy implements JavaCallback {
+//Private//////////////////////////////////////////////////////////////////////
+
+//Public///////////////////////////////////////////////////////////////////////
+
+ public V8JavaStaticMethodProxy(String name) {
+ super(name);
+ }
+
+ @Override public Object invoke(V8Object receiver, V8Array parameters) {
+ //See if a method exists.
+ Object[] coercedArguments = null;
+ Method coercedMethod = null;
+ for (Method method : getMethodSignatures()) {
+ try {
+ coercedArguments = V8JavaObjectUtils.translateJavascriptArgumentsToJava(method.isVarArgs(), method.getParameterTypes(), parameters, receiver);
+ coercedMethod = method;
+ break;
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ if (coercedArguments == null) {
+ throw new IllegalArgumentException("No method exists for specified parameters.");
+ }
+
+ //Invoke the method.
+ try {
+ return V8JavaObjectUtils.translateJavaArgumentToJavascript(coercedMethod.invoke(coercedArguments), V8JavaObjectUtils.getRuntimeSarcastically(receiver));
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Method received invalid arguments!");
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ throw new IllegalArgumentException("Method received invalid arguments!");
+ }
+ }
+}
diff --git a/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java b/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java
new file mode 100644
index 000000000..945694ac8
--- /dev/null
+++ b/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ConcurrentV8Test {
+
+ public static class Foo {
+ final int val;
+
+ public Foo(int val) {
+ this.val = val;
+ }
+
+ public int getThing() {
+ return 3344;
+ }
+
+ public int getVal() {
+ return val;
+ }
+
+ public void whine() throws Exception {
+ throw new Exception("Whaaa!");
+ }
+ }
+
+ int temp = 0;
+
+ @Test
+ public void shouldShareV8AcrossThreads() {
+ final ConcurrentV8 v8 = new ConcurrentV8();
+
+ Thread thread1 = new Thread(new Runnable() {
+ @Override public void run() {
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override
+ public void run(V8 v8) {
+ v8.executeVoidScript("var i = 3000;");
+ }
+ });
+ }
+ });
+
+ thread1.start();
+ try {
+ thread1.join();
+ } catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+
+ Thread thread2 = new Thread(new Runnable() {
+ @Override public void run() {
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) {
+ v8.executeVoidScript("i += 344;");
+ }
+ });
+ }
+ });
+
+ thread2.start();
+ try {
+ thread2.join();
+ } catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ Assert.assertEquals(3344, v8.executeIntegerScript("i"));
+ }
+ });
+
+ v8.release();
+ }
+
+ @Test
+ public void shouldShareInjectedObjectsAndClassesAcrossThreads() {
+ ConcurrentV8 v8 = new ConcurrentV8();
+
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ V8JavaAdapter.injectClass(Foo.class, v8);
+ }
+ });
+
+ temp = 0;
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ temp = v8.executeIntegerScript("var x = new Foo(30).$release(); x.getThing();");
+ }
+ });
+ Assert.assertEquals(3344, temp);
+
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ V8JavaAdapter.injectObject("fooey", new Foo(9001), v8);
+ }
+ });
+
+ temp = 0;
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ temp = v8.executeIntegerScript("fooey.getVal();");
+ }
+ });
+ Assert.assertEquals(9001, temp);
+
+ v8.release();
+ }
+
+ @Test
+ public void shouldHandleExceptionsLoudlyAndQuietly() {
+ ConcurrentV8 v8 = new ConcurrentV8();
+
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ V8JavaAdapter.injectClass(Foo.class, v8);
+ }
+ });
+
+ try {
+ v8.run(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ v8.executeScript("var x = new Foo(33).$release(); x.whine();");
+ }
+ });
+
+ Assert.fail("Regular concurrent V8 invocations should pass on exceptions.");
+ } catch (Throwable e) { }
+
+ try {
+ v8.runQuietly(new ConcurrentV8Runnable() {
+ @Override public void run(V8 v8) throws Exception {
+ v8.executeScript("var x = new Foo(33).$release(); x.whine();");
+ }
+ });
+ } catch (Throwable e) {
+ Assert.fail("Quiet concurrent V8 invocations should suppress exceptions.");
+ }
+
+ v8.release();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java b/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java
new file mode 100644
index 000000000..026bdd663
--- /dev/null
+++ b/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Brandon Sanders
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brandon Sanders - initial API and implementation and/or initial documentation
+ ******************************************************************************/
+package com.eclipsesource.v8;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Random;
+
+public class V8JavaAdapterTest {
+//Setup classes////////////////////////////////////////////////////////////////
+ private interface Baz {
+ Foo doFooInterface(Foo foo);
+ }
+
+ private interface Bar {
+ int doInterface(int args);
+ }
+
+ private static final class Foo {
+ public int i;
+ public Foo(int i) { this.i = i; }
+ public static int doStatic() { return 9001; }
+ public int doInstance(int i) { return this.i + i; }
+ public int doInstance(int i, int i2) { return this.i + (i * i2); }
+ public void add(Foo foo) { this.i += foo.i; }
+ public void add(Bar bar) { this.i = bar.doInterface(this.i); }
+ public void addBaz(Baz baz) { this.i = baz.doFooInterface(this).getI(); }
+ public String doString(String s) { return s; }
+ public int getI() { return i; }
+ public Foo copy() { return new Foo(i); }
+ public int doArray(int[] a) {
+ int ret = 0;
+ for (int i = 0; i < a.length; i++) {
+ ret += a[i];
+ }
+ return ret;
+ }
+ public int doNArray(int[][] a) {
+ int ret = 0;
+ for (int i = 0; i < a.length; i++) {
+ ret += doArray(a[i]);
+ }
+ return ret;
+ }
+ public int doVarargs(int a, int b, int... c) {
+ int ret = a + b;
+ for (int i = 0; i < c.length; i++) {
+ ret += c[i];
+ }
+ return ret;
+ }
+ }
+
+ private static final class InterceptableFoo {
+ public int i;
+ public InterceptableFoo(int i) { this.i = i; }
+ public void add(int i) { this.i += i; }
+ public int getI() { return i; }
+ public void setI(int i) { this.i = i; }
+ }
+
+ private static final class FooInterceptor implements V8JavaClassInterceptor {
+
+ @Override public String getConstructorScriptBody() {
+ return "var i = 0;\n" +
+ "this.getI = function() { return i; };\n" +
+ "this.setI = function(other) { i = other; };\n" +
+ "this.add = function(other) { i = i + other; };\n" +
+ "this.onJ2V8Inject = function(context) { i = context.get(\"i\"); };\n" +
+ "this.onJ2V8Extract = function(context) { context.set(\"i\", i); };";
+ }
+
+ @Override public void onInject(V8JavaClassInterceptorContext context, InterceptableFoo object) {
+ context.set("i", object.i);
+ }
+
+ @Override public void onExtract(V8JavaClassInterceptorContext context, InterceptableFoo object) {
+ object.i = V8JavaObjectUtils.widenNumber(context.get("i"), Integer.class);
+ }
+ }
+
+ private static final class Fooey {
+ public int i = 0;
+ public Fooey(int i) { this.i = i; }
+ public void doInstance(InterceptableFoo foo) { this.i += foo.getI(); }
+ public void setI(int i) { this.i = i; }
+ public int getI() { return i; }
+ }
+
+//Tests////////////////////////////////////////////////////////////////////////
+
+ private V8 v8;
+
+ @Before
+ public void setup() {
+ v8 = V8.createV8Runtime();
+ V8JavaAdapter.injectClass(Foo.class, v8);
+ }
+
+ @After
+ public void teardown() {
+ V8JavaObjectUtils.releaseV8Resources(v8);
+ V8JavaCache.removeGarbageCollectedJavaObjects();
+ v8.release(true);
+ }
+
+ @Test
+ public void shouldInjectObjects() {
+ V8JavaAdapter.injectObject("bar", new Bar() {
+ @Override public int doInterface(int args) {
+ return args * 2;
+ }
+ }, v8);
+ Assert.assertEquals(10, v8.executeIntegerScript("bar.doInterface(5);"));
+ V8JavaAdapter.injectObject("bar", new Bar() {
+ @Override public int doInterface(int args) {
+ return args * 4;
+ }
+ }, v8);
+ Assert.assertEquals(20, v8.executeIntegerScript("bar.doInterface(5);"));
+ }
+
+ @Test
+ public void shouldInjectClasses() {
+ int i = new Random().nextInt(1000);
+ Assert.assertEquals(i, v8.executeIntegerScript(String.format("var x = new Foo(%d).$release(); x.getI();", i)));
+ }
+
+ @Test
+ public void shouldHandleStaticInvocations() {
+ Assert.assertEquals(9001, v8.executeIntegerScript("Foo.doStatic();"));
+ }
+
+ @Test
+ public void shouldHandleInstanceInvocations() {
+ Assert.assertEquals(3344, v8.executeIntegerScript("var x = new Foo(3300).$release(); x.doInstance(44);"));
+ Assert.assertEquals(9000, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.doInstance(3000, 2);"));
+ }
+
+ @Test
+ public void shouldHandleComplexArguments() {
+ Assert.assertEquals(3344, v8.executeIntegerScript("var x = new Foo(3300).$release(); x.add(new Foo(44).$release()); x.getI();"));
+ }
+
+ @Test
+ public void shouldHandleFunctionalArguments() {
+ Assert.assertEquals(1500, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.add(function(i) { return i / 2; }); x.getI();"));
+ Assert.assertEquals(1500, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.addBaz(function(foo) { return new Foo(foo.getI() / 2).$release(); }); x.getI();"));
+ }
+
+ @Test
+ public void shouldHandleComplexReturnTypes() {
+ int i = new Random().nextInt(1000);
+ Assert.assertEquals(i, v8.executeIntegerScript(String.format("var x = new Foo(%d).$release(); x.copy().getI();", i)));
+ }
+
+ @Test
+ public void shouldHandleStringArguments() {
+ Assert.assertEquals("aStringArgument", v8.executeStringScript("var x = new Foo(9001).$release(); x.doString(\"aStringArgument\");"));
+ }
+
+ @Test
+ public void shouldHandleObjectArrays() {
+ V8JavaAdapter.injectObject("objectArray", new String[] {"Hello", "World"}, v8);
+ Assert.assertEquals("Hello", v8.executeStringScript("objectArray.get(0)"));
+ Assert.assertEquals("World", v8.executeStringScript("objectArray.get(1)"));
+ }
+
+ @Test
+ public void shouldInterceptClasses() {
+ V8JavaAdapter.injectClass(InterceptableFoo.class, new FooInterceptor(), v8);
+ Assert.assertEquals(3344, v8.executeIntegerScript("var x = new InterceptableFoo(0).$release(); x.add(3344); x.getI();"));
+ V8JavaAdapter.injectObject("bazz", new Fooey(3000), v8);
+ Assert.assertEquals(9001, v8.executeIntegerScript("x.setI(6001); bazz.doInstance(x); bazz.getI();"));
+ InterceptableFoo foo = new InterceptableFoo(4444);
+ V8JavaAdapter.injectObject("foobar", foo, v8);
+ Assert.assertEquals(8888, v8.executeIntegerScript("foobar.add(4444); foobar.getI();"));
+ Assert.assertEquals(8888, v8.executeIntegerScript("bazz.setI(0); bazz.doInstance(foobar); bazz.getI();"));
+ Assert.assertEquals(8888, foo.getI());
+ }
+
+ @Test
+ public void shouldHandleVarargs() {
+ Assert.assertEquals(30, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.doVarargs(10, 20);"));
+ Assert.assertEquals(60, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.doVarargs(10, 20, 30);"));
+ Assert.assertEquals(100, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.doVarargs(10, 20, 30, 40);"));
+ }
+
+ @Test
+ public void shouldHandleArrays() {
+ Assert.assertEquals(30, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.doArray([10, 15, 5]);"));
+ Assert.assertEquals(70, v8.executeIntegerScript("var x = new Foo(3000).$release(); x.doNArray([[10, 15], [20, 25]]);"));
+ }
+}
\ No newline at end of file