This blog post describes experimenting of using native Java data types in the JavaScript execution environment of VCF Automation. Here a behavior is visible that I cannot explain.
Automatic Conversion of Java Data Types
in JavaScript Data Types
I tried to output all types of all Java primitive data types and the non-primitive data type string. But in VCF Automation they were converted into JavaScript data types.
System.log(typeof java.lang.Boolean.TRUE);
System.log(typeof java.lang.Byte.valueOf(127));
System.log(typeof java.lang.Short.valueOf(32767));
System.log(typeof java.lang.Integer.valueOf(2147483647));
System.log(typeof java.lang.Long.valueOf(9223372036854775295));
System.log(typeof java.lang.Float.valueOf(java.lang.Math.PI));
System.log(typeof java.lang.Double.valueOf(java.lang.Math.PI));
System.log(typeof java.lang.String("Hello World"));
|
If I tried the same with Rhino engine in the REPL mode, but here the data type object is always correctly returned.

Also I tried the same approach directly in the vco-app-server container of the k8s cluster, with exactly the same result.
On this way I can be quite sure that the type conversion is not dependent on the OS or the JDK.
var varBool = java.lang.Boolean.TRUE;
java.lang.System.out.println(typeof varBool + ": " + varBool);
var testBool =
org.mozilla.javascript.Context.jsToJava(varBool, java.lang.Boolean);
java.lang.System.out.println(typeof testBool + ": " + testBool);
typeof java.lang.Boolean.TRUE;
|
If the data type object is returned, I can access to the corresponding methods. With the conversion into the JavaScript data types, I cannot use these methods.
This behavior can be reproduced with the following image:
var varBool = java.lang.Boolean.TRUE;
System.log(typeof varBool + ": " + varBool);
var testBool = org.mozilla.javascript.Context.jsToJava(
varBool,
java.lang.Boolean
);
System.log(typeof testBool + ": " + testBool);
var varProperties = java.lang.System.getProperties();
System.log(typeof varProperties + ": " + varProperties);
var testProperties = org.mozilla.javascript.Context.jsToJava(
varProperties,
java.util.Properties
);
System.log(typeof testProperties + ": " + testProperties);
|
In the first step, a variable of type Boolean is created and its type is output. In the second step I tried to convert it back to the Java data type Boolean, but with the same result as described above. In the third step I used a non-primitive data type, it delivers object but a method call occurs the error message, that the object can not convert into Property. A type conversion seems to have been made here as well.
How to prevent automatic conversion, aka force conversion, into JavaScript data types?
Or the other way around, how can I use native Java data types in the JavaScript execution environment?
Primitive Java Data Types
In the context of my analyses here I read an answer of an interesting old post about
inconsistent behaviour when accessing java.util.*-classes. wrotes: "When you try to invoke a method, the Rhino engine tries to call this method on this NativeObject ... It is a generic problem. If you create a new ArrayList, for example, the JS engine will map it to a native array and you won’t be able to call ArrayList’s methods." Here it is the other way around. Here my code to check the behaviour:
// Begin----------------------------------------------------------------
var javaLangBoolean = java.lang.Boolean.TRUE;
System.log("Boolean is " + typeof javaLangBoolean + " from " +
javaLangBoolean.getClass());
var javaLangByte = java.lang.Byte.valueOf(127);
System.log("Byte is " + typeof javaLangByte + " from " +
javaLangByte.getClass());
var javaLangShort = java.lang.Short.valueOf(32767);
System.log("Short is " + typeof javaLangShort + " from " +
javaLangShort.getClass());
var javaLangInteger = java.lang.Integer.valueOf(2147483647);
System.log("Integer is " + typeof javaLangInteger + " from " +
javaLangInteger.getClass());
var javaLangLong = java.lang.Long.valueOf(9223372036854775295);
System.log("Long is " + typeof javaLangLong + " from " +
javaLangLong.getClass());
var javaLangFloat = java.lang.Float.valueOf(java.lang.Math.PI);
System.log("Float is " + typeof javaLangFloat + " from " +
javaLangFloat.getClass());
var javaLangDouble = java.lang.Double.valueOf(java.lang.Math.PI);
System.log("Double is " + typeof javaLangDouble + " from " +
javaLangDouble.getClass());
var javaLangString = java.lang.String("Hello World");
System.log("String is " + typeof javaLangString + " from " +
javaLangString.getClass());
// End------------------------------------------------------------------
|
In the debugger, the above statements are confirmed.

This is exactly the opposite of the behavior we see in VCF Automation.

The getClass method cannot be found, because java.lang.Boolean has been converted into the JavaScript data type boolean.
The behavior of Rhino Engine differs, when it used directly and when it used in VCF Automation. If the valueOf() method, which delivers an instance of a value, is omitted, the native Java data types are used, without force conversion. The following code works without any problems:
// Begin----------------------------------------------------------------
var javaLangBoolean = java.lang.Boolean(java.lang.Boolean.TRUE);
System.log("Boolean is " + typeof javaLangBoolean + " from " +
javaLangBoolean.getClass());
var javaLangByte = java.lang.Byte(127);
System.log("Byte is " + typeof javaLangByte + " from " +
javaLangByte.getClass());
var javaLangShort = java.lang.Short(32767);
System.log("Short is " + typeof javaLangShort + " from " +
javaLangShort.getClass());
var javaLangInteger = java.lang.Integer(2147483647);
System.log("Integer is " + typeof javaLangInteger + " from " +
javaLangInteger.getClass());
var javaLangLong = java.lang.Long(9223372036854775295);
System.log("Long is " + typeof javaLangLong + " from " +
javaLangLong.getClass());
var javaLangFloat = java.lang.Float(java.lang.Math.PI);
System.log("Float is " + typeof javaLangFloat + " from " +
javaLangFloat.getClass());
var javaLangDouble = java.lang.Double(java.lang.Math.PI);
System.log("Double is " + typeof javaLangDouble + " from " +
javaLangDouble.getClass());
var javaLangString = java.lang.String("Hello World");
System.log("String is " + typeof javaLangString + " from " +
javaLangString.getClass());
// End------------------------------------------------------------------
|
It delivers this result:
Boolean ist object from class java.lang.Boolean
Byte is object from class java.lang.Byte
Short is object from class java.lang.Short
Integer is object from class java.lang.Integer
Long is object from class java.lang.Long
Float is object from class java.lang.Float
Double is object from class java.lang.Double
String is object from class java.lang.String
|
With the direct type cast of the Java data type the native primitive data types and strings of Java can be used in the Rhino JavaScript engine in VCF Automation.
Note the different behavior of using Rhino Engine directly and in the context of VCF Automation.
Rhino wraps primitve types to special script objects on the same way as it expose any other JavaObject to scripts via
LiveConnect. To override this it is possible to use a custom WrapHandler from the WrapFactory.
Now there is still the question of how to handle the non-primitive data types.
Complex Java Data Types
try {
var ints = java.lang.reflect.Array.newInstance(java.lang.Integer, 1);
System.log(ints.constructor.name); //Array
// System.log(ints.getClass()); // TypeError
var int = java.lang.Integer(2147483647);
// System.log(int.constructor.name); // TypeError
System.log(int.getClass()); // class java.lang.Integer
} catch (exception) {
System.log(exception);
}
|
It seems that the Dunes framework of VCF Automation changes native Java data types into in its opinion equivalent JavaScript data types automatically, Here an example:
/**
* Hint: The Method getClass is inherited from the class java.lang.Object
* and it is available in both classes, because both extends the
* Object class.
*/
/**
* The variable of the java.io.File class is automatically type casted
* into the JavaScript data type File from the Dunes Framework.
*/
var javaIoFile = java.io.File.createTempFile("vco-", null);
System.log(javaIoFile.constructor.name);
// System.log(javaIoFile.getClass().getName());
// TypeError: Cannot find function getClass in object
// The method getClass is not available here, so this variable could not
// be from type java.io.File.
/**
* The variable of the java.nio.file.Files class not type casted,
* it is a Java data type.
*/
var javaNioFile = java.nio.file.Files.createTempFile("vco-", null);
// System.log(javaNioFile.constructor.name);
// TypeError: Cannot read property "name" from undefined
System.log(javaNioFile.getClass().getName());
|
The result of creating a temporary file using the java.io.File class is a JavaScript data type File from the Dunes Framework. If I do the same with the java.nio.file.Files class, then a native Java data type is returned. In the latter case, the native Java class methods can be used. This approach is not very sustainable, especially when using native Java data types in arrays. Because any array is automatically type casted into a JavaScript array by the Dunes framework. This makes the result unusable for further use in the native Java context.
List of Automatic Casted Types
List of native types, which are automatically type casted.
Native Type |
Converted Type |
java.lang.reflect.Array |
Array |
java.io.File |
File |
java.util.Properties |
Properties |
java.util.HashMap |
Properties |
java.net.url |
URL |
java.util.Date |
Date |
Solution Approach
The unrestricted use of complex Java data types can be done by executing the JavaScript code in a separate context. With this approach the caller is left and access to its objects with their methods, like System.log, is not possible in the new context.
function invokeScriptInContext(
in_moduleName,
in_actionName,
in_arguments
) {
/**
* @param {string} in_moduleName Module name which contains the action
* @param {string} in_actionName Action name which contains the text
* @param {Array/Any} in_arguments Arguments as array
*
* @outputType Any
*/
// Begin ---------------------------------------------------------------
function main(moduleName, actionName, arguments) {
var result = null;
try {
const script = System.getModule("de.stschnell").getActionAsText(
moduleName,
actionName
);
const cx = org.mozilla.javascript.Context.enter();
const scope = cx.initStandardObjects();
var func = cx.compileFunction(scope, script, "script", 1, null);
result = func.call(cx, scope, arguments);
// var result = cx.evaluateString(scope, script, "script", 1, null);
} catch (exception) {
System.warn(exception);
} finally {
cx.exit();
}
return result;
}
// Main
return main(
in_moduleName,
in_actionName,
in_arguments
);
// End -----------------------------------------------------------------
}
|
Additional Information
Local Problem Reflection
In the following source code delivers the first sequence with the variable cx a Java object. The second sequence delivers with the variable sealedSharedScope a JavaScript object.
function main() {
// cx is Java object org.mozilla.javascript.Context
var cx = org.mozilla.javascript.Context.getCurrentContext();
java.lang.System.out.println(typeof cx);
// object
java.lang.System.out.println(cx.getClass().getSimpleName());
// Context
java.lang.System.out.println(cx.getClass().getName());
// org.mozilla.javascript.Context
java.lang.System.out.println(cx.getClass().getCanonicalName());
// org.mozilla.javascript.Context
if (cx instanceof org.mozilla.javascript.Context) {
// cx is instance
java.lang.System.out.println("Instance of org.mozilla.javascript.Context");
}
/*
Here a direct comparison when the code is executed in VCF Automation
System.log(typeof cx);
// object
System.log(cx.getClass().getSimpleName());
// VcoContext
System.log(cx.getClass().getName());
// ch.dunes.scripting.jsmodel.VcoContext
System.log(cx.getClass().getCanonicalName());
// ch.dunes.scripting.jsmodel.VcoContext
if (cx instanceof org.mozilla.javascript.Context) {
// cx is instance
System.log("Instance of org.mozilla.javascript.Context");
}
*/
// sealedSharedScope is JavaScript Object
var sealedSharedScope = cx.initStandardObjects(null, true);
java.lang.System.out.println(typeof sealedSharedScope);
// object
// Expected argument to getClass() to be a Java object
// java.lang.System.out.println(sealedSharedScope.getClass().getName());
java.lang.System.out.println(sealedSharedScope.constructor.name);
// Object
java.lang.System.out.println(sealedSharedScope.constructor);
// org.mozilla.javascript.IdFunctionObject
java.lang.System.out.println(Object.getPrototypeOf(sealedSharedScope));
// [object Object]
// If the same sealedSharedScope code is executed in VCF Automation,
// the result is identical.
}
// Main
main();
|
Every object is technically a java class. If the class implements the org.mozilla.javascript.Scriptable interface, then it will appear to the Rhino runtime as a JavaScript object. If it does not implement Scriptable, then the runtime will see it as a wrapped Java class.
Context does not implement Scriptable, but the return value of initStandardObjects does.