diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TStackOverflowError.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TStackOverflowError.java new file mode 100644 index 000000000..65c108b2e --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TStackOverflowError.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Sergey Kapralov. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang; + +/** + * + * @author Sergey Kapralov + */ +public class TStackOverflowError extends TVirtualMachineError { + + public TStackOverflowError() { + super(); + } + + public TStackOverflowError(TString string) { + super(string); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java index 799db2ca0..b1ceca084 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java @@ -15,6 +15,9 @@ */ package org.teavm.classlib.java.util; +import java.util.function.BiFunction; +import java.util.function.Function; + /** * * @author Alexey Andreev @@ -53,4 +56,75 @@ public interface TMap { TCollection values(); TSet> entrySet(); + + default boolean replace(K key, V value, V newValue) { + if (containsKey(key) && TObjects.equals(get(key), value)) { + put(key, newValue); + return true; + } else { + return false; + } + } + + default V replace(K key, V value) { + if (containsKey(key)) { + return put(key, value); + } else { + return null; + } + } + + default V computeIfAbsent(K key, Function mappingFunction) { + V v = get(key); + if (v == null) { + V newValue = mappingFunction.apply(key); + if (newValue != null) { + put(key, newValue); + } + return newValue; + } + return v; + } + + default V computeIfPresent(K key, BiFunction remappingFunction) { + V v = get(key); + if (v != null) { + V oldValue = v; + V newValue = remappingFunction.apply(key, oldValue); + if (newValue != null) { + put(key, newValue); + } else { + remove(key); + } + return newValue; + } + return null; + } + + default V compute(K key, BiFunction remappingFunction) { + V oldValue = get(key); + V newValue = remappingFunction.apply(key, oldValue); + if (oldValue != null) { + if (newValue != null) { + put(key, newValue); + } else { + remove(key); + } + } else if (newValue != null) { + put(key, newValue); + } + return newValue; + } + + default V merge(K key, V value, BiFunction remappingFunction) { + V oldValue = get(key); + V newValue = (oldValue == null) ? value + : remappingFunction.apply(oldValue, value); + if (newValue == null) { + remove(key); + } else { + put(key, newValue); + } + return newValue; + } } diff --git a/tests/src/test/java/org/teavm/classlib/java/util/HashtableTest.java b/tests/src/test/java/org/teavm/classlib/java/util/HashtableTest.java index 8478b05bd..e8582ba32 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/HashtableTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/HashtableTest.java @@ -693,6 +693,315 @@ public class HashtableTest { } } + @Test + public void test_computeUpdatesValueIfPresent() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.compute("Key5", (k,v) -> "changed"); + assertEquals("changed", newVal); + assertEquals(10, ht10.size()); + + for(int i = 0; i < 10; i++) { + if(i == 5) { + assertEquals("Value was incorrectly changed", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_computePutsNewEntryIfKeyIsAbsent() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.compute("absent key", (k,v) -> "added"); + assertEquals("added", newVal); + assertEquals(11, ht10.size()); + + for(int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected","added", ht10.get("absent key")); + } + + @Test + public void test_computeRemovesEntryWhenNullProduced() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.compute("Key5", (k,v) -> null); + assertEquals(null, newVal); + assertEquals(9, ht10.size()); + + for(int i = 0; i < 10; i++) { + if(i == 5) { + assertEquals("Value was unexpectedly present in map", null, ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_computeIfAbsentNominal() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfAbsent("absent key", (k) -> "added"); + assertEquals("added", newVal); + assertEquals(11, ht10.size()); + + for(int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected","added", ht10.get("absent key")); + } + + @Test + public void test_computeIfAbsentIgnoresExistingEntry() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfAbsent("Key5", (v) -> "changed"); + assertEquals("Val5", newVal); + assertEquals(10, ht10.size()); + + for(int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_computeIfAbsentDoesNothingIfNullProduced() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfAbsent("absent key", (v) -> null); + assertEquals(null, newVal); + assertEquals(10, ht10.size()); + + for(int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_computeIfPresentNominal() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfPresent("Key5", (k, v) -> "changed"); + assertEquals("changed", newVal); + assertEquals(10, ht10.size()); + + for(int i = 0; i < 10; i++) { + if(i == 5) { + assertEquals("Value was incorrectly updated", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_computeIfPresentIgnoresAbsentKeys() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfPresent("absent key", (k, v) -> "added"); + assertEquals(null, newVal); + assertEquals(10, ht10.size()); + + for(int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_computeIfPresentRemovesEntryWhenNullProduced() { + Hashtable ht10 = new Hashtable<>(); + for(int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfPresent("Key5", (k, v) -> null); + assertEquals(null, newVal); + assertEquals(9, ht10.size()); + + for(int i = 0; i < 10; i++) { + if(i == 5) { + assertNull("Value unexpectedly present", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + + @Test + public void test_mergeKeyAbsentCase() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("absent key", "changed", (k, v) -> "remapped"); + assertEquals("changed", newVal); + assertEquals(11, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected", "changed", ht10.get("absent key")); + } + + @Test + public void test_mergeKeyPresentCase() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("Key5", "changed", (k, v) -> "remapped"); + assertEquals("remapped", newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly updated", "remapped", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_mergeKeyAbsentCase_remapToNull() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("absent key", "changed", (k, v) -> null); + assertEquals("changed", newVal); + assertEquals(11, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected", "changed", ht10.get("absent key")); + } + + @Test + public void test_mergeKeyPresentCase_remapToNull() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("Key5", "changed", (k, v) -> null); + assertEquals(null, newVal); + assertEquals(9, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertNull("Null value expected", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_replaceNominal() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.replace("Key5", "changed"); + assertEquals("Val5", newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly updated", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_replaceAbsentKey() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.replace("absent key", "changed"); + assertEquals(null, newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_replace2Nominal() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + boolean replaced = ht10.replace("Key5", "Val5", "changed"); + assertEquals(true, replaced); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly updated", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_replace2WithIncorrectExpectation() { + Hashtable ht10 = new Hashtable<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + boolean replaced = ht10.replace("Key5", "incorrect value", "changed"); + assertEquals(false, replaced); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + @SuppressWarnings("unchecked") protected Hashtable hashtableClone(Hashtable s) { return (Hashtable) s.clone();