All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sshd.common.util.GenericUtils Maven / Gradle / Ivy

The newest version!
/*
 * 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 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 iter) { return !isEmpty(iter); } public static boolean isEmpty(Iterator iter) { return (iter == null) || (!iter.hasNext()); } public static boolean isNotEmpty(Iterator 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 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 c1, List c2) { return findFirstDifferentValueIndex(c1, c2, UnaryEquator.defaultEquality()); } public static int findFirstDifferentValueIndex( List c1, List c2, UnaryEquator 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 c1, Iterable c2) { return findFirstDifferentValueIndex(c1, c2, UnaryEquator.defaultEquality()); } public static int findFirstDifferentValueIndex( Iterable c1, Iterable c2, UnaryEquator equator) { return findFirstDifferentValueIndex(iteratorOf(c1), iteratorOf(c2), equator); } public static int findFirstDifferentValueIndex(Iterator i1, Iterator i2) { return findFirstDifferentValueIndex(i1, i2, UnaryEquator.defaultEquality()); } public static int findFirstDifferentValueIndex( Iterator i1, Iterator i2, UnaryEquator 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 coll, Iterable values) { if (isEmpty(coll)) { return false; } for (T v : values) { if (coll.contains(v)) { return true; } } return false; } public static void forEach( Iterable values, Consumer consumer) { if (isNotEmpty(values)) { values.forEach(consumer); } } public static List map( Collection values, Function mapper) { return stream(values).map(mapper).collect(Collectors.toList()); } public static NavigableSet mapSort( Collection values, Function mapper, Comparator comparator) { return stream(values).map(mapper).collect(toSortedSet(comparator)); } public static Collector> toSortedSet(Comparator 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 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 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 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 comp, Collection 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 acceptor, T... values) { return findFirstMatchingMember(acceptor, isEmpty(values) ? Collections.emptyList() : Arrays.asList(values)); } public static T findFirstMatchingMember( Predicate acceptor, Collection 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 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 acceptor, Collection 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 it) { if (it == null) { return null; } else if (it instanceof Deque) { // check before (!) instanceof List since LinkedList implements List Deque l = (Deque) it; return !l.isEmpty() ? l.getFirst() : null; } else if (it instanceof List) { List l = (List) it; return !l.isEmpty() ? l.get(0) : null; } else if (it instanceof SortedSet) { SortedSet s = (SortedSet) it; return !s.isEmpty() ? s.first() : null; } else { Iterator 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 iter, Function mapper) { return () -> wrapIterator(iter, mapper); } @SuppressWarnings({ "unchecked", "rawtypes" }) public static Iterator wrapIterator( Iterable iter, Function mapper) { return (Iterator) stream(iter).map(mapper).iterator(); } public static Iterator wrapIterator( Iterator iter, Function mapper) { Iterator 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>> 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 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(); } }