From 42d60f9f28ba159a91e991c806831f375006fdb8 Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Tue, 21 Jun 2016 11:25:43 -0400 Subject: [PATCH] maps n lists --- .../elasticsearch/painless/Augmentation.java | 290 +++++++++++++++++- .../java/org/elasticsearch/painless/Def.java | 4 +- .../elasticsearch/painless/Definition.java | 4 +- .../elasticsearch/painless/FunctionRef.java | 29 +- .../painless/WriterConstants.java | 2 + .../painless/node/ECapturingFunctionRef.java | 2 +- .../painless/node/EFunctionRef.java | 4 +- .../elasticsearch/painless/node/ELambda.java | 6 +- .../org/elasticsearch/painless/java.lang.txt | 7 + .../org/elasticsearch/painless/java.util.txt | 22 ++ .../painless/AugmentationTests.java | 149 ++++++++- 11 files changed, 491 insertions(+), 28 deletions(-) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java index 456642eb8c5..4bca673b4dc 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java @@ -20,9 +20,14 @@ package org.elasticsearch.painless; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.ObjIntConsumer; @@ -30,15 +35,26 @@ import java.util.function.Predicate; import java.util.function.ToDoubleFunction; import java.util.regex.Matcher; +/** Additional methods added to classes. These must be static methods with receiver as first argument */ public class Augmentation { + + // static methods only! + private Augmentation() {} + + /** Exposes List.size() as getLength(), so that .length shortcut works on lists */ public static int getLength(List receiver) { return receiver.size(); } - + + /** Exposes Matcher.group(String) as namedGroup(String), so it doesn't conflict with group(int) */ public static String namedGroup(Matcher receiver, String name) { return receiver.group(name); } + // some groovy methods on iterable + // see http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Iterable.html + + /** Iterates over the contents of an iterable, and checks whether a predicate is valid for at least one element. */ public static boolean any(Iterable receiver, Predicate predicate) { for (T t : receiver) { if (predicate.test(t)) { @@ -48,6 +64,7 @@ public class Augmentation { return false; } + /** Counts the number of occurrences which satisfy the given predicate from inside this Iterable. */ public static int count(Iterable receiver, Predicate predicate) { int count = 0; for (T t : receiver) { @@ -58,12 +75,20 @@ public class Augmentation { return count; } - public static Iterable each(Iterable receiver, Consumer consumer) { + // instead of covariant overrides for every possibility, we just return receiver as 'def' for now + // that way if someone chains the calls, everything works. + + /** Iterates through an Iterable, passing each item to the given consumer. */ + public static Object each(Iterable receiver, Consumer consumer) { receiver.forEach(consumer); return receiver; } - public static Iterable eachWithIndex(Iterable receiver, ObjIntConsumer consumer) { + /** + * Iterates through an iterable type, passing each item and the item's index + * (a counter starting at zero) to the given consumer. + */ + public static Object eachWithIndex(Iterable receiver, ObjIntConsumer consumer) { int count = 0; for (T t : receiver) { consumer.accept(t, count++); @@ -71,6 +96,9 @@ public class Augmentation { return receiver; } + /** + * Used to determine if the given predicate is valid (i.e. returns true for all items in this iterable). + */ public static boolean every(Iterable receiver, Predicate predicate) { for (T t : receiver) { if (predicate.test(t) == false) { @@ -80,6 +108,10 @@ public class Augmentation { return true; } + /** + * Iterates through the Iterable transforming items using the supplied function and + * collecting any non-null results. + */ public static List findResults(Iterable receiver, Function filter) { List list = new ArrayList<>(); for (T t: receiver) { @@ -91,6 +123,9 @@ public class Augmentation { return list; } + /** + * Sorts all Iterable members into groups determined by the supplied mapping function. + */ public static Map> groupBy(Iterable receiver, Function mapper) { Map> map = new LinkedHashMap<>(); for (T t : receiver) { @@ -105,6 +140,10 @@ public class Augmentation { return map; } + /** + * Concatenates the toString() representation of each item in this Iterable, + * with the given String as a separator between each item. + */ public static String join(Iterable receiver, String separator) { StringBuilder sb = new StringBuilder(); for (T t : receiver) { @@ -116,6 +155,9 @@ public class Augmentation { return sb.toString(); } + /** + * Sums the result of applying a function to each item of an Iterable. + */ public static double sum(Iterable receiver, ToDoubleFunction function) { double sum = 0; for (T t : receiver) { @@ -123,4 +165,246 @@ public class Augmentation { } return sum; } + + // some groovy methods on collection + // see http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Collection.html + + /** + * Iterates through this collection transforming each entry into a new value using + * the function, returning a list of transformed values. + */ + public static List collect(Collection receiver, Function function) { + List list = new ArrayList<>(); + for (T t : receiver) { + list.add(function.apply(t)); + } + return list; + } + + /** + * Iterates through this collection transforming each entry into a new value using + * the function, adding the values to the specified collection. + */ + public static Object collect(Collection receiver, Collection collection, Function function) { + for (T t : receiver) { + collection.add(function.apply(t)); + } + return collection; + } + + /** + * Finds the first value matching the predicate, or returns null. + */ + public static T find(Collection receiver, Predicate predicate) { + for (T t : receiver) { + if (predicate.test(t)) { + return t; + } + } + return null; + } + + /** + * Finds all values matching the predicate, returns as a list + */ + public static List findAll(Collection receiver, Predicate predicate) { + List list = new ArrayList<>(); + for (T t : receiver) { + if (predicate.test(t)) { + list.add(t); + } + } + return list; + } + + /** + * Iterates through the collection calling the given function for each item + * but stopping once the first non-null result is found and returning that result. + * If all results are null, null is returned. + */ + public static Object findResult(Collection receiver, Function function) { + return findResult(receiver, null, function); + } + + /** + * Iterates through the collection calling the given function for each item + * but stopping once the first non-null result is found and returning that result. + * If all results are null, defaultResult is returned. + */ + public static Object findResult(Collection receiver, Object defaultResult, Function function) { + for (T t : receiver) { + U value = function.apply(t); + if (value != null) { + return value; + } + } + return defaultResult; + } + + /** + * Splits all items into two collections based on the predicate. + * The first list contains all items which match the closure expression. The second list all those that don't. + */ + public static List> split(Collection receiver, Predicate predicate) { + List matched = new ArrayList<>(); + List unmatched = new ArrayList<>(); + List> result = new ArrayList<>(2); + result.add(matched); + result.add(unmatched); + for (T t : receiver) { + if (predicate.test(t)) { + matched.add(t); + } else { + unmatched.add(t); + } + } + return result; + } + + // some groovy methods on map + // see http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Map.html + + /** + * Iterates through this map transforming each entry into a new value using + * the function, returning a list of transformed values. + */ + public static List collect(Map receiver, BiFunction function) { + List list = new ArrayList<>(); + for (Map.Entry kvPair : receiver.entrySet()) { + list.add(function.apply(kvPair.getKey(), kvPair.getValue())); + } + return list; + } + + /** + * Iterates through this map transforming each entry into a new value using + * the function, adding the values to the specified collection. + */ + public static Object collect(Map receiver, Collection collection, BiFunction function) { + for (Map.Entry kvPair : receiver.entrySet()) { + collection.add(function.apply(kvPair.getKey(), kvPair.getValue())); + } + return collection; + } + + /** Counts the number of occurrences which satisfy the given predicate from inside this Map */ + public static int count(Map receiver, BiPredicate predicate) { + int count = 0; + for (Map.Entry kvPair : receiver.entrySet()) { + if (predicate.test(kvPair.getKey(), kvPair.getValue())) { + count++; + } + } + return count; + } + + /** Iterates through a Map, passing each item to the given consumer. */ + public static Object each(Map receiver, BiConsumer consumer) { + receiver.forEach(consumer); + return receiver; + } + + /** + * Used to determine if the given predicate is valid (i.e. returns true for all items in this map). + */ + public static boolean every(Map receiver, BiPredicate predicate) { + for (Map.Entry kvPair : receiver.entrySet()) { + if (predicate.test(kvPair.getKey(), kvPair.getValue()) == false) { + return false; + } + } + return true; + } + + /** + * Finds the first entry matching the predicate, or returns null. + */ + public static Map.Entry find(Map receiver, BiPredicate predicate) { + for (Map.Entry kvPair : receiver.entrySet()) { + if (predicate.test(kvPair.getKey(), kvPair.getValue())) { + return kvPair; + } + } + return null; + } + + /** + * Finds all values matching the predicate, returns as a map. + */ + public static Map findAll(Map receiver, BiPredicate predicate) { + // try to preserve some properties of the receiver (see the groovy javadocs) + final Map map; + if (receiver instanceof TreeMap) { + map = new TreeMap<>(); + } else { + map = new LinkedHashMap<>(); + } + for (Map.Entry kvPair : receiver.entrySet()) { + if (predicate.test(kvPair.getKey(), kvPair.getValue())) { + map.put(kvPair.getKey(), kvPair.getValue()); + } + } + return map; + } + + /** + * Iterates through the map calling the given function for each item + * but stopping once the first non-null result is found and returning that result. + * If all results are null, null is returned. + */ + public static Object findResult(Map receiver, BiFunction function) { + return findResult(receiver, null, function); + } + + /** + * Iterates through the map calling the given function for each item + * but stopping once the first non-null result is found and returning that result. + * If all results are null, defaultResult is returned. + */ + public static Object findResult(Map receiver, Object defaultResult, BiFunction function) { + for (Map.Entry kvPair : receiver.entrySet()) { + T value = function.apply(kvPair.getKey(), kvPair.getValue()); + if (value != null) { + return value; + } + } + return defaultResult; + } + + /** + * Iterates through the map transforming items using the supplied function and + * collecting any non-null results. + */ + public static List findResults(Map receiver, BiFunction filter) { + List list = new ArrayList<>(); + for (Map.Entry kvPair : receiver.entrySet()) { + T result = filter.apply(kvPair.getKey(), kvPair.getValue()); + if (result != null) { + list.add(result); + } + } + return list; + } + + /** + * Sorts all Map members into groups determined by the supplied mapping function. + */ + public static Map> groupBy(Map receiver, BiFunction mapper) { + Map> map = new LinkedHashMap<>(); + for (Map.Entry kvPair : receiver.entrySet()) { + T mapped = mapper.apply(kvPair.getKey(), kvPair.getValue()); + Map results = map.get(mapped); + if (results == null) { + // try to preserve some properties of the receiver (see the groovy javadocs) + if (receiver instanceof TreeMap) { + results = new TreeMap<>(); + } else { + results = new LinkedHashMap<>(); + } + map.put(mapped, results); + } + results.put(kvPair.getKey(), kvPair.getValue()); + } + return map; + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java index 5461771bca6..cd761d0ad44 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java @@ -350,10 +350,10 @@ public final class Def { } throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments."); } - ref = new FunctionRef(clazz, interfaceMethod, handle, captures); + ref = new FunctionRef(clazz, interfaceMethod, handle, captures.length); } else { // whitelist lookup - ref = new FunctionRef(clazz, type, call, captures); + ref = new FunctionRef(clazz, type, call, captures.length); } final CallSite callSite; if (ref.needsBridges()) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java index 376be44e28b..8aa4da4015f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java @@ -257,7 +257,7 @@ public final class Definition { final org.objectweb.asm.Type type; if (augmentation) { assert java.lang.reflect.Modifier.isStatic(modifiers); - type = org.objectweb.asm.Type.getType(Augmentation.class); + type = WriterConstants.AUGMENTATION_TYPE; } else { type = owner.type; } @@ -825,7 +825,7 @@ public final class Definition { reflect = implClass.getMethod(name, params); } catch (NoSuchMethodException exception) { throw new IllegalArgumentException("Method [" + name + - "] not found for class [" + owner.clazz.getName() + "]" + + "] not found for class [" + implClass.getName() + "]" + " with arguments " + Arrays.toString(params) + "."); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java index 72676f1c04c..d5e02e12058 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java @@ -53,10 +53,10 @@ public class FunctionRef { * @param expected interface type to implement. * @param type the left hand side of a method reference expression * @param call the right hand side of a method reference expression - * @param captures captured arguments + * @param numCaptures number of captured arguments */ - public FunctionRef(Definition.Type expected, String type, String call, Class... captures) { - this(expected, expected.struct.getFunctionalMethod(), lookup(expected, type, call, captures.length > 0), captures); + public FunctionRef(Definition.Type expected, String type, String call, int numCaptures) { + this(expected, expected.struct.getFunctionalMethod(), lookup(expected, type, call, numCaptures > 0), numCaptures); } /** @@ -64,13 +64,16 @@ public class FunctionRef { * @param expected interface type to implement * @param method functional interface method * @param impl implementation method - * @param captures captured arguments + * @param numCaptures number of captured arguments */ - public FunctionRef(Definition.Type expected, Definition.Method method, Definition.Method impl, Class... captures) { + public FunctionRef(Definition.Type expected, Definition.Method method, Definition.Method impl, int numCaptures) { // e.g. compareTo invokedName = method.name; // e.g. (Object)Comparator - invokedType = MethodType.methodType(expected.clazz, captures); + MethodType implType = impl.getMethodType(); + // only include captured parameters as arguments + invokedType = MethodType.methodType(expected.clazz, + implType.dropParameterTypes(numCaptures, implType.parameterCount())); // e.g. (Object,Object)int interfaceMethodType = method.getMethodType().dropParameterTypes(0, 1); @@ -90,6 +93,9 @@ public class FunctionRef { // owner == null: script class itself ownerIsInterface = false; owner = WriterConstants.CLASS_TYPE.getInternalName(); + } else if (impl.augmentation) { + ownerIsInterface = false; + owner = WriterConstants.AUGMENTATION_TYPE.getInternalName(); } else { ownerIsInterface = impl.owner.clazz.isInterface(); owner = impl.owner.type.getInternalName(); @@ -98,7 +104,7 @@ public class FunctionRef { implMethod = impl.handle; // remove any prepended captured arguments for the 'natural' signature. - samMethodType = adapt(interfaceMethodType, impl.getMethodType().dropParameterTypes(0, captures.length)); + samMethodType = adapt(interfaceMethodType, impl.getMethodType().dropParameterTypes(0, numCaptures)); } /** @@ -106,11 +112,14 @@ public class FunctionRef { *

* This will not set implMethodASM. It is for runtime use only. */ - public FunctionRef(Definition.Type expected, Definition.Method method, MethodHandle impl, Class... captures) { + public FunctionRef(Definition.Type expected, Definition.Method method, MethodHandle impl, int numCaptures) { // e.g. compareTo invokedName = method.name; // e.g. (Object)Comparator - invokedType = MethodType.methodType(expected.clazz, captures); + MethodType implType = impl.type(); + // only include captured parameters as arguments + invokedType = MethodType.methodType(expected.clazz, + implType.dropParameterTypes(numCaptures, implType.parameterCount())); // e.g. (Object,Object)int interfaceMethodType = method.getMethodType().dropParameterTypes(0, 1); @@ -119,7 +128,7 @@ public class FunctionRef { implMethodASM = null; // remove any prepended captured arguments for the 'natural' signature. - samMethodType = adapt(interfaceMethodType, impl.type().dropParameterTypes(0, captures.length)); + samMethodType = adapt(interfaceMethodType, impl.type().dropParameterTypes(0, numCaptures)); } /** diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java index d581ba8518a..e2bf804c181 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java @@ -72,6 +72,8 @@ public final class WriterConstants { public final static Method CHAR_TO_STRING = getAsmMethod(String.class, "charToString", char.class); public final static Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); + + public static final Type AUGMENTATION_TYPE = Type.getType(Augmentation.class); /** * A Method instance for {@linkplain Pattern#compile}. This isn't available from Definition because we intentionally don't add it there diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java index aa7f807aff1..7bf8e195d6d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java @@ -76,7 +76,7 @@ public class ECapturingFunctionRef extends AExpression implements ILambda { // static case if (captured.type.sort != Definition.Sort.DEF) { try { - ref = new FunctionRef(expected, captured.type.name, call, captured.type.clazz); + ref = new FunctionRef(expected, captured.type.name, call, 1); } catch (IllegalArgumentException e) { throw createError(e); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java index 298b84ffe29..380bfd6c43f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java @@ -76,10 +76,10 @@ public class EFunctionRef extends AExpression implements ILambda { throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " + "to [" + expected.name + "], function not found"); } - ref = new FunctionRef(expected, interfaceMethod, implMethod); + ref = new FunctionRef(expected, interfaceMethod, implMethod, 0); } else { // whitelist lookup - ref = new FunctionRef(expected, type, call); + ref = new FunctionRef(expected, type, call, 0); } } catch (IllegalArgumentException e) { throw createError(e); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java index e0ea5e73aeb..0cbb2ed1b33 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java @@ -175,11 +175,7 @@ public class ELambda extends AExpression implements ILambda { } else { defPointer = null; try { - Class captureClasses[] = new Class[captures.size()]; - for (int i = 0; i < captures.size(); i++) { - captureClasses[i] = captures.get(i).type.clazz; - } - ref = new FunctionRef(expected, interfaceMethod, desugared.method, captureClasses); + ref = new FunctionRef(expected, interfaceMethod, desugared.method, captures.size()); } catch (IllegalArgumentException e) { throw createError(e); } diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt index 859a7b314dd..035dc9ba0c8 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt @@ -52,6 +52,13 @@ class Iterable -> java.lang.Iterable { Spliterator spliterator() # some adaptations of groovy methods boolean any*(Predicate) + def each*(Consumer) + def eachWithIndex*(ObjIntConsumer) + boolean every*(Predicate) + List findResults*(Function) + Map groupBy*(Function) + String join*(String) + double sum*(ToDoubleFunction) } # Readable: i/o diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt index 2a41b2c2c13..66f8f67d869 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt @@ -39,6 +39,15 @@ class Collection -> java.util.Collection extends Iterable { Stream stream() def[] toArray() def[] toArray(def[]) + + # some adaptations of groovy methods + List collect*(Function) + def collect*(Collection,Function) + def find*(Predicate) + List findAll*(Predicate) + def findResult*(Function) + def findResult*(def,Function) + List split*(Predicate) } class Comparator -> java.util.Comparator { @@ -152,6 +161,19 @@ class Map -> java.util.Map { void replaceAll(BiFunction) int size() Collection values() + + # some adaptations of groovy methods + List collect*(BiFunction) + def collect*(Collection,BiFunction) + int count*(BiPredicate) + def each*(BiConsumer) + boolean every*(BiPredicate) + Map.Entry find*(BiPredicate) + Map findAll*(BiPredicate) + def findResult*(BiFunction) + def findResult*(def,BiFunction) + List findResults*(BiFunction) + Map groupBy*(BiFunction) } class Map.Entry -> java.util.Map$Entry { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java index ccc3620b1c9..c0872dd1994 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java @@ -19,17 +19,160 @@ package org.elasticsearch.painless; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + public class AugmentationTests extends ScriptTestCase { - @AwaitsFix(bugUrl = "rmuir is working on this") + public void testStatic() { + assertEquals(1, exec("ArrayList l = new ArrayList(); l.add(1); return l.getLength();")); + assertEquals(1, exec("ArrayList l = new ArrayList(); l.add(1); return l.length;")); + } + + public void testSubclass() { + assertEquals(1, exec("List l = new ArrayList(); l.add(1); return l.getLength();")); + assertEquals(1, exec("List l = new ArrayList(); l.add(1); return l.length;")); + } + + public void testDef() { + assertEquals(1, exec("def l = new ArrayList(); l.add(1); return l.getLength();")); + assertEquals(1, exec("def l = new ArrayList(); l.add(1); return l.length;")); + } + public void testCapturingReference() { + assertEquals(1, exec("int foo(Supplier t) { return t.get() }" + + "ArrayList l = new ArrayList(); l.add(1);" + + "return foo(l::getLength);")); + assertEquals(1, exec("int foo(Supplier t) { return t.get() }" + + "List l = new ArrayList(); l.add(1);" + + "return foo(l::getLength);")); assertEquals(1, exec("int foo(Supplier t) { return t.get() }" + "def l = new ArrayList(); l.add(1);" + "return foo(l::getLength);")); } - public void testIterable_Any() { - assertEquals(true, exec("List l = new ArrayList(); l.add(1); l.any(x -> x == 1)")); + assertEquals(true, + exec("List l = new ArrayList(); l.add(1); l.any(x -> x == 1)")); + } + + public void testIterable_Each() { + assertEquals(1, + exec("List l = new ArrayList(); l.add(1); List l2 = new ArrayList(); l.each(l2::add); return l2.size()")); + } + + public void testIterable_EachWithIndex() { + assertEquals(0, + exec("List l = new ArrayList(); l.add(2); Map m = new HashMap(); l.eachWithIndex(m::put); return m.get(2)")); + } + + public void testIterable_Every() { + assertEquals(false, exec("List l = new ArrayList(); l.add(1); l.add(2); l.every(x -> x == 1)")); + } + + public void testIterable_FindResults() { + assertEquals(1, + exec("List l = new ArrayList(); l.add(1); l.add(2); l.findResults(x -> x == 1 ? x : null).size()")); + } + + public void testIterable_GroupBy() { + assertEquals(2, + exec("List l = new ArrayList(); l.add(1); l.add(-1); l.groupBy(x -> x < 0 ? 'negative' : 'positive').size()")); + } + + public void testIterable_Join() { + assertEquals("test,ing", + exec("List l = new ArrayList(); l.add('test'); l.add('ing'); l.join(',')")); + } + + public void testIterable_Sum() { + assertEquals(5.0D, + exec("List l = new ArrayList(); l.add(1); l.add(2); l.sum(x -> x + 1)")); + } + + public void testCollection_Collect() { + assertEquals(Arrays.asList(2, 3), + exec("List l = new ArrayList(); l.add(1); l.add(2); l.collect(x -> x + 1)")); + assertEquals(asSet(2, 3), + exec("List l = new ArrayList(); l.add(1); l.add(2); l.collect(new HashSet(), x -> x + 1)")); + } + + public void testCollection_Find() { + assertEquals(2, + exec("List l = new ArrayList(); l.add(1); l.add(2); return l.find(x -> x == 2)")); + } + + public void testCollection_FindAll() { + assertEquals(Arrays.asList(2), + exec("List l = new ArrayList(); l.add(1); l.add(2); return l.findAll(x -> x == 2)")); + } + + public void testCollection_FindResult() { + assertEquals("found", + exec("List l = new ArrayList(); l.add(1); l.add(2); return l.findResult(x -> x > 1 ? 'found' : null)")); + assertEquals("notfound", + exec("List l = new ArrayList(); l.add(1); l.add(2); return l.findResult('notfound', x -> x > 10 ? 'found' : null)")); + } + + public void testCollection_Split() { + assertEquals(Arrays.asList(Arrays.asList(2), Arrays.asList(1)), + exec("List l = new ArrayList(); l.add(1); l.add(2); return l.split(x -> x == 2)")); + } + + public void testMap_Collect() { + assertEquals(Arrays.asList("one1", "two2"), + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; m.collect((key,value) -> key + value)")); + assertEquals(asSet("one1", "two2"), + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; m.collect(new HashSet(), (key,value) -> key + value)")); + } + + public void testMap_Count() { + assertEquals(1, + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; m.count((key,value) -> value == 2)")); + } + + public void testMap_Each() { + assertEquals(2, + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; Map m2 = new TreeMap(); m.each(m2::put); return m2.size()")); + } + + public void testMap_Every() { + assertEquals(false, + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; m.every((key,value) -> value == 2)")); + } + + public void testMap_Find() { + assertEquals("two", + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; return m.find((key,value) -> value == 2).key")); + } + + public void testMap_FindAll() { + assertEquals(Collections.singletonMap("two", 2), + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; return m.findAll((key,value) -> value == 2)")); + } + + public void testMap_FindResult() { + assertEquals("found", + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; return m.findResult((key,value) -> value == 2 ? 'found' : null)")); + assertEquals("notfound", + exec("Map m = new TreeMap(); m.one = 1; m.two = 2; " + + "return m.findResult('notfound', (key,value) -> value == 10 ? 'found' : null)")); + } + + public void testMap_FindResults() { + assertEquals(Arrays.asList("negative", "positive"), + exec("Map m = new TreeMap(); m.a = -1; m.b = 1; " + + "return m.findResults((key,value) -> value < 0 ? 'negative' : 'positive')")); + } + + public void testMap_GroupBy() { + Map> expected = new HashMap<>(); + expected.put("negative", Collections.singletonMap("a", -1)); + expected.put("positive", Collections.singletonMap("b", 1)); + assertEquals(expected, + exec("Map m = new TreeMap(); m.a = -1; m.b = 1; " + + "return m.groupBy((key,value) -> value < 0 ? 'negative' : 'positive')")); } }