Importing a map in javax.scripting javascript environment
Asked Answered
H

2

10

I'm seeing some odd behavior in the javax.scripting map implementation.

The online examples show an example of adding to a list within the js environment:

ScriptEngineManager mgr = new ScriptEngineManager();
  ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
  List<String> namesList = new ArrayList<String>();
  namesList.add("Jill");
  namesList.add("Bob");
  namesList.add("Laureen");
  namesList.add("Ed");
  jsEngine.put("namesListKey", namesList);
  System.out.println("Executing in script environment...");
  try
  {
   jsEngine.eval("var names = namesListKey.toArray();" + "for(x in names) {" + "  println(names[x]);" + "}"
     + "namesListKey.add(\"Dana\");");
  } catch (ScriptException ex)
  {
   ex.printStackTrace();
  }
  System.out.println(namesList);

However, if you try to do something similar with a map, you see odd behavior. For one thing, if you try to iterate through the map keys, e.g.

 HashMap<String, Object> m = new HashMap<String, Object>();
 jsEngine.put("map", m);

There's no way to obtain the map keys - if you try to iterate through the keys, you get method names-

jsEngine.eval(" for (var k in m.keySet()){ println(k)};");

results in :

notifyAll
removeAll
containsAll
contains
empty
equals
...

In the js context you can address values in the map with m.get(key) but not with m[key], and if the key doesn't exist it throws an error. Can anyone shed some light on this behavior, or is it just broken? Thanks.

Halleyhalli answered 13/8, 2010 at 17:7 Comment(1)
Thats very interesting. It might be iterating over the objects keySet (in Rhino you can loop over methods), but I'm not sure. I'll have to try itPodolsk
V
14

for..in in JavaScript is not the same thing as for..each in Java, even though they look similar. for..in in JavaScript iterates over the property names in an object. The method names are exposed to Rhino as properties on the native Java HashMap object, just as if you had the following JavaScript object:

{
notifyAll:function(){},
removeAll:function(){},
containsAll:function(){},
contains:function(){},
empty:function(){},
equals:function(){}
}

My recommendation is that you either convert the HashMap keyset to an array, using method Set.toArray, or you obtain an iterator using Set.iterator(). Here's a short Rhino script showing how you may accomplish this using the toArray method:

x=new java.util.HashMap();
x.put("foo","bar");
x.put("bat","bif");
x.put("barf","boo");

var keyArray = x.keySet().toArray();
for(var i=0, l = keyArray.length; i < l; i++){
    var key = keyArray[i]; 
    var value = x.get(key);
    print(value);
}

Which outputs:

bif
bar
boo

Here's how you can do the same thing using Set.iterator:

x=new java.util.HashMap();
x.put("foo","bar");
x.put("bat","bif");
x.put("barf","boo");

var keyIter = x.keySet().iterator();
while(keyIter.hasNext()){
    var key = keyIter.next() 
    var value = x.get(key);
    print(value);
}
Vilma answered 14/8, 2010 at 3:42 Comment(0)
W
3

If you convert java.util.Map to a native object, your JavaScript will be cleaner:

final Map<String,String> javaMap = new HashMap<>();
javaMap.put("alpha", "bravo");
javaMap.put("charlie", "delta");

final NativeObject jsMap = new NativeObject();
for (Map.Entry<String,String> entry : javaMap.entrySet()) {
    jsMap.defineProperty(
        entry.getKey(), entry.getValue(), NativeObject.READONLY
    );
}

final ScriptEngine jsEngine =
 (new ScriptEngineManager()).getEngineByName("JavaScript");
jsEngine.put("map", jsMap);
jsEngine.eval(
    "for (var idx in map) print(idx + '; ' + map[idx] + '\\n');"
);

Otherwise, you're stuck with standard Java syntax.

Windblown answered 17/11, 2013 at 5:51 Comment(1)
Unfortunately, your NativeObject must bei either Rhino or Nashorn API. It doesn't exist in javax.script common API.Mattoid

© 2022 - 2024 — McMap. All rights reserved.