Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve java.util.{List,Map} interop #713

Merged
merged 1 commit into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/org/mozilla/javascript/NativeJavaList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;

import java.util.List;

public class NativeJavaList extends NativeJavaObject {

private List<Object> list;

public NativeJavaList(Scriptable scope, Object list) {
super(scope, list, list.getClass());
assert list instanceof List;
this.list = (List<Object>) list;
}

@Override
public String getClassName() {
return "JavaList";
}


@Override
public boolean has(String name, Scriptable start) {
if (name.equals("length")) {
return true;
}
return super.has(name, start);
}

@Override
public boolean has(int index, Scriptable start) {
if (isWithValidIndex(index)) {
return true;
}
return super.has(index, start);
}

@Override
public boolean has(Symbol key, Scriptable start) {
if (SymbolKey.IS_CONCAT_SPREADABLE.equals(key)) {
return true;
}
return super.has(key, start);
}

@Override
public Object get(String name, Scriptable start) {
if ("length".equals(name)) {
return list.size();
}
return super.get(name, start);
}

@Override
public Object get(int index, Scriptable start) {
if (isWithValidIndex(index)) {
Context cx = Context.getContext();
Object obj = list.get(index);
return cx.getWrapFactory().wrap(cx, this, obj, obj.getClass());
}
return Undefined.instance;
}

@Override
public Object get(Symbol key, Scriptable start) {
if (SymbolKey.IS_CONCAT_SPREADABLE.equals(key)) {
return true;
}
return super.get(key, start);
}

@Override
public void put(int index, Scriptable start, Object value) {
if (isWithValidIndex(index)) {
list.set(index, Context.jsToJava(value, Object.class));
return;
}
super.put(index, start, value);
}

@Override
public Object[] getIds() {
List<?> list = (List<?>) javaObject;
Object[] result = new Object[list.size()];
int i = list.size();
while (--i >= 0) {
result[i] = Integer.valueOf(i);
}
return result;
}

private boolean isWithValidIndex(int index) {
return index >= 0 && index < list.size();
}
}
86 changes: 86 additions & 0 deletions src/org/mozilla/javascript/NativeJavaMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class NativeJavaMap extends NativeJavaObject {

private Map<Object, Object> map;

public NativeJavaMap(Scriptable scope, Object map) {
super(scope, map, map.getClass());
assert map instanceof Map;
this.map = (Map<Object, Object>) map;
}

@Override
public String getClassName() {
return "JavaMap";
}


@Override
public boolean has(String name, Scriptable start) {
if (map.containsKey(name)) {
return true;
}
return super.has(name, start);
}

@Override
public boolean has(int index, Scriptable start) {
if (map.containsKey(index)) {
return true;
}
return super.has(index, start);
}

@Override
public Object get(String name, Scriptable start) {
if (map.containsKey(name)) {
Context cx = Context.getContext();
Object obj = map.get(name);
return cx.getWrapFactory().wrap(cx, this, obj, obj.getClass());
}
return super.get(name, start);
}

@Override
public Object get(int index, Scriptable start) {
if (map.containsKey(index)) {
Context cx = Context.getContext();
Object obj = map.get(index);
return cx.getWrapFactory().wrap(cx, this, obj, obj.getClass());
}
return super.get(index, start);
}

@Override
public void put(String name, Scriptable start, Object value) {
map.put(name, Context.jsToJava(value, Object.class));
}

@Override
public void put(int index, Scriptable start, Object value) {
map.put(index, Context.jsToJava(value, Object.class));
}

@Override
public Object[] getIds() {
List<Object> ids = new ArrayList<>(map.size());
for (Object key : map.keySet()) {
if (key instanceof Integer) {
ids.add(ScriptRuntime.toInt32(key));
} else {
ids.add(ScriptRuntime.toString(key));
}
}
return ids.toArray();
}
}
8 changes: 8 additions & 0 deletions src/org/mozilla/javascript/WrapFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

package org.mozilla.javascript;

import java.util.List;
import java.util.Map;

/**
* Embeddings that wish to provide their own custom wrappings for Java
* objects may extend this class and call
Expand Down Expand Up @@ -117,6 +120,11 @@ public Scriptable wrapNewObject(Context cx, Scriptable scope, Object obj)
public Scriptable wrapAsJavaObject(Context cx, Scriptable scope,
Object javaObject, Class<?> staticType)
{
if (List.class.isAssignableFrom(javaObject.getClass())) {
return new NativeJavaList(scope, javaObject);
} else if (Map.class.isAssignableFrom(javaObject.getClass())) {
return new NativeJavaMap(scope, javaObject);
}
return new NativeJavaObject(scope, javaObject, staticType);
}

Expand Down
123 changes: 123 additions & 0 deletions testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mozilla.javascript.tests;

import junit.framework.TestCase;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.tools.shell.Global;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
* From @makusuko (Markus Sunela), imported from PR https://github.com/mozilla/rhino/pull/561
*/
public class NativeJavaListTest extends TestCase {
protected final Global global = new Global();

public NativeJavaListTest() {
global.init(ContextFactory.getGlobal());
}


public void testAccessingJavaListIntegerValues() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

assertEquals(2, runScriptAsInt("value[1]", list));
assertEquals(3, runScriptAsInt("value[2]", list));
assertEquals(3, runScriptAsInt("value.length", list));
}

public void testLengthProperty() {
List<Integer> list = new ArrayList<>();
assertEquals(0, runScriptAsInt("value.length", list));
list.add(1);
list.add(2);
list.add(3);
assertEquals(3, runScriptAsInt("value.length", list));
}

public void testJavaMethodsCalls() {
List<Integer> list = new ArrayList<>();
assertEquals(0, runScriptAsInt("value.size()", list));
list.add(1);
list.add(2);
list.add(3);
assertEquals(3, runScriptAsInt("value.size()", list));
}

public void testUpdatingJavaListIntegerValues() {
List<Number> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

assertEquals(2, runScriptAsInt("value[1]", list));
assertEquals(5, runScriptAsInt("value[1]=5;value[1]", list));
assertEquals(5, list.get(1).intValue());
}

public void testAccessingJavaListStringValues() {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

assertEquals("b", runScriptAsString("value[1]", list));
assertEquals("c", runScriptAsString("value[2]", list));
}

public void testUpdatingJavaListStringValues() {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

assertEquals("b", runScriptAsString("value[1]", list));
assertEquals("f", runScriptAsString("value[1]=\"f\";value[1]", list));
assertEquals("f", list.get(1));
}

public void testKeys() {
List<String> list = new ArrayList<>();
NativeArray resEmpty = (NativeArray) runScript("Object.keys(value)", list, Function.identity());
assertEquals(0, resEmpty.size());

list.add("a");
list.add("b");
list.add("c");

NativeArray res = (NativeArray) runScript("Object.keys(value)", list, Function.identity());
assertEquals(3, res.size());
assertTrue(res.contains("0"));
assertTrue(res.contains("1"));
assertTrue(res.contains("2"));
}

private int runScriptAsInt(String scriptSourceText, Object value) {
return runScript(scriptSourceText, value, Context::toNumber).intValue();
}

private String runScriptAsString(String scriptSourceText, Object value) {
return runScript(scriptSourceText, value, Context::toString);
}

private <T> T runScript(String scriptSourceText, Object value, Function<Object, T> convert) {
return ContextFactory.getGlobal().call(context -> {
Scriptable scope = context.initStandardObjects(global);
scope.put("value", scope, Context.javaToJS(value, scope));
return convert.apply(context.evaluateString(scope, scriptSourceText, "", 1, null));
});
}
}
Loading