org.apache.sshd.common.util.GenericUtils Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.sshd.common.util;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.sshd.common.util.functors.UnaryEquator;
/**
* @author Apache MINA SSHD Project
*/
public final class GenericUtils {
public static final byte[] EMPTY_BYTE_ARRAY = {};
public static final char[] EMPTY_CHAR_ARRAY = {};
public static final String[] EMPTY_STRING_ARRAY = {};
public static final Object[] EMPTY_OBJECT_ARRAY = {};
public static final boolean[] EMPTY_BOOLEAN_ARRAY = {};
/**
* A value indicating a {@code null} value - to be used as a placeholder where {@code null}s are not allowed
*/
public static final Object NULL = new Object();
/**
* The complement of {@link String#CASE_INSENSITIVE_ORDER}
*/
public static final Comparator CASE_SENSITIVE_ORDER = (s1, s2) -> {
if (s1 == s2) {
return 0;
} else {
return s1.compareTo(s2);
}
};
public static final String QUOTES = "\"'";
private GenericUtils() {
throw new UnsupportedOperationException("No instance");
}
public static String trimToEmpty(String s) {
if (s == null) {
return "";
} else {
return s.trim();
}
}
public static String replaceWhitespaceAndTrim(String s) {
if (s != null) {
s = s.replace('\t', ' ');
}
return trimToEmpty(s);
}
/**
*
* Replace a String with another String inside a larger String, for the first max
values of the search
* String.
*
*
*
* A {@code null} reference passed to this method is a no-op.
*
*
* @param text text to search and replace in
* @param repl String to search for
* @param with String to replace with
* @param max maximum number of values to replace, or -1
if no maximum
* @return the text with any replacements processed
* @author Arnout J. Kuiper [email protected]
* @author Magesh Umasankar
* @author Bruce Atherton
* @author Antoine Levy-Lambert
*/
@SuppressWarnings("PMD.AssignmentInOperand")
public static String replace(String text, String repl, String with, int max) {
if ((text == null) || (repl == null) || (with == null) || (repl.length() == 0)) {
return text;
}
int start = 0;
StringBuilder buf = new StringBuilder(text.length());
for (int end = text.indexOf(repl, start); end != -1; end = text.indexOf(repl, start)) {
buf.append(text.substring(start, end)).append(with);
start = end + repl.length();
if (--max == 0) {
break;
}
}
buf.append(text.substring(start));
return buf.toString();
}
/**
* @param s The {@link String} value to calculate the hash code on - may be {@code null}/empty in which case a
* value of zero is returned
* @return The calculated hash code
* @see #hashCode(String, Boolean)
*/
public static int hashCode(String s) {
return hashCode(s, null);
}
/**
* @param s The {@link String} value to calculate the hash code on - may be {@code null}/empty in which
* case a value of zero is returned
* @param useUppercase Whether to convert the string to uppercase, lowercase or not at all:
*
* - {@code null} - no conversion
* - {@link Boolean#TRUE} - get hash code of uppercase
* - {@link Boolean#FALSE} - get hash code of lowercase
*
* @return The calculated hash code
*/
public static int hashCode(String s, Boolean useUppercase) {
if (isEmpty(s)) {
return 0;
} else if (useUppercase == null) {
return s.hashCode();
} else if (useUppercase.booleanValue()) {
return s.toUpperCase().hashCode();
} else {
return s.toLowerCase().hashCode();
}
}
public static int safeCompare(String s1, String s2, boolean caseSensitive) {
if (UnaryEquator.isSameReference(s1, s2)) {
return 0;
} else if (s1 == null) {
return +1; // push null(s) to end
} else if (s2 == null) {
return -1; // push null(s) to end
} else if (caseSensitive) {
return s1.compareTo(s2);
} else {
return s1.compareToIgnoreCase(s2);
}
}
public static int length(CharSequence cs) {
return cs == null ? 0 : cs.length();
}
public static boolean isEmpty(CharSequence cs) {
return length(cs) <= 0;
}
public static boolean isNotEmpty(CharSequence cs) {
return !isEmpty(cs);
}
/**
*
* Checks if a CharSequence is empty (""), null or whitespace only.
*
*
*
* Whitespace is defined by {@link Character#isWhitespace(char)}.
*
*
*
* GenericUtils.isBlank(null) = true
* GenericUtils.isBlank("") = true
* GenericUtils.isBlank(" ") = true
* GenericUtils.isBlank("bob") = false
* GenericUtils.isBlank(" bob ") = false
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is null, empty or whitespace only
*/
public static boolean isBlank(final CharSequence cs) {
int strLen = cs != null ? cs.length() : 0;
if (cs == null || strLen == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
public static boolean isNotBlank(final CharSequence cs) {
return !isBlank(cs);
}
public static List filterToNotBlank(final List values) {
return values.stream().filter(GenericUtils::isNotBlank).collect(Collectors.toList());
}
public static int indexOf(CharSequence cs, char c) {
int len = length(cs);
for (int pos = 0; pos < len; pos++) {
char ch = cs.charAt(pos);
if (ch == c) {
return pos;
}
}
return -1;
}
public static int lastIndexOf(CharSequence cs, char c) {
int len = length(cs);
for (int pos = len - 1; pos >= 0; pos--) {
char ch = cs.charAt(pos);
if (ch == c) {
return pos;
}
}
return -1;
}
// a List would be better, but we want to be compatible with String.split(...)
public static String[] split(String s, char ch) {
if (isEmpty(s)) {
return EMPTY_STRING_ARRAY;
}
int lastPos = 0;
int curPos = s.indexOf(ch);
if (curPos < 0) {
return new String[] { s };
}
Collection values = new LinkedList<>();
do {
String v = s.substring(lastPos, curPos);
values.add(v);
// skip separator
lastPos = curPos + 1;
if (lastPos >= s.length()) {
break;
}
curPos = s.indexOf(ch, lastPos);
if (curPos < lastPos) {
break; // no more separators
}
} while (curPos < s.length());
// check if any leftovers
if (lastPos < s.length()) {
String v = s.substring(lastPos);
values.add(v);
}
return values.toArray(new String[values.size()]);
}
public static String join(T[] values, char ch) {
return join(isEmpty(values) ? Collections. emptyList() : Arrays.asList(values), ch);
}
public static String join(Iterable> iter, char ch) {
return join((iter == null) ? null : iter.iterator(), ch);
}
public static String join(Iterator> iter, char ch) {
if ((iter == null) || (!iter.hasNext())) {
return "";
}
StringBuilder sb = new StringBuilder();
do { // we already asked hasNext...
Object o = iter.next();
if (sb.length() > 0) {
sb.append(ch);
}
sb.append(Objects.toString(o));
} while (iter.hasNext());
return sb.toString();
}
public static String join(T[] values, CharSequence sep) {
return join(isEmpty(values) ? Collections. emptyList() : Arrays.asList(values), sep);
}
public static String join(Iterable> iter, CharSequence sep) {
return join((iter == null) ? null : iter.iterator(), sep);
}
public static String join(Iterator> iter, CharSequence sep) {
if ((iter == null) || (!iter.hasNext())) {
return "";
}
StringBuilder sb = new StringBuilder();
do { // we already asked hasNext...
Object o = iter.next();
if (sb.length() > 0) {
sb.append(sep);
}
sb.append(Objects.toString(o));
} while (iter.hasNext());
return sb.toString();
}
public static int size(Collection> c) {
return (c == null) ? 0 : c.size();
}
public static boolean isEmpty(Collection> c) {
return size(c) <= 0;
}
public static boolean isNotEmpty(Collection> c) {
return !isEmpty(c);
}
/**
*
* @param Generic element type
* @param c1 First collection
* @param c2 Second collection
* @return {@code true} if the following holds:
*
* - Same size - Note: {@code null} collections are consider equal to empty ones
*
* - First collection contains all elements of second one and vice versa
*
*/
public static boolean equals(Collection c1, Collection c2) {
if (isEmpty(c1)) {
return isEmpty(c2);
} else if (isEmpty(c2)) {
return false;
}
return (c1.size() == c2.size())
&& c1.containsAll(c2)
&& c2.containsAll(c1);
}
@SafeVarargs
public static int length(T... a) {
return (a == null) ? 0 : a.length;
}
public static boolean isEmpty(Iterable extends T> iter) {
if (iter == null) {
return true;
} else if (iter instanceof Collection>) {
return isEmpty((Collection>) iter);
} else {
return isEmpty(iter.iterator());
}
}
public static boolean isNotEmpty(Iterable extends T> iter) {
return !isEmpty(iter);
}
public static boolean isEmpty(Iterator extends T> iter) {
return (iter == null) || (!iter.hasNext());
}
public static boolean isNotEmpty(Iterator extends T> iter) {
return !isEmpty(iter);
}
public static boolean isEmpty(T[] a) {
return length(a) <= 0;
}
public static int length(char[] chars) {
return (chars == null) ? 0 : chars.length;
}
public static boolean isEmpty(char[] chars) {
return length(chars) <= 0;
}
/**
* Compares 2 character arrays - Note: {@code null} and empty are considered equal
*
* @param c1 1st array
* @param c2 2nd array
* @return Negative is 1st array comes first in lexicographical order, positive if 2nd array comes first and zero
* if equal
*/
public static int compare(char[] c1, char[] c2) {
int l1 = length(c1);
int l2 = length(c2);
int cmpLen = Math.min(l1, l2);
for (int index = 0; index < cmpLen; index++) {
char c11 = c1[index];
char c22 = c2[index];
int nRes = Character.compare(c11, c22);
if (nRes != 0) {
return nRes;
}
}
int nRes = Integer.compare(l1, l2);
if (nRes != 0) {
return nRes;
}
return 0;
}
@SafeVarargs // there is no EnumSet.of(...) so we have to provide our own
public static > Set of(E... values) {
return of(isEmpty(values) ? Collections.emptySet() : Arrays.asList(values));
}
public static > Set of(Collection extends E> values) {
if (isEmpty(values)) {
return Collections.emptySet();
}
Set result = null;
for (E v : values) {
/*
* A trick to compensate for the fact that we do not have the enum Class to invoke EnumSet.noneOf
*/
if (result == null) {
result = EnumSet.of(v);
} else {
result.add(v);
}
}
return result;
}
public static int findFirstDifferentValueIndex(List extends T> c1, List extends T> c2) {
return findFirstDifferentValueIndex(c1, c2, UnaryEquator.defaultEquality());
}
public static int findFirstDifferentValueIndex(
List extends T> c1, List extends T> c2, UnaryEquator super T> equator) {
Objects.requireNonNull(equator, "No equator provided");
int l1 = size(c1);
int l2 = size(c2);
for (int index = 0, count = Math.min(l1, l2); index < count; index++) {
T v1 = c1.get(index);
T v2 = c2.get(index);
if (!equator.test(v1, v2)) {
return index;
}
}
// all common length items are equal - check length
if (l1 < l2) {
return l1;
} else if (l2 < l1) {
return l2;
} else {
return -1;
}
}
public static int findFirstDifferentValueIndex(Iterable extends T> c1, Iterable extends T> c2) {
return findFirstDifferentValueIndex(c1, c2, UnaryEquator.defaultEquality());
}
public static int findFirstDifferentValueIndex(
Iterable extends T> c1, Iterable extends T> c2, UnaryEquator super T> equator) {
return findFirstDifferentValueIndex(iteratorOf(c1), iteratorOf(c2), equator);
}
public static int findFirstDifferentValueIndex(Iterator extends T> i1, Iterator extends T> i2) {
return findFirstDifferentValueIndex(i1, i2, UnaryEquator.defaultEquality());
}
public static int findFirstDifferentValueIndex(
Iterator extends T> i1, Iterator extends T> i2, UnaryEquator super T> equator) {
Objects.requireNonNull(equator, "No equator provided");
i1 = iteratorOf(i1);
i2 = iteratorOf(i2);
for (int index = 0;; index++) {
if (i1.hasNext()) {
if (i2.hasNext()) {
T v1 = i1.next();
T v2 = i2.next();
if (!equator.test(v1, v2)) {
return index;
}
} else {
return index;
}
} else if (i2.hasNext()) {
return index;
} else {
return -1; // neither has a next value - both exhausted at the same time
}
}
}
public static boolean containsAny(
Collection extends T> coll, Iterable extends T> values) {
if (isEmpty(coll)) {
return false;
}
for (T v : values) {
if (coll.contains(v)) {
return true;
}
}
return false;
}
public static void forEach(
Iterable extends T> values, Consumer super T> consumer) {
if (isNotEmpty(values)) {
values.forEach(consumer);
}
}
public static List map(
Collection extends T> values, Function super T, ? extends U> mapper) {
return stream(values).map(mapper).collect(Collectors.toList());
}
public static NavigableSet mapSort(
Collection extends T> values, Function super T, ? extends U> mapper, Comparator super U> comparator) {
return stream(values).map(mapper).collect(toSortedSet(comparator));
}
public static Collector> toSortedSet(Comparator super T> comparator) {
return Collectors.toCollection(() -> new TreeSet<>(comparator));
}
public static Stream stream(Iterable values) {
if (isEmpty(values)) {
return Stream.empty();
} else if (values instanceof Collection>) {
return ((Collection) values).stream();
} else {
return StreamSupport.stream(values.spliterator(), false);
}
}
@SafeVarargs
public static List unmodifiableList(T... values) {
return unmodifiableList(asList(values));
}
public static List unmodifiableList(Collection extends T> values) {
if (isEmpty(values)) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(new ArrayList<>(values));
}
}
public static List unmodifiableList(Stream values) {
return unmodifiableList(values.collect(Collectors.toList()));
}
@SafeVarargs
public static List asList(T... values) {
return isEmpty(values) ? Collections.emptyList() : Arrays.asList(values);
}
@SafeVarargs
public static Set asSet(T... values) {
return new HashSet<>(asList(values));
}
@SafeVarargs
public static > NavigableSet asSortedSet(V... values) {
return asSortedSet(Comparator.naturalOrder(), values);
}
public static > NavigableSet asSortedSet(Collection extends V> values) {
return asSortedSet(Comparator.naturalOrder(), values);
}
/**
* @param The element type
* @param comp The (non-{@code null}) {@link Comparator} to use
* @param values The values to be added (ignored if {@code null})
* @return A {@link NavigableSet} containing the values (if any) sorted using the provided comparator
*/
@SafeVarargs
public static NavigableSet asSortedSet(Comparator super V> comp, V... values) {
return asSortedSet(comp, isEmpty(values) ? Collections.emptyList() : Arrays.asList(values));
}
/**
* @param The element type
* @param comp The (non-{@code null}) {@link Comparator} to use
* @param values The values to be added (ignored if {@code null}/empty)
* @return A {@link NavigableSet} containing the values (if any) sorted using the provided comparator
*/
public static NavigableSet asSortedSet(
Comparator super V> comp, Collection extends V> values) {
NavigableSet set = new TreeSet<>(Objects.requireNonNull(comp, "No comparator"));
if (size(values) > 0) {
set.addAll(values);
}
return set;
}
@SafeVarargs
public static T findFirstMatchingMember(Predicate super T> acceptor, T... values) {
return findFirstMatchingMember(acceptor,
isEmpty(values) ? Collections.emptyList() : Arrays.asList(values));
}
public static T findFirstMatchingMember(
Predicate super T> acceptor, Collection extends T> values) {
List matches = selectMatchingMembers(acceptor, values);
return GenericUtils.isEmpty(matches) ? null : matches.get(0);
}
/**
* Returns a list of all the values that were accepted by a predicate
*
* @param The type of value being evaluated
* @param acceptor The {@link Predicate} to consult whether a member is selected
* @param values The values to be scanned
* @return A {@link List} of all the values that were accepted by the predicate
*/
@SafeVarargs
public static List selectMatchingMembers(Predicate super T> acceptor, T... values) {
return selectMatchingMembers(acceptor,
isEmpty(values) ? Collections.emptyList() : Arrays.asList(values));
}
/**
* Returns a list of all the values that were accepted by a predicate
*
* @param The type of value being evaluated
* @param acceptor The {@link Predicate} to consult whether a member is selected
* @param values The values to be scanned
* @return A {@link List} of all the values that were accepted by the predicate
*/
public static List selectMatchingMembers(
Predicate super T> acceptor, Collection extends T> values) {
return GenericUtils.stream(values)
.filter(acceptor)
.collect(Collectors.toList());
}
/**
* @param s The {@link CharSequence} to be checked
* @return If the sequence contains any of the {@link #QUOTES} on both ends, then they are stripped,
* otherwise nothing is done
* @see #stripDelimiters(CharSequence, char)
*/
public static CharSequence stripQuotes(CharSequence s) {
if (isEmpty(s)) {
return s;
}
for (int index = 0; index < QUOTES.length(); index++) {
char delim = QUOTES.charAt(index);
CharSequence v = stripDelimiters(s, delim);
if (v != s) { // if stripped one don't continue
return v;
}
}
return s;
}
/**
* @param s The {@link CharSequence} to be checked
* @param delim The expected delimiter
* @return If the sequence contains the delimiter on both ends, then it is are stripped, otherwise
* nothing is done
*/
public static CharSequence stripDelimiters(CharSequence s, char delim) {
if (isEmpty(s) || (s.length() < 2)) {
return s;
}
int lastPos = s.length() - 1;
if ((s.charAt(0) != delim) || (s.charAt(lastPos) != delim)) {
return s;
} else {
return s.subSequence(1, lastPos);
}
}
/**
* Wraps a value into a {@link Supplier}
*
* @param Type of value being supplied
* @param value The value to be supplied
* @return The supplier wrapper
*/
public static Supplier supplierOf(T value) {
return () -> value;
}
/**
* Resolves to an always non-{@code null} iterator
*
* @param Type of value being iterated
* @param iterable The {@link Iterable} instance
* @return A non-{@code null} iterator which may be empty if no iterable instance or no iterator returned
* from it
* @see #iteratorOf(Iterator)
*/
public static Iterator iteratorOf(Iterable iterable) {
return iteratorOf((iterable == null) ? null : iterable.iterator());
}
/**
* @param Generic base class
* @param Generic child class
* @return An identity {@link Function} that returns its input child class as a base class
*/
public static Function downcast() {
return t -> t;
}
/**
* Returns the first element in iterable - it has some optimization for {@link List}-s {@link Deque}-s and
* {@link SortedSet}s.
*
* @param Type of element
* @param it The {@link Iterable} instance - ignored if {@code null}/empty
* @return first element by iteration or {@code null} if none available
*/
public static T head(Iterable extends T> it) {
if (it == null) {
return null;
} else if (it instanceof Deque>) { // check before (!) instanceof List since LinkedList implements List
Deque extends T> l = (Deque extends T>) it;
return !l.isEmpty() ? l.getFirst() : null;
} else if (it instanceof List>) {
List extends T> l = (List extends T>) it;
return !l.isEmpty() ? l.get(0) : null;
} else if (it instanceof SortedSet>) {
SortedSet extends T> s = (SortedSet extends T>) it;
return !s.isEmpty() ? s.first() : null;
} else {
Iterator extends T> iter = it.iterator();
return ((iter == null) || (!iter.hasNext())) ? null : iter.next();
}
}
/**
* Resolves to an always non-{@code null} iterator
*
* @param Type of value being iterated
* @param iter The {@link Iterator} instance
* @return A non-{@code null} iterator which may be empty if no iterator instance
* @see Collections#emptyIterator()
*/
public static Iterator iteratorOf(Iterator iter) {
return (iter == null) ? Collections.emptyIterator() : iter;
}
public static Iterable wrapIterable(
Iterable extends U> iter, Function super U, ? extends V> mapper) {
return () -> wrapIterator(iter, mapper);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Iterator wrapIterator(
Iterable extends U> iter, Function super U, ? extends V> mapper) {
return (Iterator) stream(iter).map(mapper).iterator();
}
public static Iterator wrapIterator(
Iterator extends U> iter, Function super U, ? extends V> mapper) {
Iterator extends U> iterator = iteratorOf(iter);
return new Iterator() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public V next() {
U value = iterator.next();
return mapper.apply(value);
}
};
}
/**
* @param Generic return type
* @param values The source values - ignored if {@code null}
* @param type The (never @code null) type of values to select - any value whose type is assignable to this type
* will be selected by the iterator.
* @return The first value that matches the specified type - {@code null} if none found
*/
public static T selectNextMatchingValue(Iterator> values, Class type) {
Objects.requireNonNull(type, "No type selector specified");
if (values == null) {
return null;
}
while (values.hasNext()) {
Object o = values.next();
if (o == null) {
continue;
}
Class> t = o.getClass();
if (type.isAssignableFrom(t)) {
return type.cast(o);
}
}
return null;
}
/**
* Wraps a group of {@link Supplier}s of {@link Iterable} instances into a "unified" {@link Iterable} of
* their values, in the same order as the suppliers - i.e., once the values from a specific supplier are exhausted,
* the next one is consulted, and so on, until all suppliers have been consulted
*
* @param Type of value being iterated
* @param providers The providers - ignored if {@code null} (i.e., return an empty iterable instance)
* @return The wrapping instance
*/
public static Iterable multiIterableSuppliers(
Iterable extends Supplier extends Iterable extends T>>> providers) {
return () -> stream(providers). flatMap(s -> stream(s.get())).map(Function.identity()).iterator();
}
/**
* The delegate Suppliers get() method is called exactly once and the result is cached.
*
* @param Generic type of supplied value
* @param delegate The actual Supplier
* @return The memoized Supplier
*/
public static Supplier memoizeLock(Supplier extends T> delegate) {
AtomicReference value = new AtomicReference<>();
return () -> {
T val = value.get();
if (val == null) {
synchronized (value) {
val = value.get();
if (val == null) {
val = Objects.requireNonNull(delegate.get());
value.set(val);
}
}
}
return val;
};
}
/**
* Check if a duration is positive
*
* @param d the duration
* @return true
if the duration is greater than zero
*/
public static boolean isPositive(Duration d) {
return !isNegativeOrNull(d);
}
/**
* Check if a duration is negative or zero
*
* @param d the duration
* @return true
if the duration is negative or zero
*/
public static boolean isNegativeOrNull(Duration d) {
return d.isNegative() || d.isZero();
}
}