From 4e4b1a26eea951cf75b53c8ad92b1f1fa38b2dfa Mon Sep 17 00:00:00 2001 From: Brandon Sanders Date: Sun, 25 Sep 2016 12:50:39 -0400 Subject: [PATCH 1/4] Add basic support for sharing Java objects and classes across the J2V8 bridge. --- .gitignore | 3 + .../com/eclipsesource/v8/ConcurrentV8.java | 111 ++++ .../v8/ConcurrentV8Runnable.java | 10 + .../com/eclipsesource/v8/V8JavaAdapter.java | 145 +++++ .../com/eclipsesource/v8/V8JavaCache.java | 43 ++ .../v8/V8JavaClassInterceptor.java | 52 ++ .../v8/V8JavaClassInterceptorContext.java | 51 ++ .../eclipsesource/v8/V8JavaClassProxy.java | 313 ++++++++++ .../v8/V8JavaInstanceMethodProxy.java | 58 ++ .../eclipsesource/v8/V8JavaMethodProxy.java | 48 ++ .../eclipsesource/v8/V8JavaObjectUtils.java | 565 ++++++++++++++++++ .../v8/V8JavaStaticMethodProxy.java | 48 ++ .../eclipsesource/v8/ConcurrentV8Test.java | 146 +++++ .../eclipsesource/v8/V8JavaAdapterTest.java | 195 ++++++ 14 files changed, 1788 insertions(+) create mode 100644 src/main/java/com/eclipsesource/v8/ConcurrentV8.java create mode 100644 src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaAdapter.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaCache.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java create mode 100644 src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java create mode 100644 src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java create mode 100644 src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java 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..8a48888c1 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8.java @@ -0,0 +1,111 @@ +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..60dd1c51d --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java @@ -0,0 +1,10 @@ +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..2de850d86 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java @@ -0,0 +1,145 @@ +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..c922dc549 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaCache.java @@ -0,0 +1,43 @@ +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..2068a9b22 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java @@ -0,0 +1,52 @@ +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..791875991 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java @@ -0,0 +1,51 @@ +// File: V8JavaClassInterceptorContext.java +// Project: Alicorn +// Since: Jun 11, 2016 +// +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Brandon Sanders [brandon@alicorn.io] +// +// All rights reserved. +/////////////////////////////////////////////////////////////////////////////// +// +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..04a319bb3 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java @@ -0,0 +1,313 @@ +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..fdc2934c5 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java @@ -0,0 +1,58 @@ +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..944e0f1e9 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java @@ -0,0 +1,48 @@ +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..435df1a19 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java @@ -0,0 +1,565 @@ +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..8cc9ab591 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java @@ -0,0 +1,48 @@ +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..388579a83 --- /dev/null +++ b/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java @@ -0,0 +1,146 @@ +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..b29e08c80 --- /dev/null +++ b/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java @@ -0,0 +1,195 @@ +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 From 0f9825e8be2bd4ad92cf5c5df30cf683f64b8d5a Mon Sep 17 00:00:00 2001 From: Brandon Sanders Date: Sun, 25 Sep 2016 17:15:32 -0400 Subject: [PATCH 2/4] Removed stray copyright header. --- .../v8/V8JavaClassInterceptorContext.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java index 791875991..56e94f9ca 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java @@ -1,13 +1,3 @@ -// File: V8JavaClassInterceptorContext.java -// Project: Alicorn -// Since: Jun 11, 2016 -// -/////////////////////////////////////////////////////////////////////////////// -// Copyright (c) Brandon Sanders [brandon@alicorn.io] -// -// All rights reserved. -/////////////////////////////////////////////////////////////////////////////// -// package com.eclipsesource.v8; import java.util.HashMap; From 7ff34dde7ae2d3d6275388b46ec0f925fdcb2fc0 Mon Sep 17 00:00:00 2001 From: Brandon Sanders Date: Tue, 27 Sep 2016 10:24:55 -0400 Subject: [PATCH 3/4] Wasn't sure what kind of copyright header to use; added basic eclipse one to all new files. --- src/main/java/com/eclipsesource/v8/ConcurrentV8.java | 10 ++++++++++ .../com/eclipsesource/v8/ConcurrentV8Runnable.java | 10 ++++++++++ src/main/java/com/eclipsesource/v8/V8JavaAdapter.java | 10 ++++++++++ src/main/java/com/eclipsesource/v8/V8JavaCache.java | 10 ++++++++++ .../com/eclipsesource/v8/V8JavaClassInterceptor.java | 10 ++++++++++ .../v8/V8JavaClassInterceptorContext.java | 10 ++++++++++ .../java/com/eclipsesource/v8/V8JavaClassProxy.java | 10 ++++++++++ .../eclipsesource/v8/V8JavaInstanceMethodProxy.java | 10 ++++++++++ .../java/com/eclipsesource/v8/V8JavaMethodProxy.java | 10 ++++++++++ .../java/com/eclipsesource/v8/V8JavaObjectUtils.java | 10 ++++++++++ .../com/eclipsesource/v8/V8JavaStaticMethodProxy.java | 10 ++++++++++ .../java/com/eclipsesource/v8/ConcurrentV8Test.java | 10 ++++++++++ .../java/com/eclipsesource/v8/V8JavaAdapterTest.java | 10 ++++++++++ 13 files changed, 130 insertions(+) diff --git a/src/main/java/com/eclipsesource/v8/ConcurrentV8.java b/src/main/java/com/eclipsesource/v8/ConcurrentV8.java index 8a48888c1..1d22de69f 100644 --- a/src/main/java/com/eclipsesource/v8/ConcurrentV8.java +++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2016 Alicorn Systems + * 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: + * Alicorn Systems - initial API and implementation and/or initial documentation + ******************************************************************************/ package com.eclipsesource.v8; /** diff --git a/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java b/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java index 60dd1c51d..c579ac43a 100644 --- a/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java +++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2016 Alicorn Systems + * 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: + * Alicorn Systems - initial API and implementation and/or initial documentation + ******************************************************************************/ package com.eclipsesource.v8; /** diff --git a/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java b/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java index 2de850d86..939843270 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2016 Alicorn Systems + * 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: + * Alicorn Systems - initial API and implementation and/or initial documentation + ******************************************************************************/ package com.eclipsesource.v8; import java.util.ArrayList; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaCache.java b/src/main/java/com/eclipsesource/v8/V8JavaCache.java index c922dc549..a1d903af5 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaCache.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaCache.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java index 2068a9b22..7a4e2ed1f 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptor.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; /** diff --git a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java index 56e94f9ca..88e1a8b01 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaClassInterceptorContext.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java index 04a319bb3..6ee9fd41d 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaClassProxy.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java index fdc2934c5..384f5564a 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaInstanceMethodProxy.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java index 944e0f1e9..49e9ab82c 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaMethodProxy.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java b/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java index 435df1a19..9bc790822 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaObjectUtils.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java b/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java index 8cc9ab591..29a14717a 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaStaticMethodProxy.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java b/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java index 388579a83..945694ac8 100644 --- a/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java +++ b/src/test/java/com/eclipsesource/v8/ConcurrentV8Test.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; diff --git a/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java b/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java index b29e08c80..026bdd663 100644 --- a/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java +++ b/src/test/java/com/eclipsesource/v8/V8JavaAdapterTest.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * 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; From 34c2bc7986d651a7419e37b2fc4dc7b174338605 Mon Sep 17 00:00:00 2001 From: Brandon Sanders Date: Tue, 27 Sep 2016 10:26:25 -0400 Subject: [PATCH 4/4] Fix two stray headers. --- src/main/java/com/eclipsesource/v8/ConcurrentV8.java | 4 ++-- src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java | 4 ++-- src/main/java/com/eclipsesource/v8/V8JavaAdapter.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/eclipsesource/v8/ConcurrentV8.java b/src/main/java/com/eclipsesource/v8/ConcurrentV8.java index 1d22de69f..68f877fd1 100644 --- a/src/main/java/com/eclipsesource/v8/ConcurrentV8.java +++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2016 Alicorn Systems + * 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: - * Alicorn Systems - initial API and implementation and/or initial documentation + * Brandon Sanders - initial API and implementation and/or initial documentation ******************************************************************************/ package com.eclipsesource.v8; diff --git a/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java b/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java index c579ac43a..3d9463196 100644 --- a/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java +++ b/src/main/java/com/eclipsesource/v8/ConcurrentV8Runnable.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2016 Alicorn Systems + * 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: - * Alicorn Systems - initial API and implementation and/or initial documentation + * Brandon Sanders - initial API and implementation and/or initial documentation ******************************************************************************/ package com.eclipsesource.v8; diff --git a/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java b/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java index 939843270..2813ef1b2 100644 --- a/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java +++ b/src/main/java/com/eclipsesource/v8/V8JavaAdapter.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2016 Alicorn Systems + * 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: - * Alicorn Systems - initial API and implementation and/or initial documentation + * Brandon Sanders - initial API and implementation and/or initial documentation ******************************************************************************/ package com.eclipsesource.v8;