
org.elasticsearch.painless.api.Augmentation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lang-painless Show documentation
Show all versions of lang-painless Show documentation
Elasticsearch module: lang-painless
The newest version!
/*
* 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 org.elasticsearch.common.hash.MessageDigests;
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.Locale;
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.Supplier;
import java.util.function.ToDoubleFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
/** 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)) {
return true;
}
}
return false;
}
/** Converts this Iterable to a Collection. Returns the original Iterable if it is already a Collection. */
public static Collection asCollection(Iterable receiver) {
if (receiver instanceof Collection) {
return (Collection)receiver;
}
List 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 List asList(Iterable receiver) {
if (receiver instanceof List) {
return (List)receiver;
}
List 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 int count(Iterable receiver, Predicate 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 Object each(Iterable receiver, Consumer 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 Object eachWithIndex(Iterable receiver, ObjIntConsumer 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 boolean every(Iterable receiver, Predicate 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 List findResults(Iterable receiver, Function filter) {
List 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 Map> groupBy(Iterable receiver, Function mapper) {
Map> map = new LinkedHashMap<>();
for (T t : receiver) {
U mapped = mapper.apply(t);
List 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 String join(Iterable 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 double sum(Iterable 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 double sum(Iterable receiver, ToDoubleFunction 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 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;
}
// 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 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 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);
}
/**
* Split 'receiver' by 'token' as many times as possible..
*/
public static String[] splitOnToken(String receiver, String token) {
return splitOnToken(receiver, token, -1);
}
/**
* Split 'receiver' by 'token' up to 'limit' times. Any limit less than 1 is ignored.
*/
public static String[] splitOnToken(String receiver, String token, int limit) {
// Check if it's even possible to perform a split
if (receiver == null || receiver.length() == 0 || token == null || token.length() == 0 || receiver.length() < token.length()) {
return new String[] { receiver };
}
// List of string segments we have found
ArrayList result = new ArrayList();
// Keep track of where we are in the string
// indexOf(tok, startPos) is faster than creating a new search context ever loop with substring(start, end)
int pos = 0;
// Loop until we hit the limit or forever if we are passed in less than one (signifying no limit)
// If Integer.MIN_VALUE is passed in, it will still continue to loop down to 1 from MAX_VALUE
// This edge case should be fine as we are limited by receiver length (Integer.MAX_VALUE) even if we split at every char
for(;limit != 1; limit--) {
// Find the next occurrence of token after current pos
int idx = receiver.indexOf(token, pos);
// Reached the end of the string without another match
if (idx == -1) {
break;
}
// Add the found segment to the result list
result.add(receiver.substring(pos, idx));
// Move our search position to the next possible location
pos = idx + token.length();
}
// Add the remaining string to the result list
result.add(receiver.substring(pos));
// O(N) or faster depending on implementation
return result.toArray(new String[0]);
}
/**
* Access values in nested containers with a dot separated path. Path elements are treated
* as strings for Maps and integers for Lists.
* @throws IllegalArgumentException if any of the following:
* - path is empty
* - path contains a trailing '.' or a repeated '.'
* - an element of the path does not exist, ie key or index not present
* - there is a non-container type at a non-terminal path element
* - a path element for a List is not an integer
* @return object at path
*/
public static Object getByPath(List receiver, String path) {
return getByPathDispatch(receiver, splitPath(path), 0, throwCantFindValue(path));
}
/**
* Same as {@link #getByPath(List, String)}, but for Map.
*/
public static Object getByPath(Map receiver, String path) {
return getByPathDispatch(receiver, splitPath(path), 0, throwCantFindValue(path));
}
/**
* Same as {@link #getByPath(List, String)}, but with a default value.
* @return element at path or {@code defaultValue} if the terminal path element does not exist.
*/
public static Object getByPath(List receiver, String path, Object defaultValue) {
return getByPathDispatch(receiver, splitPath(path), 0, () -> defaultValue);
}
/**
* Same as {@link #getByPath(List, String, Object)}, but for Map.
*/
public static Object getByPath(Map receiver, String path, Object defaultValue) {
return getByPathDispatch(receiver, splitPath(path), 0, () -> defaultValue);
}
// Dispatches to getByPathMap, getByPathList or returns obj if done. See handleMissing for dealing with missing
// elements.
private static Object getByPathDispatch(Object obj, String[] elements, int i, Supplier
© 2015 - 2025 Weber Informatics LLC | Privacy Policy