bionic.js.Bjs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bionic-js Show documentation
Show all versions of bionic-js Show documentation
The library to integrate Bionic JS in Java applications
package bionic.js;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jjbridge.api.runtime.JSReference;
import jjbridge.api.runtime.JSRuntime;
import jjbridge.api.value.JSArray;
import jjbridge.api.value.JSBoolean;
import jjbridge.api.value.JSDate;
import jjbridge.api.value.JSDouble;
import jjbridge.api.value.JSExternal;
import jjbridge.api.value.JSFunction;
import jjbridge.api.value.JSInteger;
import jjbridge.api.value.JSObject;
import jjbridge.api.value.JSString;
import jjbridge.api.value.JSType;
import jjbridge.api.value.strategy.FunctionCallback;
import java.lang.reflect.Array;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* The environment which provides JavaScript objects get/put and module loading capabilities.
* */
public class Bjs
{
private static final String NATIVE_HIDDEN_FIELD = "__native__";
private static final String BJS_NATIVE_OBJ_FIELD_NAME = "bjsNativeObj";
private static final String BJS_WRAPPER_OBJ_FIELD_NAME = "bjsWrapperObj";
private static final String UNBOUND = "unbound";
private static final Map projects = new HashMap<>();
private static JSRuntime defaultRuntime;
private final String projectName;
private final HashMap, BjsObject> jsValueToNative;
private final HashMap modulesCache;
private BjsContext context;
static void setDefaultRuntime(JSRuntime runtime)
{
defaultRuntime = runtime;
}
static synchronized Bjs get(String packageName, @NonNull String projectName)
{
String fullClassName = packageName == null || packageName.isEmpty()
? "Bjs" + projectName
: packageName + ".Bjs" + projectName;
if (projects.containsKey(fullClassName))
{
return projects.get(fullClassName);
}
else
{
Bjs bjs = new Bjs(projectName);
try
{
Class> initializerClass = Class.forName(fullClassName);
Class extends BjsProject> projectClass = initializerClass.asSubclass(BjsProject.class);
BjsProjectTypeInfo.get(projectClass).initialize(bjs);
}
catch (ClassNotFoundException e)
{
throw new RuntimeException(e);
}
projects.put(fullClassName, bjs);
return bjs;
}
}
private Bjs(@NonNull String projectName)
{
this.projectName = projectName;
this.jsValueToNative = new HashMap<>();
this.modulesCache = new HashMap<>();
}
/**
* Loads a Bjs bundle.
*
* The bundle must be a JavaScript file called {@code `bundleName`.js} inside a folder called
* {@code `bundleName`.bjs} (where {@code `bundleName`} is the value of the bundleName argument). The folder must be
* placed in the resources directory.
*
* @param forClass the {@link BjsProject} class instance associated to the bundle
* @param bundleName the name of the bundle to load
* */
public void loadBundle(@NonNull Class> forClass, @NonNull String bundleName)
{
if (defaultRuntime == null)
{
throw new NullPointerException("JSRuntime must be set before loading a bundle");
}
jsValueToNative.clear();
modulesCache.clear();
context = new BjsContext(defaultRuntime, projectName);
BjsBundle bundle = new BjsBundle(forClass, bundleName + ".bjs");
String file = bundle.loadFile(bundleName + ".js");
if (file == null)
{
throw new RuntimeException("Cannot load bundle " + bundleName + " file");
}
context.executeJs(file, bundleName + ".bjs/" + bundleName + ".js");
}
/**
* Register a native wrapper class to the Bjs environment.
*
* @param the native wrapper type
* @param the type wrapped by the wrapper
* @param nativeWrapperClass the class to register
* */
public , T extends BjsExport> void addNativeWrapper(
@NonNull Class nativeWrapperClass)
{
context.addNativeWrapper(nativeWrapperClass);
}
/**
* Creates a reference to JavaScript {@code null} value.
*
* @return a reference to JavaScript {@code null}
* */
public JSReference jsNull()
{
return context.createJsNull();
}
/**
* Creates a generic JavaScript null object.
* This method is equivalent to {@code getAny(jsNull())}.
*
* @return a JavaScript null object
* */
public BjsAnyObject anyNull()
{
return getAny(jsNull());
}
/**
* Creates a reference to JavaScript {@code undefined} value.
*
* @return a reference to JavaScript {@code undefined}
* */
public JSReference jsUndefined()
{
return context.createJsUndefined();
}
// JS FUNCTIONS CALL
/**
* Invokes a method of the given object.
*
* @param jsObject the target object
* @param name the name of the method
* @param arguments the arguments to pass to the method
* @return the return value of the invoked method
* */
public JSReference call(JSReference jsObject, String name, JSReference... arguments)
{
return context.callFunction(getProperty(jsObject, name), jsObject, arguments);
}
/**
* Invokes a function.
*
* @param jsFunction the target function
* @param arguments the arguments to pass to the function
* @return the return value of the invoked function
* */
public JSReference funcCall(JSReference jsFunction, JSReference... arguments)
{
return context.callFunction(jsFunction, jsFunction, arguments);
}
// JS PROPERTIES
/**
* Gets a reference to a property of an object.
*
* @param jsObject the target object
* @param name the name of the desired property
* @return the reference to the property
* */
public JSReference getProperty(JSReference jsObject, String name)
{
JSObject> resolve = context.resolve(jsObject);
return resolve.get(name);
}
/**
* Sets the reference to a property of an object.
*
* @param jsObject the target object
* @param name the name of the desired property
* @param value the new reference of the property
* */
public void setProperty(JSReference jsObject, String name, JSReference value)
{
JSObject> resolve = context.resolve(jsObject);
resolve.set(name, value);
}
// PUT (NATIVE -> JS)
/**
* Creates a reference to a JavaScript Boolean value.
*
* @param primitive the value of the reference
* @return a reference to the JavaScript value
* */
public JSReference putPrimitive(Boolean primitive)
{
return primitive == null ? jsNull() : context.newBoolean(primitive);
}
/**
* Creates a reference to a JavaScript Integer value.
*
* @param primitive the value of the reference
* @return a reference to the JavaScript value
* */
public JSReference putPrimitive(Integer primitive)
{
return primitive == null ? jsNull() : context.newInteger(primitive);
}
/**
* Creates a reference to a JavaScript Double value.
*
* @param primitive the value of the reference
* @return a reference to the JavaScript value
* */
public JSReference putPrimitive(Double primitive)
{
return primitive == null ? jsNull() : context.newDouble(primitive);
}
/**
* Creates a reference to a JavaScript String value.
*
* @param primitive the value of the reference
* @return a reference to the JavaScript value
* */
public JSReference putPrimitive(String primitive)
{
return primitive == null ? jsNull() : context.newString(primitive);
}
/**
* Creates a reference to a JavaScript Date value.
*
* @param primitive the value of the reference
* @return a reference to the JavaScript value
* */
public JSReference putPrimitive(Date primitive)
{
return primitive == null ? jsNull() : context.newDate(primitive);
}
/**
* Stores a Java object in a JavaScript reference.
* If the object class has an associated native wrapper class, you can also use
* {@link #putWrapped(BjsExport, Class)}
*
* @param nativeObject the object to store
* @return a reference to the JavaScript value
* */
public JSReference putNative(T nativeObject)
{
if (nativeObject == null)
{
return jsNull();
}
else
{
JSReference jsReference = context.newObject();
setProperty(jsReference, NATIVE_HIDDEN_FIELD, context.newExternal(nativeObject));
return jsReference;
}
}
/**
* Stores a Java object in a JavaScript reference.
* Use can use this method if the object class is mapped to a JavaScript native interface via a native wrapper
* class.
*
* @param nativeObject the object to store
* @param nativeWrapperClass the class instance of the associated native wrapper
* @return a reference to the JavaScript value
* */
public , T extends BjsExport> JSReference putWrapped(T nativeObject,
Class nativeWrapperClass)
{
if (nativeObject == null)
{
return jsNull();
}
JSReference jsNative = putNative(nativeObject);
JSReference jsWrapperObj = getProperty(jsNative, BJS_WRAPPER_OBJ_FIELD_NAME);
if (jsWrapperObj == null || jsWrapperObj.getNominalType().equals(JSType.Undefined))
{
setProperty(jsNative, BJS_WRAPPER_OBJ_FIELD_NAME, context.newString(UNBOUND));
JSReference wrapperClass = BjsNativeWrapperTypeInfo.get(nativeWrapperClass).bjsClass();
return constructObject(wrapperClass, new JSReference[]{jsNative});
}
else
{
return jsWrapperObj;
}
}
/**
* Creates a reference to a JavaScript Object value.
*
* @param bjsObject the value of the reference
* @return a reference to the JavaScript value
* */
public JSReference putObj(BjsObject bjsObject)
{
return bjsObject == null ? jsNull() : bjsObject.jsObject;
}
/**
* Creates a reference to a JavaScript Function value.
*
* @param nativeFunc the value of the reference
* @param jsFuncCallback the function implementation
* @return a reference to the JavaScript value
* */
public JSReference putFunc(F nativeFunc, FunctionCallback> jsFuncCallback)
{
return nativeFunc == null ? jsNull() : context.newFunction(jsFuncCallback);
}
/**
* Creates a reference to a JavaScript Array value.
*
* @param nativeArray the value of the reference
* @param converter the strategy to create a reference for each array item
* @return a reference to the JavaScript value
* */
public JSReference putArray(T[] nativeArray, NativeConverter converter)
{
if (nativeArray == null)
{
return jsNull();
}
JSReference reference = context.newArray();
JSArray> jsArray = context.resolve(reference);
for (int i = 0; i < nativeArray.length; i++)
{
jsArray.set(i, converter.convert(nativeArray[i]));
}
return reference;
}
// GET (JS -> NATIVE)
/**
* Extract the Boolean value from a reference to a JavaScript value.
*
* @param jsBoolean the reference to the JavaScript value
* @return the referenced value
* */
@CheckForNull
public Boolean getBoolean(JSReference jsBoolean)
{
return isNullOrUndefined(jsBoolean) ? null : ((JSBoolean) context.resolve(jsBoolean)).getValue();
}
/**
* Extract the Integer value from a reference to a JavaScript value.
*
* @param jsInteger the reference to the JavaScript value
* @return the referenced value
* */
@CheckForNull
public Integer getInteger(JSReference jsInteger)
{
return isNullOrUndefined(jsInteger) ? null : ((JSInteger) context.resolve(jsInteger)).getValue();
}
/**
* Extract the Double value from a reference to a JavaScript value.
*
* @param jsDouble the reference to the JavaScript value
* @return the referenced value
* */
@CheckForNull
public Double getDouble(JSReference jsDouble)
{
return isNullOrUndefined(jsDouble) ? null : ((JSDouble) context.resolve(jsDouble)).getValue();
}
/**
* Extract the String value from a reference to a JavaScript value.
*
* @param jsString the reference to the JavaScript value
* @return the referenced value
* */
@CheckForNull
public String getString(JSReference jsString)
{
return isNullOrUndefined(jsString) ? null : ((JSString) context.resolve(jsString)).getValue();
}
/**
* Extract the Date value from a reference to a JavaScript value.
*
* @param jsDate the reference to the JavaScript value
* @return the referenced value
* */
@CheckForNull
public Date getDate(JSReference jsDate)
{
if (isNullOrUndefined(jsDate))
{
return null;
}
JSDate> resolve = context.resolve(jsDate);
return resolve.getValue();
}
/**
* Extract the Java object from a reference to a JavaScript value.
*
* @param jsNative the reference to the JavaScript value
* @return the referenced Java object
* */
@CheckForNull
public T getNative(JSReference jsNative)
{
if (isNullOrUndefined(jsNative))
{
return null;
}
else
{
JSReference nativeRef = ((JSObject>) context.resolve(jsNative)).get(NATIVE_HIDDEN_FIELD);
if (isNullOrUndefined(nativeRef))
{
return null;
}
JSExternal resolve = context.resolve(nativeRef);
return resolve.getValue();
}
}
/**
* Extract the Java object from a reference to a JavaScript native interface value.
*
* @param jsWrappedObject the reference to the JavaScript native wrapper
* @return the referenced Java object
* */
@CheckForNull
public T getWrapped(JSReference jsWrappedObject)
{
if (isNullOrUndefined(jsWrappedObject))
{
return null;
}
JSReference bjsNativeObj = getProperty(jsWrappedObject, BJS_NATIVE_OBJ_FIELD_NAME);
if (isNullOrUndefined(bjsNativeObj))
{
return null;
}
return getNative(bjsNativeObj);
}
/**
* Extract the Java function from a reference to a JavaScript value.
*
* @param jsObject the reference to the JavaScript value
* @param nativeFunc the function implementation
* @return the referenced function
* */
@CheckForNull
public F getFunc(JSReference jsObject, F nativeFunc)
{
return isNullOrUndefined(jsObject) ? null : nativeFunc;
}
/**
* Extract a generic JavaScript object from its JavaScript reference.
*
* @param jsObject the reference to the JavaScript object
* @return the referenced object
* */
public BjsAnyObject getAny(JSReference jsObject)
{
return new BjsAnyObject(jsObject);
}
/**
* Extract the Java object from a reference to a JavaScript exported object.
*
* @param jsObject the reference to the JavaScript object
* @param converter the strategy to convert the JavaScript reference to the desired value
* @param clazz the class instance of the returning type
* @return the referenced Java object
* */
@CheckForNull
public T getObj(JSReference jsObject, JSReferenceConverter converter, Class clazz)
{
if (isNullOrUndefined(jsObject))
{
return null;
}
BjsNativeObjectIdentifier identifier = new BjsNativeObjectIdentifier<>(jsObject, clazz);
if (jsValueToNative.containsKey(identifier))
{
@SuppressWarnings("unchecked")
T nativeObject = (T) jsValueToNative.get(identifier);
return nativeObject;
}
T nativeObject = converter.convert(jsObject);
jsValueToNative.put(identifier, nativeObject);
return nativeObject;
}
/**
* Extract the Array value from a reference to a JavaScript value.
*
* @param jsArray the reference to the JavaScript value
* @param elementConverter the strategy to convert the JavaScript array items
* @param nativeClass the class instance of the returning type
* @return the referenced value
* */
@CheckForNull
@SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS",
justification = "null is allowed here since coming from JS code")
public T[] getArray(JSReference jsArray, JSReferenceConverter elementConverter, Class nativeClass)
{
if (isNullOrUndefined(jsArray))
{
return null;
}
JSArray> array = context.resolve(jsArray);
@SuppressWarnings("unchecked")
T[] nativeArray = (T[]) Array.newInstance(nativeClass, array.size());
for (int i = 0; i < nativeArray.length; i++)
{
nativeArray[i] = elementConverter.convert(array.get(i));
}
return nativeArray;
}
// WRAPPERS
/**
* Extract a Java exported object from the reference to the given JavaScript native interface, if available.
*
* @param jsObject the reference to the Javascript object
* @param nativeClass the class instance of the result
* @return the exported Java object if available, or {@code null} otherwise
* */
@CheckForNull
public T getBound(JSReference jsObject, Class nativeClass)
{
Object obj = getNative(jsObject);
if (obj != null && nativeClass.isAssignableFrom(obj.getClass()))
{
JSReference bjsWrapperObj = getProperty(jsObject, BJS_WRAPPER_OBJ_FIELD_NAME);
if (UNBOUND.equals(getString(bjsWrapperObj)))
{
@SuppressWarnings("unchecked")
T object = (T) obj;
return object;
}
}
return null;
}
/**
* Binds a Java exported object with a reference to JavaScript native wrapper instance.
*
* @param nativeObject the native object to bind
* @param wrapper the JavaScript object reference
* */
public void bindNative(T nativeObject, JSReference wrapper)
{
JSReference nativeReference = putNative(nativeObject);
if (nativeReference != null)
{
setProperty(wrapper, BJS_NATIVE_OBJ_FIELD_NAME, nativeReference);
setProperty(nativeReference, BJS_WRAPPER_OBJ_FIELD_NAME, wrapper);
}
}
/**
* Returns an array with at least the length specified.
* Specifically, it returns:
*
* - the original array if it is already bigger (or equal) than the given size.
* - Otherwise a new array is created; the elements from the starting array keep the same positions, while the
* remaining slots are filled with JavaScript references to undefined.
*
*
* @see #jsUndefined()
*
* @param in the array to ensure the length of
* @param size the length to ensure
* @return an array with the given size
* */
public JSReference[] ensureArraySize(JSReference[] in, int size)
{
if (in.length >= size)
{
return in;
}
JSReference[] out = new JSReference[size];
for (int i = 0; i < out.length; i++)
{
out[i] = i < in.length ? in[i] : jsUndefined();
}
return out;
}
// MODULES AND ENVIRONMENT
JSReference loadModule(@NonNull String moduleName)
{
if (modulesCache.containsKey(moduleName))
{
return modulesCache.get(moduleName);
}
if (this.context == null)
{
throw new RuntimeException("Bjs context was not initialized");
}
JSReference export = context.getModule(moduleName);
if (isNullOrUndefined(export))
{
throw new RuntimeException("Module " + moduleName + " was not found in Bjs bundle");
}
JSReference module = getProperty(export, moduleName);
if (module.getActualType() == JSType.Undefined)
{
module = export;
}
modulesCache.put(moduleName, module);
return module;
}
private boolean isNullOrUndefined(JSReference jsReference)
{
JSType type = jsReference.getNominalType();
return type == JSType.Null || type == JSType.Undefined;
}
JSReference constructObject(JSReference jsClass, JSReference[] arguments)
{
JSFunction> resolve = context.resolve(jsClass);
return resolve.invokeConstructor(arguments);
}
void createNativeObject(JSReference jsObject, T bjsObject)
{
@SuppressWarnings("unchecked")
BjsNativeObjectIdentifier identifier =
new BjsNativeObjectIdentifier<>(jsObject, (Class) bjsObject.getClass());
if (!jsValueToNative.containsKey(identifier))
{
jsValueToNative.put(identifier, bjsObject);
return;
}
context.logError("Bjs object " + bjsObject.getClass().getSimpleName()
+ " was initialized with a js object already bound with another bjs object");
}
/**
* The strategy to convert a JavaScript reference to the corresponding Java object.
*
* @param the type of the Java object after conversion
* */
public interface JSReferenceConverter
{
T convert(JSReference jsReference);
}
/**
* The strategy to convert a Java object to the corresponding JavaScript reference.
*
* @param the type of the Java object before conversion
* */
public interface NativeConverter
{
JSReference convert(T nativeValue);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy