From 9931a3023cb5ee29e08983644615976e929b298e Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Fri, 17 Sep 2021 09:00:03 +0200 Subject: [PATCH 1/2] ADD: delete method and set length property to NativeJavaList --- .../mozilla/javascript/NativeJavaList.java | 29 ++++++++ .../javascript/tests/NativeJavaListTest.java | 67 ++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/org/mozilla/javascript/NativeJavaList.java b/src/org/mozilla/javascript/NativeJavaList.java index ade836b261..ca422d4b55 100644 --- a/src/org/mozilla/javascript/NativeJavaList.java +++ b/src/org/mozilla/javascript/NativeJavaList.java @@ -40,6 +40,12 @@ public boolean has(int index, Scriptable start) { return super.has(index, start); } + public void delete(int index) { + if (isWithValidIndex(index)) { + list.set(index, null); + } + } + @Override public boolean has(Symbol key, Scriptable start) { if (SymbolKey.IS_CONCAT_SPREADABLE.equals(key)) { @@ -87,6 +93,15 @@ public void put(int index, Scriptable start, Object value) { super.put(index, start, value); } + @Override + public void put(String name, Scriptable start, Object value) { + if (list != null && "length".equals(name)) { + setLength(value); + return; + } + super.put(name, start, value); + } + private void ensureCapacity(int minCapacity) { if (minCapacity > list.size()) { if (list instanceof ArrayList) { @@ -98,6 +113,20 @@ private void ensureCapacity(int minCapacity) { } } + private void setLength(Object val) { + double d = ScriptRuntime.toNumber(val); + long longVal = ScriptRuntime.toUint32(d); + if (longVal != d || longVal > Integer.MAX_VALUE) { + String msg = ScriptRuntime.getMessageById("msg.arraylength.bad"); + throw ScriptRuntime.rangeError(msg); + } + if (longVal < list.size()) { + list.subList((int) longVal, list.size()).clear(); + } else { + ensureCapacity((int) longVal); + } + } + @Override public Object[] getIds() { List list = (List) javaObject; diff --git a/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java b/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java index 5c458639ba..c81f7f8bcd 100644 --- a/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java +++ b/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java @@ -13,6 +13,7 @@ import junit.framework.TestCase; import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextFactory; +import org.mozilla.javascript.EcmaError; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.tools.shell.Global; @@ -65,6 +66,10 @@ public void testLengthProperty() { list.add(2); list.add(3); assertEquals(3, runScriptAsInt("value.length", list)); + runScriptAsInt("value.length = 6", list); + assertEquals(6, list.size()); + runScriptAsInt("value.length = 2", list); + assertEquals(2, list.size()); } public void testJavaMethodsCalls() { @@ -83,8 +88,9 @@ public void testUpdatingJavaListIntegerValues() { list.add(3); assertEquals(2, runScriptAsInt("value[1]", list)); - assertEquals(5, runScriptAsInt("value[1]=5;value[1]", list)); - assertEquals(5, list.get(1).intValue()); + // setting values in lists will set them as double + assertEquals("5.0", runScriptAsString("value[1]=5;value[1]", list)); + assertEquals(5.0, list.get(1)); } public void testAccessingJavaListStringValues() { @@ -108,6 +114,63 @@ public void testUpdatingJavaListStringValues() { assertEquals("f", list.get(1)); } + public void testAutoGrow() { + List list = new ArrayList<>(); + // Object list = runScript("[]", null, Function.identity()); + assertEquals(0, runScriptAsInt("value.length", list)); + assertEquals(1, runScriptAsInt("value[0]='a'; value.length", list)); + assertEquals(3, runScriptAsInt("value[2]='c'; value.length", list)); + assertEquals("a", runScriptAsString("value[0]", list)); + // NativeArray will have 'undefined' here. + assertEquals("null", runScriptAsString("value[1]", list)); + assertEquals("c", runScriptAsString("value[2]", list)); + // NativeList will return "a,,c" + assertEquals("a,,c", runScriptAsString("Array.prototype.join.call(value)", list)); + } + + public void testLength() { + List list = new ArrayList<>(); + list.add("a"); + list.add("b"); + list.add("c"); + runScriptAsString("value.length = 0", list); + assertEquals(0, list.size()); + runScriptAsString("value.length = 10", list); + assertEquals(10, list.size()); + + try { + runScriptAsString("value.length = -10", list); + fail(); + } catch (EcmaError e) { + assertEquals("RangeError: Inappropriate array length. (#1)", e.getMessage()); + } + + try { + runScriptAsString("value.length = 2.1", list); + fail(); + } catch (EcmaError e) { + assertEquals("RangeError: Inappropriate array length. (#1)", e.getMessage()); + } + + try { + runScriptAsString("value.length = 2147483648", list); // Integer.MAX_VALUE + 1 + fail(); + } catch (EcmaError e) { + assertEquals("RangeError: Inappropriate array length. (#1)", e.getMessage()); + } + } + + public void testDelete() { + List list = new ArrayList<>(); + list.add("a"); + list.add("b"); + list.add("c"); + // Object list = runScript("['a','b','c']", null, Function.identity()); + // TODO: should NativeJavaList distinguish between 'null' and 'undefined'? + assertEquals("false", runScriptAsString("delete value[1]", list)); + assertEquals("a,,c", runScriptAsString("Array.prototype.join.call(value)", list)); + } + public void testKeys() { List list = new ArrayList<>(); NativeArray resEmpty = From 917426d3b5d568abadecc402e0c4f1e1c9d06544 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Tue, 21 Sep 2021 11:55:24 +0200 Subject: [PATCH 2/2] ADD: JavaDoc / using list.add if last element is added --- .../mozilla/javascript/NativeJavaList.java | 46 ++++++++++++++++++- src/org/mozilla/javascript/NativeJavaMap.java | 13 ++++++ .../mozilla/javascript/NativeJavaObject.java | 3 +- .../javascript/tests/NativeJavaListTest.java | 13 ++++++ 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/org/mozilla/javascript/NativeJavaList.java b/src/org/mozilla/javascript/NativeJavaList.java index ca422d4b55..464dbfc7df 100644 --- a/src/org/mozilla/javascript/NativeJavaList.java +++ b/src/org/mozilla/javascript/NativeJavaList.java @@ -8,8 +8,45 @@ import java.util.ArrayList; import java.util.List; +/** + * NativeJavaList is a wrapper for java objects implementing java.util.List + * interface. This wrapper delegates index based access in javascript (like + * value[x] = 3) to the according {@link List#get(int)}, {@link List#set(int, Object)} and + * {@link List#add(Object)} methods. This allows you to use java lists in many places like a + * javascript Array. + * + *

Supported functions: + * + *

    + *
  • index based access is delegated to List.get/set/add. If index >= length, + * the skipped elements will be filled with null values + *
  • iterator support with for...of (provided by NativeJavaObject for all + * iterables) + *
  • when iterating with for .. in (or for each .. in) then + * getIds + * + index based access is used. + *
  • reading and setting length property. When modifying the length property, the + * list is either truncated or will be filled with null values up to length + * + *
  • deleting entries: delete value[index] will be equivalent with + * value[index] = null and is implemented to provide array compatibility. + *
+ * + * Important: JavaList does not support sparse arrays. So setting the length property to a + * high value or writing to a high index may allocate a lot of memory. + * + *

Note: Although JavaList looks like a javascript-Array, it is + * not an + * Array. Some methods behave very similar like Array.indexOf and + * java.util.List.indexOf, others are named differently like Array.includes vs. + * java.util.List.contains. Especially forEach is different in Array + * and java.util.List. Also deleting entries will set entries to null + * instead to Undefined + */ public class NativeJavaList extends NativeJavaObject { + private static final long serialVersionUID = 660285467829047519L; + private List list; @SuppressWarnings("unchecked") @@ -86,8 +123,13 @@ public Object get(Symbol key, Scriptable start) { @Override public void put(int index, Scriptable start, Object value) { if (index >= 0) { - ensureCapacity(index + 1); - list.set(index, Context.jsToJava(value, Object.class)); + Object javaValue = Context.jsToJava(value, Object.class); + if (index == list.size()) { + list.add(javaValue); // use "add" at the end of list. + } else { + ensureCapacity(index + 1); + list.set(index, javaValue); + } return; } super.put(index, start, value); diff --git a/src/org/mozilla/javascript/NativeJavaMap.java b/src/org/mozilla/javascript/NativeJavaMap.java index 13f321241a..ab09f5f947 100644 --- a/src/org/mozilla/javascript/NativeJavaMap.java +++ b/src/org/mozilla/javascript/NativeJavaMap.java @@ -10,8 +10,21 @@ import java.util.List; import java.util.Map; +/** + * NativeJavaMap is a wrapper for java objects implementing java.util.Map + * interface. When {@link Context#FEATURE_ENABLE_JAVA_MAP_ACCESS} is enabled, property based + * access like map[key] is delegated to {@link Map#get(Object)} or {@link + * Map#put(Object, Object)} operations so that a JavaMap acts very similar to a + * javascript Object There is also an iterator to iterate over entries with + * for .. of. + * + *

Limitations: The wrapped map should have String or Integer as + * key. Otherwise, property based access may not work properly. + */ public class NativeJavaMap extends NativeJavaObject { + private static final long serialVersionUID = -3786257752907047381L; + private Map map; static void init(ScriptableObject scope, boolean sealed) { diff --git a/src/org/mozilla/javascript/NativeJavaObject.java b/src/org/mozilla/javascript/NativeJavaObject.java index 2174120de2..161d6505cd 100644 --- a/src/org/mozilla/javascript/NativeJavaObject.java +++ b/src/org/mozilla/javascript/NativeJavaObject.java @@ -19,7 +19,8 @@ /** * This class reflects non-Array Java objects into the JavaScript environment. It reflect fields - * directly, and uses NativeJavaMethod objects to reflect (possibly overloaded) methods. + * directly, and uses NativeJavaMethod objects to reflect (possibly overloaded) methods. It also + * provides iterator support for all iterable objects. * *

* diff --git a/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java b/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java index c81f7f8bcd..6fc4087bdf 100644 --- a/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java +++ b/testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java @@ -171,6 +171,19 @@ public void testDelete() { assertEquals("a,,c", runScriptAsString("Array.prototype.join.call(value)", list)); } + public void testAdd() { + List list = new ArrayList<>(); + runScriptAsString("value[0] = 'a'", list); + runScriptAsString("value[1] = 'b'", list); + runScriptAsString("value[2] = 'c'", list); + assertEquals("[a, b, c]", list.toString()); + runScriptAsString("value[5] = 'f'", list); + assertEquals("[a, b, c, null, null, f]", list.toString()); + runScriptAsString("value[4] = 'e'", list); + runScriptAsString("value[3] = 'd'", list); + assertEquals("[a, b, c, d, e, f]", list.toString()); + } + public void testKeys() { List list = new ArrayList<>(); NativeArray resEmpty =