18

I have the following:

ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
jsEngine.eval("function getArray() {return [1,2,3,4,5];};");
Object result = jsEngine.eval("getArray();");

How can i convert the result object which is of type sun.org.mozilla.javascript.internal.NativeArray to a corresponding java array? Can somone show me a working code sample where this is done? It should work for String and Integer arrays. Plus, it would be great to know where to look for other data type conversions between the rhino engine and java.

Btw, i know this page but i'm really looking for a working code sample.

Jason S
  • 184,598
  • 164
  • 608
  • 970
Chris
  • 15,429
  • 19
  • 72
  • 74

5 Answers5

23
NativeArray arr = (NativeArray) result;
Object [] array = new Object[(int) arr.getLength()];
for (Object o : arr.getIds()) {
    int index = (Integer) o;
    array[index] = arr.get(index, null);
}
Kevin
  • 30,111
  • 9
  • 76
  • 83
  • thanks for helping a stupid guy ;-) i wish all answers would be that precise,short and working! – Chris Sep 16 '09 at 14:54
  • Wonder why though you can't cast the entire getIds array to Integer[]. I'm getting a ClassCastException. Oh well – TheLQ Jul 04 '10 at 07:51
9

I'm not sure if it was the case when this question was first asked, but NativeArray implements the java.util.List interface. A simple way to convert to a real Java array is therefore:

Object[] array = ((List<?>) result).toArray();
Dave Hartnoll
  • 1,144
  • 11
  • 21
  • jdk1.6.0_45 says sun.org.mozilla.javascript.internal.NativeArray cannot be cast to java.util.List – pal Jul 06 '14 at 20:43
  • @pal NativeArray definitely implements List in more recent versions of Rhino, but I'm not sure when that was first the case. See [here](http://javadox.com/org.mozilla/rhino/1.7R3/org/mozilla/javascript/NativeArray.html). – Dave Hartnoll Jul 07 '14 at 15:14
  • 1
    By the way, Nashorn (the JavaScript engine included with Java 8) unfortunately returns objects of a class (ScriptObjectMirror) that implements Map instead of List. If you want your code to work under both Java 7 and 8+, you'll have to do an instanceof check. – Matt Passell Aug 21 '14 at 13:58
8

If the Javascript is under your control, you could do the transformation there, as per this document. So to adapt your example, something like:

ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
jsEngine.eval("function getArray() {return [1,2,3,4,5];};");
String convertFuncSrc =
     "function convertArray(type, arr) {"
   + "  var jArr = java.lang.reflect.Array.newInstance(type, arr.length);"
   + "  for (var i = 0; i < arr.length; i++) { "
   + "    jArr[i] = arr[i];"
   + "  }"
   + "  return jArr;"
   + "};";
jsEngine.eval(convertFuncSrc);
Object result = jsEngine.eval("convertArray(java.lang.Integer.TYPE, getArray());");
int[] javaArray = (int[])result;

Although, if you can't change the Javascript this approach won't work, and you [i]will[/i] have an instance of sun.org.mozilla.javascript.internal.NativeArray as your result variable. At which point I think you just need to cast and deal with it directly, using whatever public methods it exposes; it's probably not pretty but I don't see any other options. In particular I think the only thing you can guarantee at the nice Rhino level is that it will be an instance of Scriptable (and probably ScriptableObject), which doesn't help you use it as an Array.

Kevin's answer looks like a good way to go here (and is similar to what I was just about to edit in! :-))

Forage
  • 2,726
  • 2
  • 18
  • 20
Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
0

General solution using JASON as data intermediate:

Object result = jsEngine.eval("JSON.stringify(getArray())");    
JsonReader jsonReader = Json.createReader(new StringReader(result.toString()));
JsonArray jsonArray = jsonReader.readArray();
jsonReader.close();
int[] numbers = new int[jsonArray.size()];
int index = 0;
for(JsonValue value : jsonArray){
    numbers[index++] = Integer.parseInt(value.toString());
}
0

In my case I wanted to produce a Java array within the script. (This use case also matches the question.)

Following Creating Java Arrays, I came up with

var javaArray = java.lang.reflect.Array.newInstance(java.lang.Integer, jsArray.length);
for (var i = 0; i < javaArray.length; i++) {
  javaArray[i] = new java.lang.Integer(jsArray[i]);
}
bgerth
  • 1,256
  • 1
  • 12
  • 19