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.
// Begin ---------------------------------------------------------------
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"));
// End -----------------------------------------------------------------
|
If I tried the same with Rhino engine in the REPL mode, but here the data type object is always correctly returned.
![windows console with rhino javascript type casting example](automaticConversionJavaDataTypes.002.jpg)
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:
// Begin ---------------------------------------------------------------
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);
// End -----------------------------------------------------------------
|
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?
Use 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.
![rhino javascript debugger](automaticConversionJavaDataTypes.009.jpg)
This is exactly the opposite of the behavior we see in VCF Automation.
![vmware aria automation action with type castings of java data types](automaticConversionJavaDataTypes.010.jpg)
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.
Problems with 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.
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.
// Begin ---------------------------------------------------------------
function main() {
// cx is Java object org.mozilla.javascript.Context
var cx = org.mozilla.javascript.Context.getCurrentContext();
java.lang.System.out.println(typeof cx);
java.lang.System.out.println(cx.getClass().getSimpleName());
java.lang.System.out.println(cx.getClass().getName());
java.lang.System.out.println(cx.getClass().getCanonicalName());
if (cx instanceof org.mozilla.javascript.Context) {
java.lang.System.out.println("Instance of org.mozilla.javascript.Context");
}
// sealedSharedScope is JavaScript Object
var sealedSharedScope = cx.initStandardObjects(null, true);
java.lang.System.out.println(typeof sealedSharedScope);
// 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);
// org.mozilla.javascript.IdFunctionObject
java.lang.System.out.println(sealedSharedScope.constructor);
java.lang.System.out.println(Object.getPrototypeOf(sealedSharedScope));
}
// Main
main();
// End -----------------------------------------------------------------
|
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.
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 |