com.eclipsesource.v8.V8 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of j2v8_win32_x86_64 Show documentation
Show all versions of j2v8_win32_x86_64 Show documentation
J2V8 is a set of Java bindings for V8
The newest version!
/*******************************************************************************
* Copyright (c) 2014 EclipseSource and others.
* 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:
* EclipseSource - initial API and implementation
******************************************************************************/
package com.eclipsesource.v8;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.eclipsesource.v8.utils.V8Executor;
import com.eclipsesource.v8.utils.V8Map;
/**
* An isolated V8Runtime. All JavaScript execution must exist
* on a single runtime, and data is not shared between runtimes.
* A runtime must be created and released when finished.
*
* All access to a runtime must come from the same thread, unless
* the thread explicitly gives up control using the V8Locker.
*
* A public static factory method can be used to create the runtime.
*
* V8 runtime = V8.createV8Runtime();
*
*/
public class V8 extends V8Object {
private static Object lock = new Object();
private volatile static int runtimeCounter = 0;
private static String v8Flags = null;
private static boolean initialized = false;
private final V8Locker locker;
private long objectReferences = 0;
private long v8RuntimePtr = 0;
private List resources = null;
private V8Map executors = null;
private boolean forceTerminateExecutors = false;
private Map functionRegistry = new HashMap();
private LinkedList referenceHandlers = new LinkedList();
private static boolean nativeLibraryLoaded = false;
private static Error nativeLoadError = null;
private static Exception nativeLoadException = null;
private static V8Value undefined = new V8Object.Undefined();
private static Object invalid = new Object();
private class MethodDescriptor {
Object object;
Method method;
JavaCallback callback;
JavaVoidCallback voidCallback;
boolean includeReceiver;
}
private synchronized static void load(final String tmpDirectory) {
try {
LibraryLoader.loadLibrary(tmpDirectory);
nativeLibraryLoaded = true;
} catch (Error e) {
nativeLoadError = e;
} catch (Exception e) {
nativeLoadException = e;
}
}
/**
* Determines if the native libraries are loaded.
*
* @return Returns true if the native libraries are loaded,
* false otherwise.
*/
public static boolean isLoaded() {
return nativeLibraryLoaded;
}
/**
* Sets the V8 flags on the platform. All runtimes will be created
* with the same flags. Flags must be set before the runtime is
* created.
*
* @param flags The flags to set on V8
*/
public static void setFlags(final String flags) {
v8Flags = flags;
initialized = false;
}
/**
* Creates a new V8Runtime and loads the required
* native libraries if they are not already loaded.
* The current thread is given the lock to this runtime.
*
* @return A new isolated V8 Runtime.
*/
public static V8 createV8Runtime() {
return createV8Runtime(null, null);
}
/**
* Creates a new V8Runtime and loads the required native libraries if they
* are not already loaded. An alias is also set for the global scope. For example,
* 'window' can be set as the global scope name.
*
* The current thread is given the lock to this runtime.
*
* @param globalAlias The name to associate with the global scope.
*
* @return A new isolated V8 Runtime.
*/
public static V8 createV8Runtime(final String globalAlias) {
return createV8Runtime(globalAlias, null);
}
/**
* Creates a new V8Runtime and loads the required native libraries if they
* are not already loaded. An alias is also set for the global scope. For example,
* 'window' can be set as the global scope name.
*
* The current thread is given the lock to this runtime.
*
* @param globalAlias The name to associate with the global scope.
* @param tempDirectory The name of the directory to extract the native
* libraries too.
*
* @return A new isolated V8 Runtime.
*/
public static V8 createV8Runtime(final String globalAlias, final String tempDirectory) {
if (!nativeLibraryLoaded) {
synchronized (lock) {
if (!nativeLibraryLoaded) {
load(tempDirectory);
}
}
}
checkNativeLibraryLoaded();
if (!initialized) {
_setFlags(v8Flags);
initialized = true;
}
V8 runtime = new V8(globalAlias);
synchronized (lock) {
runtimeCounter++;
}
return runtime;
}
/**
* Adds a ReferenceHandler to track when new V8Objects are created.
*
* @param handler The ReferenceHandler to add
*/
public void addReferenceHandler(final ReferenceHandler handler) {
referenceHandlers.add(0, handler);
}
/**
* Removes an existing ReferenceHandler from the collection of reference handlers.
* If the ReferenceHandler does not exist in the collection, it is ignored.
*
* @param handler The reference handler to remove
*/
public void removeReferenceHandler(final ReferenceHandler handler) {
referenceHandlers.remove(handler);
}
private void notifyReferenceCreated(final V8Value object) {
for (ReferenceHandler referenceHandler : referenceHandlers) {
referenceHandler.v8HandleCreated(object);
}
}
private void notifyReferenceDisposed(final V8Value object) {
for (ReferenceHandler referenceHandler : referenceHandlers) {
referenceHandler.v8HandleDisposed(object);
}
}
private static void checkNativeLibraryLoaded() {
if (!nativeLibraryLoaded) {
if (nativeLoadError != null) {
throw new IllegalStateException("J2V8 native library not loaded", nativeLoadError);
} else if (nativeLoadException != null) {
throw new IllegalStateException("J2V8 native library not loaded", nativeLoadException);
} else {
throw new IllegalStateException("J2V8 native library not loaded");
}
}
}
protected V8() {
this(null);
}
protected V8(final String globalAlias) {
super(null);
released = false;
locker = new V8Locker();
checkThread();
v8RuntimePtr = _createIsolate(globalAlias);
objectHandle = _getGlobalObject(v8RuntimePtr);
}
/**
* Returns an UNDEFINED constant.
*
* @return The UNDEFINED constant value.
*/
public static V8Value getUndefined() {
return undefined;
}
/**
* Returns the number of active runtimes.
*
* @return The number of active runtimes.
*/
public static int getActiveRuntimes() {
return runtimeCounter;
}
/**
* Returns the number of Object References for this runtime.
*
* @return The number of Object References on this runtime.
*/
public long getObjectReferenceCount() {
return objectReferences;
}
protected long getV8RuntimePtr() {
return v8RuntimePtr;
}
/**
* Gets the version of the V8 engine
*
* @return The version of the V8 Engine.
*/
public static String getV8Version() {
return _getVersion();
}
/*
* (non-Javadoc)
* @see com.eclipsesource.v8.V8Value#release()
*/
@Override
public void release() {
release(true);
}
/**
* Terminates any JavaScript executing on this runtime. Once
* the runtime is released, any executors that were spawned
* will also be force terminated.
*/
public void terminateExecution() {
forceTerminateExecutors = true;
terminateExecution(v8RuntimePtr);
}
/**
* Release native resources associated with this runtime. Once
* released, a runtime cannot be reused.
*
* @param reportMemoryLeaks True if memory leaks should be
* reported by throwing an IllegalStateException if any
* objects were not released.
*/
public void release(final boolean reportMemoryLeaks) {
if (isReleased()) {
return;
}
checkThread();
releaseResources();
shutdownExecutors(forceTerminateExecutors);
if (executors != null) {
executors.clear();
}
releaseNativeMethodDescriptors();
synchronized (lock) {
runtimeCounter--;
}
_releaseRuntime(v8RuntimePtr);
v8RuntimePtr = 0L;
released = true;
if (reportMemoryLeaks && (objectReferences > 0)) {
throw new IllegalStateException(objectReferences + " Object(s) still exist in runtime");
}
}
private void releaseNativeMethodDescriptors() {
Set nativeMethodDescriptors = functionRegistry.keySet();
for (Long nativeMethodDescriptor : nativeMethodDescriptors) {
releaseMethodDescriptor(v8RuntimePtr, nativeMethodDescriptor);
}
}
private void releaseResources() {
if (resources != null) {
for (Releasable releasable : resources) {
releasable.release();
}
resources.clear();
resources = null;
}
}
/**
* Registers an executor with this runtime. An executor is another
* runtime with its own thread. By registering an executor, it can be
* terminated when this runtime is released.
*
* @param key The key to associate the executor with.
* @param executor The executor itself.
*/
public void registerV8Executor(final V8Object key, final V8Executor executor) {
checkThread();
if (executors == null) {
executors = new V8Map();
}
executors.put(key, executor);
}
/**
* Removes the executor from this runtime. The executor is
* *NOT* shutdown, simply removed from the list of known
* executors.
*
* @param key The key the executor was associated with.
* @return The executor or null if it does not exist.
*/
public V8Executor removeExecutor(final V8Object key) {
checkThread();
if (executors == null) {
return null;
}
return executors.remove(key);
}
/**
* Returns the executor associated with the given key.
*
* @param key The key the executor was associated with.
* @return The executor or null if it does not exist.
*/
public V8Executor getExecutor(final V8Object key) {
checkThread();
if (executors == null) {
return null;
}
return executors.get(key);
}
/**
* Shutdown all executors associated with this runtime.
* If force terminate is specified, it will forcefully terminate
* the executors, otherwise it will simply signal that they
* should terminate.
*
* @param forceTerminate Specify if the executors should be
* forcefully terminated, or simply notified to shutdown when ready.
*/
public void shutdownExecutors(final boolean forceTerminate) {
checkThread();
if (executors == null) {
return;
}
for (V8Executor executor : executors.values()) {
if (forceTerminate) {
executor.forceTermination();
} else {
executor.shutdown();
}
}
}
/**
* Registers a resource with this runtime. All registered
* resources will be released before the runtime is released.
*
* @param resource The resource to register.
*/
public void registerResource(final Releasable resource) {
checkThread();
if (resources == null) {
resources = new ArrayList();
}
resources.add(resource);
}
/**
* Executes a JS Script on this runtime and returns the result as an integer.
* If the result is not an integer, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
*
* @return The result of the script as an integer, or V8ResultUndefinedException if
* the result is not an integer.
*/
public int executeIntegerScript(final String script) {
return executeIntegerScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime and returns the result as an integer.
* If the result is not an integer, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for excepton purposes.
*
* @return The result of the script as an integer, or V8ResultUndefinedException if
* the result is not an integer.
*/
public int executeIntegerScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
checkScript(script);
return executeIntegerScript(v8RuntimePtr, script, scriptName, lineNumber);
}
protected void createTwin(final V8Value value, final V8Value twin) {
checkThread();
createTwin(v8RuntimePtr, value.getHandle(), twin.getHandle());
}
/**
* Executes a JS Script on this runtime and returns the result as a double.
* If the result is not a double, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
*
* @return The result of the script as a double, or V8ResultUndefinedException if
* the result is not a double.
*/
public double executeDoubleScript(final String script) {
return executeDoubleScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime and returns the result as a double.
* If the result is not a double, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for exception stack trace purposes.
*
* @return The result of the script as a double, or V8ResultUndefinedException if
* the result is not a double.
*/
public double executeDoubleScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
checkScript(script);
return executeDoubleScript(v8RuntimePtr, script, scriptName, lineNumber);
}
/**
* Executes a JS Script on this runtime and returns the result as a String.
* If the result is not a String, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
*
* @return The result of the script as a String, or V8ResultUndefinedException if
* the result is not a String.
*/
public String executeStringScript(final String script) {
return executeStringScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime and returns the result as a String.
* If the result is not a String, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for exception stack trace purposes.
*
* @return The result of the script as a String, or V8ResultUndefinedException if
* the result is not a String.
*/
public String executeStringScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
checkScript(script);
return executeStringScript(v8RuntimePtr, script, scriptName, lineNumber);
}
/**
* Executes a JS Script on this runtime and returns the result as a boolean.
* If the result is not a boolean, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
*
* @return The result of the script as a boolean, or V8ResultUndefinedException if
* the result is not a boolean.
*/
public boolean executeBooleanScript(final String script) {
return executeBooleanScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime and returns the result as a boolean.
* If the result is not a boolean, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for exception stack trace purposes.
*
* @return The result of the script as a boolean, or V8ResultUndefinedException if
* the result is not a boolean.
*/
public boolean executeBooleanScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
checkScript(script);
return executeBooleanScript(v8RuntimePtr, script, scriptName, lineNumber);
}
/**
* Executes a JS Script on this runtime and returns the result as a V8Array.
* If the result is not a V8Array, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
*
* @return The result of the script as a V8Array, or V8ResultUndefinedException if
* the result is not a V8Array.
*/
public V8Array executeArrayScript(final String script) {
return executeArrayScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime and returns the result as a V8Array.
* If the result is not a V8Array, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for exception stack trace purposes.
*
* @return The result of the script as a V8Array, or V8ResultUndefinedException if
* the result is not a V8Array.
*/
public V8Array executeArrayScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
Object result = this.executeScript(script, scriptName, lineNumber);
if (result instanceof V8Array) {
return (V8Array) result;
}
throw new V8ResultUndefined();
}
/**
* Executes a JS Script on this runtime and returns the result as a Java Object.
* Primitives will be boxed.
*
* @param script The script to execute.
*
* @return The result of the script as a Java Object.
*/
public Object executeScript(final String script) {
return executeScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime and returns the result as a Java Object.
* Primitives will be boxed.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for exception stack trace purposes.
*
* @return The result of the script as a Java Object.
*/
public Object executeScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
checkScript(script);
return executeScript(getV8RuntimePtr(), UNKNOWN, script, scriptName, lineNumber);
}
/**
* Executes a JS Script on this runtime and returns the result as a V8Object.
* If the result is not a V8Object, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
*
* @return The result of the script as a V8Object, or V8ResultUndefinedException if
* the result is not a V8Object.
*/
public V8Object executeObjectScript(final String script) {
return this.executeObjectScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime and returns the result as a V8Object.
* If the result is not a V8Object, then a V8ResultUndefinedException is thrown.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for exception stack trace purposes.
*
* @return The result of the script as a V8Object, or V8ResultUndefinedException if
* the result is not a V8Object.
*/
public V8Object executeObjectScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
Object result = this.executeScript(script, scriptName, lineNumber);
if (result instanceof V8Object) {
return (V8Object) result;
}
throw new V8ResultUndefined();
}
/**
* Executes a JS Script on this runtime.
*
* @param script The script to execute.
*/
public void executeVoidScript(final String script) {
executeVoidScript(script, null, 0);
}
/**
* Executes a JS Script on this runtime.
*
* @param script The script to execute.
* @param scriptName The name of the script
* @param lineNumber The line number that is considered to be the first line of
* the script. Typically 0, but could be set to another value for exception stack trace purposes.
*/
public void executeVoidScript(final String script, final String scriptName, final int lineNumber) {
checkThread();
checkScript(script);
executeVoidScript(v8RuntimePtr, script, scriptName, lineNumber);
}
/**
* Returns the locker associated with this runtime. The locker allows
* threads to give up control of the runtime and other threads to acquire
* control.
*
* @return The locker associated with this runtime.
*/
public V8Locker getLocker() {
return locker;
}
/**
* Returns the unique build ID of the native library.
*
* @return The unique build ID of the Native library.
*/
public long getBuildID() {
return _getBuildID();
}
void checkThread() {
locker.checkThread();
if (isReleased()) {
throw new Error("Runtime disposed error");
}
}
static void checkScript(final String script) {
if (script == null) {
throw new NullPointerException("Script is null");
}
}
void registerCallback(final Object object, final Method method, final long objectHandle, final String jsFunctionName, final boolean includeReceiver) {
MethodDescriptor methodDescriptor = new MethodDescriptor();
methodDescriptor.object = object;
methodDescriptor.method = method;
methodDescriptor.includeReceiver = includeReceiver;
long methodID = registerJavaMethod(getV8RuntimePtr(), objectHandle, jsFunctionName, isVoidMethod(method));
functionRegistry.put(methodID, methodDescriptor);
}
void registerVoidCallback(final JavaVoidCallback callback, final long objectHandle, final String jsFunctionName) {
MethodDescriptor methodDescriptor = new MethodDescriptor();
methodDescriptor.voidCallback = callback;
long methodID = registerJavaMethod(getV8RuntimePtr(), objectHandle, jsFunctionName, true);
functionRegistry.put(methodID, methodDescriptor);
}
void registerCallback(final JavaCallback callback, final long objectHandle, final String jsFunctionName) {
long methodID = registerJavaMethod(getV8RuntimePtr(), objectHandle, jsFunctionName, false);
createAndRegisterMethodDescriptor(callback, methodID);
}
void createAndRegisterMethodDescriptor(final JavaCallback callback, final long methodID) {
MethodDescriptor methodDescriptor = new MethodDescriptor();
methodDescriptor.callback = callback;
functionRegistry.put(methodID, methodDescriptor);
}
private boolean isVoidMethod(final Method method) {
Class> returnType = method.getReturnType();
if (returnType.equals(Void.TYPE)) {
return true;
}
return false;
}
private Object getDefaultValue(final Class> type) {
if (type.equals(V8Object.class)) {
return new V8Object.Undefined();
} else if (type.equals(V8Array.class)) {
return new V8Array.Undefined();
}
return invalid;
}
protected void disposeMethodID(final long methodID) {
functionRegistry.remove(methodID);
}
protected Object callObjectJavaMethod(final long methodID, final V8Object receiver, final V8Array parameters) throws Throwable {
MethodDescriptor methodDescriptor = functionRegistry.get(methodID);
if (methodDescriptor.callback != null) {
return checkResult(methodDescriptor.callback.invoke(receiver, parameters));
}
boolean hasVarArgs = methodDescriptor.method.isVarArgs();
Object[] args = getArgs(receiver, methodDescriptor, parameters, hasVarArgs);
checkArgs(args);
try {
Object result = methodDescriptor.method.invoke(methodDescriptor.object, args);
return checkResult(result);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalAccessException e) {
throw e;
} catch (IllegalArgumentException e) {
throw e;
} finally {
releaseArguments(args, hasVarArgs);
}
}
private Object checkResult(final Object result) {
if (result == null) {
return result;
}
if (result instanceof Float) {
return ((Float) result).doubleValue();
}
if ((result instanceof Integer) || (result instanceof Double) || (result instanceof Boolean)
|| (result instanceof String)) {
return result;
}
if (result instanceof V8Value) {
if (((V8Value) result).isReleased()) {
throw new V8RuntimeException("V8Value already released");
}
return result;
}
throw new V8RuntimeException("Unknown return type: " + result.getClass());
}
protected void callVoidJavaMethod(final long methodID, final V8Object receiver, final V8Array parameters) throws Throwable {
MethodDescriptor methodDescriptor = functionRegistry.get(methodID);
if (methodDescriptor.voidCallback != null) {
methodDescriptor.voidCallback.invoke(receiver, parameters);
return;
}
boolean hasVarArgs = methodDescriptor.method.isVarArgs();
Object[] args = getArgs(receiver, methodDescriptor, parameters, hasVarArgs);
checkArgs(args);
try {
methodDescriptor.method.invoke(methodDescriptor.object, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalAccessException e) {
throw e;
} catch (IllegalArgumentException e) {
throw e;
} finally {
releaseArguments(args, hasVarArgs);
}
}
private void checkArgs(final Object[] args) {
for (Object argument : args) {
if (argument == invalid) {
throw new IllegalArgumentException("argument type mismatch");
}
}
}
private void releaseArguments(final Object[] args, final boolean hasVarArgs) {
if (hasVarArgs && ((args.length > 0) && (args[args.length - 1] instanceof Object[]))) {
Object[] varArgs = (Object[]) args[args.length - 1];
for (Object object : varArgs) {
if (object instanceof V8Value) {
((V8Value) object).release();
}
}
}
for (Object arg : args) {
if (arg instanceof V8Value) {
((V8Value) arg).release();
}
}
}
private Object[] getArgs(final V8Object receiver, final MethodDescriptor methodDescriptor, final V8Array parameters, final boolean hasVarArgs) {
int numberOfParameters = methodDescriptor.method.getParameterTypes().length;
int varArgIndex = hasVarArgs ? numberOfParameters - 1 : numberOfParameters;
Object[] args = setDefaultValues(new Object[numberOfParameters], methodDescriptor.method.getParameterTypes(), receiver, methodDescriptor.includeReceiver);
List