/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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.elasticsearch.painless.api; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; 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; import java.util.function.Predicate; import java.util.function.ToDoubleFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; /** 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 <T> int getLength(List<T> 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 <T> boolean any(Iterable<T> receiver, Predicate<T> predicate) { for (T t : receiver) { if (predicate.test(t)) { return true; } } return false; } /** Converts this Iterable to a Collection. Returns the original Iterable if it is already a Collection. */ public static <T> Collection<T> asCollection(Iterable<T> receiver) { if (receiver instanceof Collection) { return (Collection<T>)receiver; } List<T> list = new ArrayList<>(); for (T t : receiver) { list.add(t); } return list; } /** Converts this Iterable to a List. Returns the original Iterable if it is already a List. */ public static <T> List<T> asList(Iterable<T> receiver) { if (receiver instanceof List) { return (List<T>)receiver; } List<T> list = new ArrayList<>(); for (T t : receiver) { list.add(t); } return list; } /** Counts the number of occurrences which satisfy the given predicate from inside this Iterable. */ public static <T> int count(Iterable<T> receiver, Predicate<T> predicate) { int count = 0; for (T t : receiver) { if (predicate.test(t)) { count++; } } return count; } // 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 <T> Object each(Iterable<T> receiver, Consumer<T> consumer) { receiver.forEach(consumer); return receiver; } /** * Iterates through an iterable type, passing each item and the item's index * (a counter starting at zero) to the given consumer. */ public static <T> Object eachWithIndex(Iterable<T> receiver, ObjIntConsumer<T> consumer) { int count = 0; for (T t : receiver) { consumer.accept(t, count++); } return receiver; } /** * Used to determine if the given predicate is valid (i.e. returns true for all items in this iterable). */ public static <T> boolean every(Iterable<T> receiver, Predicate<T> predicate) { for (T t : receiver) { if (predicate.test(t) == false) { return false; } } return true; } /** * Iterates through the Iterable transforming items using the supplied function and * collecting any non-null results. */ public static <T,U> List<U> findResults(Iterable<T> receiver, Function<T,U> filter) { List<U> list = new ArrayList<>(); for (T t: receiver) { U result = filter.apply(t); if (result != null) { list.add(result); } } return list; } /** * Sorts all Iterable members into groups determined by the supplied mapping function. */ public static <T,U> Map<U,List<T>> groupBy(Iterable<T> receiver, Function<T,U> mapper) { Map<U,List<T>> map = new LinkedHashMap<>(); for (T t : receiver) { U mapped = mapper.apply(t); List<T> results = map.get(mapped); if (results == null) { results = new ArrayList<>(); map.put(mapped, results); } results.add(t); } return map; } /** * Concatenates the toString() representation of each item in this Iterable, * with the given String as a separator between each item. */ public static <T> String join(Iterable<T> receiver, String separator) { StringBuilder sb = new StringBuilder(); for (T t : receiver) { if (sb.length() > 0) { sb.append(separator); } sb.append(t); } return sb.toString(); } /** * Sums the result of an Iterable */ public static <T extends Number> double sum(Iterable<T> receiver) { double sum = 0; for (T t : receiver) { sum += t.doubleValue(); } return sum; } /** * Sums the result of applying a function to each item of an Iterable. */ public static <T> double sum(Iterable<T> receiver, ToDoubleFunction<T> function) { double sum = 0; for (T t : receiver) { sum += function.applyAsDouble(t); } 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 <T,U> List<U> collect(Collection<T> receiver, Function<T,U> function) { List<U> 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 <T,U> Object collect(Collection<T> receiver, Collection<U> collection, Function<T,U> 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> T find(Collection<T> receiver, Predicate<T> 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 <T> List<T> findAll(Collection<T> receiver, Predicate<T> predicate) { List<T> 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 <T,U> Object findResult(Collection<T> receiver, Function<T,U> 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 <T,U> Object findResult(Collection<T> receiver, Object defaultResult, Function<T,U> 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 <T> List<List<T>> split(Collection<T> receiver, Predicate<T> predicate) { List<T> matched = new ArrayList<>(); List<T> unmatched = new ArrayList<>(); List<List<T>> 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 <K,V,T> List<T> collect(Map<K,V> receiver, BiFunction<K,V,T> function) { List<T> list = new ArrayList<>(); for (Map.Entry<K,V> 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 <K,V,T> Object collect(Map<K,V> receiver, Collection<T> collection, BiFunction<K,V,T> function) { for (Map.Entry<K,V> 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 <K,V> int count(Map<K,V> receiver, BiPredicate<K,V> predicate) { int count = 0; for (Map.Entry<K,V> 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 <K,V> Object each(Map<K,V> receiver, BiConsumer<K,V> 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 <K,V> boolean every(Map<K,V> receiver, BiPredicate<K,V> predicate) { for (Map.Entry<K,V> 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 <K,V> Map.Entry<K,V> find(Map<K,V> receiver, BiPredicate<K,V> predicate) { for (Map.Entry<K,V> 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 <K,V> Map<K,V> findAll(Map<K,V> receiver, BiPredicate<K,V> predicate) { // try to preserve some properties of the receiver (see the groovy javadocs) final Map<K,V> map; if (receiver instanceof TreeMap) { map = new TreeMap<>(); } else { map = new LinkedHashMap<>(); } for (Map.Entry<K,V> 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 <K,V,T> Object findResult(Map<K,V> receiver, BiFunction<K,V,T> 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 <K,V,T> Object findResult(Map<K,V> receiver, Object defaultResult, BiFunction<K,V,T> function) { for (Map.Entry<K,V> 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 <K,V,T> List<T> findResults(Map<K,V> receiver, BiFunction<K,V,T> filter) { List<T> list = new ArrayList<>(); for (Map.Entry<K,V> 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 <K,V,T> Map<T,Map<K,V>> groupBy(Map<K,V> receiver, BiFunction<K,V,T> mapper) { Map<T,Map<K,V>> map = new LinkedHashMap<>(); for (Map.Entry<K,V> kvPair : receiver.entrySet()) { T mapped = mapper.apply(kvPair.getKey(), kvPair.getValue()); Map<K,V> 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; } // CharSequence augmentation /** * Replace all matches. Similar to {@link Matcher#replaceAll(String)} but allows you to customize the replacement based on the match. */ public static String replaceAll(CharSequence receiver, Pattern pattern, Function<Matcher, String> replacementBuilder) { Matcher m = pattern.matcher(receiver); if (false == m.find()) { // CharSequqence's toString is *supposed* to always return the characters in the sequence as a String return receiver.toString(); } StringBuffer result = new StringBuffer(initialBufferForReplaceWith(receiver)); do { m.appendReplacement(result, Matcher.quoteReplacement(replacementBuilder.apply(m))); } while (m.find()); m.appendTail(result); return result.toString(); } /** * Replace the first match. Similar to {@link Matcher#replaceFirst(String)} but allows you to customize the replacement based on the * match. */ public static String replaceFirst(CharSequence receiver, Pattern pattern, Function<Matcher, String> replacementBuilder) { Matcher m = pattern.matcher(receiver); if (false == m.find()) { // CharSequqence's toString is *supposed* to always return the characters in the sequence as a String return receiver.toString(); } StringBuffer result = new StringBuffer(initialBufferForReplaceWith(receiver)); m.appendReplacement(result, Matcher.quoteReplacement(replacementBuilder.apply(m))); m.appendTail(result); return result.toString(); } /** * The initial size of the {@link StringBuilder} used for {@link #replaceFirst(CharSequence, Pattern, Function)} and * {@link #replaceAll(CharSequence, Pattern, Function)} for a particular sequence. We ape * {{@link StringBuilder#StringBuilder(CharSequence)} here and add 16 extra chars to the buffer to have a little room for growth. */ private static int initialBufferForReplaceWith(CharSequence seq) { return seq.length() + 16; } /** * Encode a String in Base64. Use {@link Base64.Encoder#encodeToString(byte[])} if you have to encode bytes rather than a string. */ public static String encodeBase64(String receiver) { return Base64.getEncoder().encodeToString(receiver.getBytes(StandardCharsets.UTF_8)); } /** * Decode some Base64 bytes and build a UTF-8 encoded string. Use {@link Base64.Decoder#decode(String)} if you'd prefer bytes to work * with bytes. */ public static String decodeBase64(String receiver) { return new String(Base64.getDecoder().decode(receiver.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); } }