http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java new file mode 100644 index 0000000..1620bad --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java @@ -0,0 +1,212 @@ +/* + * 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.lang.reflect.Proxy; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EventListener; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class EventListenerUtils { + /** + * A special "comparator" whose only purpose is to ensure + * there are no same references in a listener's set - to be used + * in conjunction with a {@code TreeSet} as its comparator + */ + @SuppressWarnings("checkstyle:anoninnerlength") + public static final Comparator<EventListener> LISTENER_INSTANCE_COMPARATOR = (l1, l2) -> { + if (l1 == l2) { + return 0; + } else if (l1 == null) { + return 1; + } else if (l2 == null) { + return -1; + } + + Class<?> c1 = l1.getClass(); + Class<?> c2 = l2.getClass(); + boolean checkHashCodes = true; + if (Proxy.isProxyClass(c1)) { + if (Proxy.isProxyClass(c2)) { + checkHashCodes = false; // cannot call hashCode on a proxy + } else { + return 1; + } + } else if (Proxy.isProxyClass(c2)) { + return -1; + } + + if (checkHashCodes) { + int nRes = Integer.compare(l1.hashCode(), l2.hashCode()); + if (nRes != 0) { + return nRes; + } + } + + int nRes = Integer.compare(System.identityHashCode(l1), System.identityHashCode(l2)); + if (nRes != 0) { + return nRes; + } + + if (c1 != c2) { + return c1.getName().compareTo(c2.getName()); + } + + String s1 = Objects.toString(l1.toString(), ""); + String s2 = Objects.toString(l2.toString(), ""); + nRes = s1.compareTo(s2); + if (nRes != 0) { + return nRes; + } + throw new UnsupportedOperationException("Ran out of options to compare instance of " + s1 + " vs. " + s2); + }; + + private EventListenerUtils() { + throw new UnsupportedOperationException("No instance"); + } + + /** + * @param <L> Type of {@link SshdEventListener} contained in the set + * @param listeners The listeners to pre-add to the create set - ignored + * if (@code null}/empty + * @return A (synchronized) {@link Set} for containing the listeners ensuring + * that if same listener instance is added repeatedly only <U>one</U> + * instance is actually contained + */ + public static <L extends SshdEventListener> Set<L> synchronizedListenersSet(Collection<? extends L> listeners) { + Set<L> s = EventListenerUtils.synchronizedListenersSet(); + if (GenericUtils.size(listeners) > 0) { + s.addAll(listeners); + } + + return s; + } + + /** + * @param <L> Type of {@link SshdEventListener} contained in the set + * @return A (synchronized) {@link Set} for containing the listeners ensuring + * that if same listener instance is added repeatedly only <U>one</U> + * instance is actually contained + * @see #LISTENER_INSTANCE_COMPARATOR + */ + public static <L extends SshdEventListener> Set<L> synchronizedListenersSet() { + return Collections.synchronizedSet(new TreeSet<L>(LISTENER_INSTANCE_COMPARATOR)); + } + + /** + * Provides proxy wrapper around an {@link Iterable} container of listener + * interface implementation. <b>Note:</b> a listener interface is one whose + * invoked methods return <u>only</u> {@code void}. + * + * @param <T> Generic listener type + * @param listenerType The expected listener <u>interface</u> + * @param listeners An {@link Iterable} container of listeners to be invoked. + * <p> + * <b>Note(s):</b> + * </p> + * <ul> + * <li><p> + * The invocation order is same as the {@link Iterable} container + * </p></li> + * + * <li><p> + * If any of the invoked listener methods throws an exception, the + * rest of the listener are <u>not</u> invoked and the exception is + * propagated to the caller + * </p></li> + * + * <li><p> + * It is up to the <u>caller</u> to ensure that the container does + * not change while the proxy is invoked + * </p></li> + * </ul> + * @return A proxy wrapper implementing the same interface, but delegating + * the calls to the container + * @see #proxyWrapper(Class, ClassLoader, Iterable) + */ + public static <T extends SshdEventListener> T proxyWrapper(Class<T> listenerType, Iterable<? extends T> listeners) { + return proxyWrapper(listenerType, listenerType.getClassLoader(), listeners); + } + + /** + * Provides proxy wrapper around an {@link Iterable} container of listener + * interface implementation. <b>Note:</b> a listener interface is one whose + * invoked methods return <u>only</u> {@code void}. + * + * @param <T> Generic {@link SshdEventListener} type + * @param listenerType The expected listener <u>interface</u> + * @param loader The {@link ClassLoader} to use for the proxy + * @param listeners An {@link Iterable} container of listeners to be invoked. + * <p> + * <b>Note(s):</b> + * </p> + * <ul> + * <li><p> + * The invocation order is same as the {@link Iterable} container + * </p></li> + * + * <li><p> + * If any of the invoked listener methods throws an exception, the + * rest of the listener are <u>not</u> invoked and the exception is + * propagated to the caller + * </p></li> + * + * <li><p> + * It is up to the <u>caller</u> to ensure that the container does + * not change while the proxy is invoked + * </p></li> + * </ul> + * @return A proxy wrapper implementing the same interface, but delegating + * the calls to the container + * @throws IllegalArgumentException if <tt>listenerType</tt> is not an interface + * or a {@code null} container has been provided + * @see #proxyWrapper(Class, ClassLoader, Iterable) + */ + public static <T extends SshdEventListener> T proxyWrapper(Class<T> listenerType, ClassLoader loader, final Iterable<? extends T> listeners) { + Objects.requireNonNull(listenerType, "No listener type specified"); + ValidateUtils.checkTrue(listenerType.isInterface(), "Target proxy is not an interface: %s", listenerType.getSimpleName()); + Objects.requireNonNull(listeners, "No listeners container provided"); + + Object wrapper = Proxy.newProxyInstance(loader, new Class<?>[]{listenerType}, (proxy, method, args) -> { + Throwable err = null; + for (T l : listeners) { + try { + method.invoke(l, args); + } catch (Throwable t) { + Throwable e = GenericUtils.peelException(t); + err = GenericUtils.accumulateException(err, e); + } + } + + if (err != null) { + throw err; + } + + return null; // we assume always void return value... + }); + return listenerType.cast(wrapper); + } +}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/EventNotifier.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/EventNotifier.java b/sshd-common/src/main/java/org/apache/sshd/common/util/EventNotifier.java new file mode 100644 index 0000000..c041f1b --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/EventNotifier.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Notify about the occurrence of an event + * + * @param <E> type of event being notified + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface EventNotifier<E> { + /** + * @param event The event + * @throws Exception If failed to process the event notification + */ + void notifyEvent(E event) throws Exception; +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java new file mode 100644 index 0000000..c924d1e --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java @@ -0,0 +1,915 @@ +/* + * 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.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ExecutionException; +import java.util.function.BinaryOperator; +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 javax.management.MBeanException; +import javax.management.ReflectionException; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +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 = {}; + + /** + * 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<String> CASE_SENSITIVE_ORDER = (s1, s2) -> { + if (s1 == s2) { + return 0; + } else { + return s1.compareTo(s2); + } + }; + + public static final String QUOTES = "\"'"; + + @SuppressWarnings("rawtypes") + private static final Supplier CASE_INSENSITIVE_MAP_FACTORY = () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + 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); + } + + /** + * @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: + * <UL> + * <LI>{@code null} - no conversion</LI> + * <LI>{@link Boolean#TRUE} - get hash code of uppercase</LI> + * <LI>{@link Boolean#FALSE} - get hash code of lowercase</LI> + * </UL> + * @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 (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 <T> boolean isSameReference(T o1, T o2) { + return o1 == o2; + } + + 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); + } + + 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<String> 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 <T> String join(T[] values, char ch) { + return join(isEmpty(values) ? Collections.<T>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 <T> String join(T[] values, CharSequence sep) { + return join(isEmpty(values) ? Collections.<T>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 (c == null) || c.isEmpty(); + } + + public static boolean isNotEmpty(Collection<?> c) { + return !isEmpty(c); + } + + public static int size(Map<?, ?> m) { + return m == null ? 0 : m.size(); + } + + public static boolean isEmpty(Map<?, ?> m) { + return (m == null) || m.isEmpty(); + } + + public static boolean isNotEmpty(Map<?, ?> m) { + return !isEmpty(m); + } + + @SafeVarargs + public static <T> int length(T... a) { + return a == null ? 0 : a.length; + } + + public static <T> 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 <T> boolean isNotEmpty(Iterable<? extends T> iter) { + return !isEmpty(iter); + } + + public static <T> boolean isEmpty(Iterator<? extends T> iter) { + return iter == null || !iter.hasNext(); + } + + public static <T> boolean isNotEmpty(Iterator<? extends T> iter) { + return !isEmpty(iter); + } + + @SafeVarargs + public static <T> 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 - <B>Note:</B> {@code null} and empty + * are considered <U>equal</U> + * + * @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 <E extends Enum<E>> Set<E> of(E... values) { + return of(isEmpty(values) ? Collections.emptySet() : Arrays.asList(values)); + } + + public static <E extends Enum<E>> Set<E> of(Collection<? extends E> values) { + if (isEmpty(values)) { + return Collections.emptySet(); + } + + Set<E> 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 <T> 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 <T> void forEach(Iterable<T> values, Consumer<T> consumer) { + if (isNotEmpty(values)) { + values.forEach(consumer); + } + } + + public static <T, U> List<U> map(Collection<T> values, Function<? super T, ? extends U> mapper) { + return stream(values).map(mapper).collect(Collectors.toList()); + } + + public static <T, U> NavigableSet<U> mapSort( + Collection<T> values, Function<? super T, ? extends U> mapper, Comparator<U> comparator) { + return stream(values).map(mapper).collect(toSortedSet(comparator)); + } + + public static <T, K, U> NavigableMap<K, U> toSortedMap( + Iterable<T> values, Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, Comparator<K> comparator) { + return stream(values).collect(toSortedMap(keyMapper, valueMapper, comparator)); + } + + public static <T, K, U> Collector<T, ?, NavigableMap<K, U>> toSortedMap( + Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, Comparator<K> comparator) { + return Collectors.toMap(keyMapper, valueMapper, throwingMerger(), () -> new TreeMap<>(comparator)); + } + + private static <T> BinaryOperator<T> throwingMerger() { + return (u, v) -> { + throw new IllegalStateException(String.format("Duplicate key %s", u)); + }; + } + + public static <T> Collector<T, ?, NavigableSet<T>> toSortedSet(Comparator<T> comparator) { + return Collectors.toCollection(() -> new TreeSet<>(comparator)); + } + + public static <T> Stream<T> stream(Iterable<T> values) { + if (isEmpty(values)) { + return Stream.empty(); + } else if (values instanceof Collection<?>) { + return ((Collection<T>) values).stream(); + } else { + return StreamSupport.stream(values.spliterator(), false); + } + } + + @SafeVarargs + public static <T> List<T> unmodifiableList(T... values) { + return unmodifiableList(asList(values)); + } + + public static <T> List<T> unmodifiableList(Collection<? extends T> values) { + if (isEmpty(values)) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableList(new ArrayList<>(values)); + } + } + + public static <T> List<T> unmodifiableList(Stream<T> values) { + return unmodifiableList(values.collect(Collectors.toList())); + } + + @SafeVarargs + public static <T> List<T> asList(T... values) { + return isEmpty(values) ? Collections.emptyList() : Arrays.asList(values); + } + + @SafeVarargs + public static <T> Set<T> asSet(T... values) { + return new HashSet<>(asList(values)); + } + + @SafeVarargs + public static <V extends Comparable<V>> NavigableSet<V> asSortedSet(V... values) { + return asSortedSet(Comparator.naturalOrder(), values); + } + + public static <V extends Comparable<V>> NavigableSet<V> asSortedSet(Collection<? extends V> values) { + return asSortedSet(Comparator.naturalOrder(), values); + } + + /** + * @param <V> 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 <V> NavigableSet<V> asSortedSet(Comparator<? super V> comp, V... values) { + return asSortedSet(comp, isEmpty(values) ? Collections.emptyList() : Arrays.asList(values)); + } + + /** + * @param <V> 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 <V> NavigableSet<V> asSortedSet(Comparator<? super V> comp, Collection<? extends V> values) { + NavigableSet<V> set = new TreeSet<>(Objects.requireNonNull(comp, "No comparator")); + if (size(values) > 0) { + set.addAll(values); + } + return set; + } + + /** + * @param <V> Type of mapped value + * @return A {@link Supplier} that returns a <U>new</U> {@link NavigableMap} + * whenever its {@code get()} method is invoked + */ + @SuppressWarnings("unchecked") + public static <V> Supplier<NavigableMap<String, V>> caseInsensitiveMap() { + return CASE_INSENSITIVE_MAP_FACTORY; + } + + /** + * Flips between keys and values of an input map + * + * @param <K> Original map key type + * @param <V> Original map value type + * @param <M> Flipped map type + * @param map The original map to flip + * @param mapCreator The creator of the target map + * @param allowDuplicates Whether to ignore duplicates on flip + * @return The flipped map result + * @throws IllegalArgumentException if <tt>allowDuplicates</tt> is {@code false} + * and a duplicate value found in the original map. + */ + public static <K, V, M extends Map<V, K>> M flipMap( + Map<? extends K, ? extends V> map, Supplier<? extends M> mapCreator, boolean allowDuplicates) { + M result = Objects.requireNonNull(mapCreator.get(), "No map created"); + map.forEach((key, value) -> { + K prev = result.put(value, key); + if ((prev != null) && (!allowDuplicates)) { + ValidateUtils.throwIllegalArgumentException("Multiple values for key=%s: current=%s, previous=%s", value, key, prev); + } + }); + + return result; + } + + @SafeVarargs + public static <K, V, M extends Map<K, V>> M mapValues( + Function<? super V, ? extends K> keyMapper, Supplier<? extends M> mapCreator, V... values) { + return mapValues(keyMapper, mapCreator, isEmpty(values) ? Collections.emptyList() : Arrays.asList(values)); + } + + /** + * Creates a map out of a group of values + * + * @param <K> The key type + * @param <V> The value type + * @param <M> The result {@link Map} type + * @param keyMapper The {@link Function} that generates a key for a given value. + * If the returned key is {@code null} then the value is not mapped + * @param mapCreator The {@link Supplier} used to create/retrieve the result map - provided + * non-empty group of values + * @param values The values to be mapped + * @return The resulting {@link Map} - <B>Note:</B> no validation is made to ensure + * that 2 (or more) values are not mapped to the same key + */ + public static <K, V, M extends Map<K, V>> M mapValues( + Function<? super V, ? extends K> keyMapper, Supplier<? extends M> mapCreator, Collection<? extends V> values) { + M map = mapCreator.get(); + for (V v : values) { + K k = keyMapper.apply(v); + if (k == null) { + continue; // debug breakpoint + } + map.put(k, v); + } + + return map; + } + + @SafeVarargs + public static <T> T findFirstMatchingMember(Predicate<? super T> acceptor, T... values) { + return findFirstMatchingMember(acceptor, isEmpty(values) ? Collections.emptyList() : Arrays.asList(values)); + } + + public static <T> T findFirstMatchingMember(Predicate<? super T> acceptor, Collection<? extends T> values) { + List<T> 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 <T> 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 <T> List<T> 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 <T> 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 <T> List<T> 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 <U>both</U> 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 <U>both</U> 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); + } + } + + public static RuntimeException toRuntimeException(Throwable t) { + return toRuntimeException(t, true); + } + + /** + * Converts a thrown generic exception to a {@link RuntimeException} + * + * @param t The original thrown exception + * @param peelThrowable Whether to determine the root cause by "peeling" + * any enclosing exceptions + * @return The thrown cause if already a runtime exception, otherwise a + * runtime exception of the resolved exception as its cause + * @see #peelException(Throwable) + */ + public static RuntimeException toRuntimeException(Throwable t, boolean peelThrowable) { + Throwable e = peelThrowable ? peelException(t) : t; + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + + return new RuntimeException(e); + } + + /** + * Attempts to get to the "effective" exception being thrown, + * by taking care of some known exceptions that wrap the original thrown + * one. + * + * @param t The original {@link Throwable} - ignored if {@code null} + * @return The effective exception - same as input if not a wrapper + */ + public static Throwable peelException(Throwable t) { + // NOTE: check order is important - e.g., InvocationTargetException extends ReflectiveOperationException + if (t == null) { + return t; + } else if (t instanceof UndeclaredThrowableException) { + Throwable wrapped = ((UndeclaredThrowableException) t).getUndeclaredThrowable(); + // according to the Javadoc it may be null, in which case 'getCause' + // might contain the information we need + if (wrapped != null) { + return peelException(wrapped); + } + + wrapped = t.getCause(); + if (wrapped != t) { // make sure it is a real cause + return peelException(wrapped); + } + } else if (t instanceof InvocationTargetException) { + Throwable target = ((InvocationTargetException) t).getTargetException(); + if (target != null) { + return peelException(target); + } + } else if (t instanceof ReflectionException) { + Throwable target = ((ReflectionException) t).getTargetException(); + if (target != null) { + return peelException(target); + } + } else if (t instanceof ExecutionException) { + Throwable wrapped = resolveExceptionCause(t); + if (wrapped != null) { + return peelException(wrapped); + } + } else if (t instanceof MBeanException) { + Throwable target = ((MBeanException) t).getTargetException(); + if (target != null) { + return peelException(target); + } + } + + return t; // no special handling required or available + } + + /** + * @param t The original {@link Throwable} - ignored if {@code null} + * @return If {@link Throwable#getCause()} is non-{@code null} then + * the cause, otherwise the original exception - {@code null} if + * the original exception was {@code null} + */ + public static Throwable resolveExceptionCause(Throwable t) { + if (t == null) { + return t; + } + + Throwable c = t.getCause(); + if (c == null) { + return t; + } else { + return c; + } + } + + /** + * Used to "accumulate" exceptions of the <U>same type</U>. If the + * current exception is {@code null} then the new one becomes the current, + * otherwise the new one is added as a <U>suppressed</U> exception to the + * current one + * + * @param <T> The exception type + * @param current The current exception + * @param extra The extra/new exception + * @return The resolved exception + * @see Throwable#addSuppressed(Throwable) + */ + public static <T extends Throwable> T accumulateException(T current, T extra) { + if (current == null) { + return extra; + } + + if ((extra == null) || (extra == current)) { + return current; + } + + current.addSuppressed(extra); + return current; + } + + public static IOException toIOException(Throwable e) { + if (e instanceof IOException) { + return (IOException) e; + } else { + return new IOException(e); + } + } + + /** + * Wraps a value into a {@link Supplier} + * @param <T> Type of value being supplied + * @param value The value to be supplied + * @return The supplier wrapper + */ + public static <T> Supplier<T> supplierOf(T value) { + return () -> value; + } + + /** + * Resolves to an always non-{@code null} iterator + * + * @param <T> 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 <T> Iterator<T> iteratorOf(Iterable<T> iterable) { + return iteratorOf((iterable == null) ? null : iterable.iterator()); + } + + /** + * @param <B> Generic base class + * @param <D> Generic child class + * @return An identity {@link Function} that returns its input child class as a base class + */ + public static <B, D extends B> Function<D, B> downcast() { + return t -> t; + } + + /** + * Resolves to an always non-{@code null} iterator + * + * @param <T> 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 <T> Iterator<T> iteratorOf(Iterator<T> iter) { + return (iter == null) ? Collections.emptyIterator() : iter; + } + + public static <U, V> Iterable<V> wrapIterable(Iterable<? extends U> iter, Function<? super U, ? extends V> mapper) { + return () -> wrapIterator(iter, mapper); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static <U, V> Iterator<V> wrapIterator(Iterable<? extends U> iter, Function<? super U, ? extends V> mapper) { + return (Iterator) stream(iter).map(mapper).iterator(); + } + + public static <U, V> Iterator<V> wrapIterator(Iterator<? extends U> iter, Function<? super U, ? extends V> mapper) { + Iterator<? extends U> iterator = iteratorOf(iter); + return new Iterator<V>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public V next() { + U value = iterator.next(); + return mapper.apply(value); + } + }; + } + + /** + * 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 <T> 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 <T> Iterable<T> multiIterableSuppliers(Iterable<? extends Supplier<? extends Iterable<? extends T>>> providers) { + return () -> stream(providers).<T>flatMap(s -> stream(s.get())).map(Function.identity()).iterator(); + } + + public static <K, V> MapBuilder<K, V> mapBuilder() { + return new MapBuilder<>(); + } + + public static <K, V> MapBuilder<K, V> mapBuilder(Comparator<K> comparator) { + return new MapBuilder<>(comparator); + } + + public static class MapBuilder<K, V> { + private Map<K, V> map; + + public MapBuilder() { + this.map = new LinkedHashMap<>(); + } + + public MapBuilder(Comparator<? super K> comparator) { + this.map = new TreeMap<>(comparator); + } + + public MapBuilder<K, V> put(K k, V v) { + map.put(k, v); + return this; + } + + public Map<K, V> build() { + return map; + } + + public Map<K, V> immutable() { + return Collections.unmodifiableMap(map); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/IgnoringEmptyMap.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/IgnoringEmptyMap.java b/sshd-common/src/main/java/org/apache/sshd/common/util/IgnoringEmptyMap.java new file mode 100644 index 0000000..8f18bfe --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/IgnoringEmptyMap.java @@ -0,0 +1,128 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * A dummy map that ignores all {@code put/remove} calls + * + * @param <K> Key type + * @param <V> Value type + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class IgnoringEmptyMap<K, V> implements Map<K, V> { + @SuppressWarnings("rawtypes") + private static final IgnoringEmptyMap INSTANCE = new IgnoringEmptyMap(); + + public IgnoringEmptyMap() { + super(); + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public boolean containsValue(Object value) { + Objects.requireNonNull(value, "No value provided"); + return false; + } + + @Override + public boolean containsKey(Object key) { + Objects.requireNonNull(key, "No key provided"); + return false; + } + + @Override + public V get(Object key) { + Objects.requireNonNull(key, "No key provided"); + return null; + } + + @Override + public V put(K key, V value) { + Objects.requireNonNull(key, "No key provided"); + Objects.requireNonNull(value, "No value provided"); + return null; + } + + @Override + public V remove(Object key) { + Objects.requireNonNull(key, "No key provided"); + return null; + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + // ignored + } + + @Override + public void clear() { + // ignored + } + + @Override + public Set<K> keySet() { + return Collections.emptySet(); + } + + @Override + public Collection<V> values() { + return Collections.emptyList(); + } + + @Override + public boolean equals(Object o) { + return o instanceof IgnoringEmptyMap<?, ?>; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "{}"; + } + + @Override + public Set<Entry<K, V>> entrySet() { + return Collections.emptySet(); + } + + @SuppressWarnings("unchecked") + public static <K, V> IgnoringEmptyMap<K, V> getInstance() { + return INSTANCE; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/Int2IntFunction.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/Int2IntFunction.java b/sshd-common/src/main/java/org/apache/sshd/common/util/Int2IntFunction.java new file mode 100644 index 0000000..490abb0 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/Int2IntFunction.java @@ -0,0 +1,66 @@ +/* + * 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.util.function.IntUnaryOperator; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class Int2IntFunction { + private Int2IntFunction() { + throw new UnsupportedOperationException("No instance"); + } + + public static IntUnaryOperator sub(int delta) { + return add(0 - delta); + } + + public static IntUnaryOperator add(int delta) { + if (delta == 0) { + return IntUnaryOperator.identity(); + } else { + return value -> value + delta; + } + } + + public static IntUnaryOperator mul(int factor) { + if (factor == 0) { + return constant(0); + } else if (factor == 1) { + return IntUnaryOperator.identity(); + } else { + return value -> value * factor; + } + } + + public static IntUnaryOperator constant(int v) { + return value -> v; + } + + public static IntUnaryOperator div(int factor) { + if (factor == 1) { + return IntUnaryOperator.identity(); + } else { + ValidateUtils.checkTrue(factor != 0, "Zero division factor"); + return value -> value / factor; + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java b/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java new file mode 100644 index 0000000..71cbebd --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java @@ -0,0 +1,116 @@ +/* + * 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.util.AbstractMap.SimpleImmutableEntry; +import java.util.Collection; +import java.util.Map; + +/** + * The complement to the {@code Callable} interface - accepts one argument + * and possibly throws somethind + * + * @param <ARG> Argument type + * @param <RET> Return type + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface Invoker<ARG, RET> { + RET invoke(ARG arg) throws Throwable; + + static <ARG> Invoker<ARG, Void> wrapAll(Collection<? extends Invoker<? super ARG, ?>> invokers) { + return arg -> { + invokeAll(arg, invokers); + return null; + }; + } + + /** + * Invokes <U>all</U> the instances ignoring the return value. Any + * intermediate exceptions are accumulated and thrown at the end. + * + * @param <ARG> Argument type + * @param arg The argument to pass to the {@link #invoke(Object)} method + * @param invokers The invokers to scan - ignored if {@code null}/empty + * (also ignores {@code null} members) + * @throws Throwable If invocation failed + */ + static <ARG> void invokeAll(ARG arg, Collection<? extends Invoker<? super ARG, ?>> invokers) throws Throwable { + if (GenericUtils.isEmpty(invokers)) { + return; + } + + Throwable err = null; + for (Invoker<? super ARG, ?> i : invokers) { + if (i == null) { + continue; + } + + try { + i.invoke(arg); + } catch (Throwable t) { + err = GenericUtils.accumulateException(err, t); + } + } + + if (err != null) { + throw err; + } + } + + static <ARG> Invoker<ARG, Void> wrapFirst(Collection<? extends Invoker<? super ARG, ?>> invokers) { + return arg -> { + Map.Entry<Invoker<? super ARG, ?>, Throwable> result = invokeTillFirstFailure(arg, invokers); + if (result != null) { + throw result.getValue(); + } + return null; + }; + } + + /** + * Invokes all instances until 1st failure (if any) + * + * @param <ARG> Argument type + * @param arg The argument to pass to the {@link #invoke(Object)} method + * @param invokers The invokers to scan - ignored if {@code null}/empty + * (also ignores {@code null} members) + * @return A {@link SimpleImmutableEntry} representing the <U>first</U> failed + * invocation - {@code null} if all were successful (or none invoked). + */ + static <ARG> SimpleImmutableEntry<Invoker<? super ARG, ?>, Throwable> invokeTillFirstFailure(ARG arg, Collection<? extends Invoker<? super ARG, ?>> invokers) { + if (GenericUtils.isEmpty(invokers)) { + return null; + } + + for (Invoker<? super ARG, ?> i : invokers) { + if (i == null) { + continue; + } + + try { + i.invoke(arg); + } catch (Throwable t) { + return new SimpleImmutableEntry<>(i, t); + } + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java new file mode 100644 index 0000000..6bcc93a --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java @@ -0,0 +1,51 @@ +/* + * 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.util.Comparator; +import java.util.Map; + +/** + * Represents an un-modifiable pair of values + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class MapEntryUtils { + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final Comparator<Map.Entry<Comparable, ?>> BY_KEY_COMPARATOR = (o1, o2) -> { + Comparable k1 = o1.getKey(); + Comparable k2 = o2.getKey(); + return k1.compareTo(k2); + }; + + private MapEntryUtils() { + throw new UnsupportedOperationException("No instance"); + } + + /** + * @param <K> The {@link Comparable} key type + * @param <V> The associated entry value + * @return A {@link Comparator} for {@link java.util.Map.Entry}-ies that + * compares the key values + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static <K extends Comparable<K>, V> Comparator<Map.Entry<K, V>> byKeyEntryComparator() { + return (Comparator) BY_KEY_COMPARATOR; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java new file mode 100644 index 0000000..c8e1817 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java @@ -0,0 +1,310 @@ +/* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class NumberUtils { + /** + * A {@link List} of all the {@link Class} types used to represent the + * primitive numerical values + */ + public static final List<Class<?>> NUMERIC_PRIMITIVE_CLASSES = + GenericUtils.unmodifiableList( + Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE); + + /** + * A {@link List} containing all the pure powers of 2 for a {@code long} + * value. The value at index <I>n</I> is 2 to the power of <I>n</I> + */ + public static final List<Long> POWERS_OF_TWO = + GenericUtils.unmodifiableList(IntStream.range(0, 64).mapToObj(i -> 1L << i)); + + private NumberUtils() { + throw new UnsupportedOperationException("No instance"); + } + + public static boolean isPowerOf2(long value) { + for (Long l : POWERS_OF_TWO) { + if (value == l) { + return true; + } + } + + return false; + } + + public static long getNextPowerOf2(long value) { + long j = 1L; + while (j < value) { + j <<= 1; + } + return j; + } + + public static int getNextPowerOf2(int value) { + int j = 1; + while (j < value) { + j <<= 1; + } + return j; + } + + public static int hashCode(long... values) { + return Arrays.hashCode(values); + } + + public static int hashCode(int... values) { + return Arrays.hashCode(values); + } + + public static int hashCode(byte... values) { + return Arrays.hashCode(values); + } + + public static int hashCode(byte[] a, int offset, int len) { + if (len == 0) { + return 0; + } + + int result = 1; + for (int pos = offset, count = 0; count < len; pos++, count++) { + byte element = a[pos]; + result = 31 * result + element; + } + + return result; + } + + public static int diffOffset(byte[] a1, int startPos1, byte[] a2, int startPos2, int len) { + for (int pos1 = startPos1, pos2 = startPos2, count = 0; count < len; pos1++, pos2++, count++) { + byte v1 = a1[pos1]; + byte v2 = a2[pos2]; + if (v1 != v2) { + return count; + } + } + + return -1; + } + + /** + * @param clazz The {@link Class} to examine - ignored if {@code null} + * @return If the class is a {@link Number} or one of the primitive numerical types + * @see #NUMERIC_PRIMITIVE_CLASSES + */ + public static boolean isNumericClass(Class<?> clazz) { + if (clazz == null) { + return false; + } + + // turns out that the primitive types are not assignable to Number + if (Number.class.isAssignableFrom(clazz)) { + return true; + } + + return NUMERIC_PRIMITIVE_CLASSES.indexOf(clazz) >= 0; + } + + /** + * Converts a {@link Number} into an {@link Integer} if not already such + * + * @param n The {@link Number} - ignored if {@code null} + * @return The equivalent {@link Integer} value + */ + public static Integer toInteger(Number n) { + if (n == null) { + return null; + } else if (n instanceof Integer) { + return (Integer) n; + } else { + return n.intValue(); + } + } + + public static String join(CharSequence separator, long... values) { + if (NumberUtils.isEmpty(values)) { + return ""; + } + + StringBuilder sb = new StringBuilder(values.length * Byte.SIZE); + for (long v : values) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(v); + } + + return sb.toString(); + } + + public static String join(char separator, long... values) { + if (NumberUtils.isEmpty(values)) { + return ""; + } + + StringBuilder sb = new StringBuilder(values.length * Byte.SIZE); + for (long v : values) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(v); + } + + return sb.toString(); + } + + public static String join(CharSequence separator, boolean unsigned, byte... values) { + if (NumberUtils.isEmpty(values)) { + return ""; + } + + StringBuilder sb = new StringBuilder(values.length * Byte.SIZE); + for (byte v : values) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(unsigned ? (v & 0xFF) : v); + } + + return sb.toString(); + } + + public static String join(char separator, boolean unsigned, byte... values) { + if (NumberUtils.isEmpty(values)) { + return ""; + } + + StringBuilder sb = new StringBuilder(values.length * Byte.SIZE); + for (byte v : values) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(unsigned ? (v & 0xFF) : v); + } + + return sb.toString(); + } + + public static String join(CharSequence separator, int... values) { + if (NumberUtils.isEmpty(values)) { + return ""; + } + + StringBuilder sb = new StringBuilder(values.length * Byte.SIZE); + for (int v : values) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(v); + } + + return sb.toString(); + } + + public static String join(char separator, int... values) { + if (NumberUtils.isEmpty(values)) { + return ""; + } + + StringBuilder sb = new StringBuilder(values.length * Byte.SIZE); + for (int v : values) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(v); + } + + return sb.toString(); + } + + public static boolean isEmpty(byte[] a) { + return NumberUtils.length(a) <= 0; + } + + public static boolean isEmpty(int[] a) { + return NumberUtils.length(a) <= 0; + } + + public static boolean isEmpty(long[] a) { + return NumberUtils.length(a) <= 0; + } + + public static int length(byte... a) { + return a == null ? 0 : a.length; + } + + public static int length(int... a) { + return a == null ? 0 : a.length; + } + + public static int length(long... a) { + return a == null ? 0 : a.length; + } + + public static List<Integer> asList(int... values) { + int len = length(values); + if (len <= 0) { + return Collections.emptyList(); + } + + List<Integer> l = new ArrayList<>(len); + for (int v : values) { + l.add(v); + } + + return l; + } + + /** + * Checks if optional sign and all others are '0'-'9' + * @param cs The {@link CharSequence} to check + * @return {@code true} if valid integer number + */ + public static boolean isIntegerNumber(CharSequence cs) { + if (GenericUtils.isEmpty(cs)) { + return false; + } + + for (int index = 0; index < cs.length(); index++) { + char c = cs.charAt(0); + if ((c >= '0') && (c <= '9')) { + continue; + } + + if ((c == '+') || (c == '-')) { + if (index == 0) { + continue; + } + } + + return false; + } + + return true; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/ObjectBuilder.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/ObjectBuilder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/ObjectBuilder.java new file mode 100644 index 0000000..23884a2 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/ObjectBuilder.java @@ -0,0 +1,38 @@ +/* + * 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.util.function.Supplier; + +/** + * A generic builder interface + * + * @param <T> Type of object being built + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface ObjectBuilder<T> extends Supplier<T> { + @Override + default T get() { + return build(); + } + + T build(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java new file mode 100644 index 0000000..f0b2c7e --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java @@ -0,0 +1,257 @@ +/* + * 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.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Operating system dependent utility methods. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class OsUtils { + + /** + * Property that can be used to override the reported value from {@link #getCurrentUser()}. + * If not set then "user.name" system property is used + */ + public static final String CURRENT_USER_OVERRIDE_PROP = "org.apache.sshd.currentUser"; + + /** + * Property that can be used to override the reported value from {@link #getJavaVersion()}. + * If not set then "java.version" system property is used + */ + public static final String JAVA_VERSION_OVERRIDE_PROP = "org.apache.sshd.javaVersion"; + + /** + * Property that can be used to override the reported value from {@link #isWin32()}. + * If not set then "os.name" system property is used + */ + public static final String OS_TYPE_OVERRIDE_PROP = "org.apache.sshd.osType"; + + public static final String WINDOWS_SHELL_COMMAND_NAME = "cmd.exe"; + public static final String LINUX_SHELL_COMMAND_NAME = "/bin/sh"; + + public static final String ROOT_USER = "root"; + + public static final List<String> LINUX_COMMAND = + Collections.unmodifiableList(Arrays.asList(LINUX_SHELL_COMMAND_NAME, "-i", "-l")); + public static final List<String> WINDOWS_COMMAND = + Collections.unmodifiableList(Collections.singletonList(WINDOWS_SHELL_COMMAND_NAME)); + + private static final AtomicReference<String> CURRENT_USER_HOLDER = new AtomicReference<>(null); + private static final AtomicReference<VersionInfo> JAVA_VERSION_HOLDER = new AtomicReference<>(null); + private static final AtomicReference<Boolean> OS_TYPE_HOLDER = new AtomicReference<>(null); + + private OsUtils() { + throw new UnsupportedOperationException("No instance allowed"); + } + + /** + * @return true if the host is a UNIX system (and not Windows). + */ + public static boolean isUNIX() { + return !isWin32(); + } + + /** + * @return true if the host is Windows (and not UNIX). + * @see #OS_TYPE_OVERRIDE_PROP + * @see #setWin32(Boolean) + */ + public static boolean isWin32() { + Boolean typeValue; + synchronized (OS_TYPE_HOLDER) { + typeValue = OS_TYPE_HOLDER.get(); + if (typeValue != null) { // is it the 1st time + return typeValue; + } + + String value = System.getProperty(OS_TYPE_OVERRIDE_PROP, System.getProperty("os.name")); + typeValue = GenericUtils.trimToEmpty(value).toLowerCase().contains("windows"); + OS_TYPE_HOLDER.set(typeValue); + } + + return typeValue; + } + + /** + * Can be used to enforce Win32 or Linux report from {@link #isWin32()} or {@link #isUNIX()} + * @param win32 The value to set - if {@code null} then O/S type is auto-detected + * @see #isWin32() + */ + public static void setWin32(Boolean win32) { + synchronized (OS_TYPE_HOLDER) { + OS_TYPE_HOLDER.set(win32); + } + } + + public static List<String> resolveDefaultInteractiveCommand() { + return resolveInteractiveCommand(isWin32()); + } + + public static List<String> resolveInteractiveCommand(boolean isWin32) { + if (isWin32) { + return WINDOWS_COMMAND; + } else { + return LINUX_COMMAND; + } + } + + /** + * Get current user name + * + * @return Current user + * @see #CURRENT_USER_OVERRIDE_PROP + */ + public static String getCurrentUser() { + String username = null; + synchronized (CURRENT_USER_HOLDER) { + username = CURRENT_USER_HOLDER.get(); + if (username != null) { // have we already resolved it ? + return username; + } + + username = getCanonicalUser(System.getProperty(CURRENT_USER_OVERRIDE_PROP, System.getProperty("user.name"))); + ValidateUtils.checkNotNullAndNotEmpty(username, "No username available"); + CURRENT_USER_HOLDER.set(username); + } + + return username; + } + + /** + * Remove {@code Windows} domain and/or group prefix as well as "(User);" suffix + * + * @param user The original username - ignored if {@code null}/empty + * @return The canonical user - unchanged if {@code Unix} O/S + */ + public static String getCanonicalUser(String user) { + if (GenericUtils.isEmpty(user)) { + return user; + } + + // Windows owner sometime has the domain and/or group prepended to it + if (isWin32()) { + int pos = user.lastIndexOf('\\'); + if (pos > 0) { + user = user.substring(pos + 1); + } + + pos = user.indexOf(' '); + if (pos > 0) { + user = user.substring(0, pos).trim(); + } + } + + return user; + } + + /** + * Attempts to resolve canonical group name for {@code Windows} + * + * @param group The original group name - used if not {@code null}/empty + * @param user The owner name - sometimes it contains a group name + * @return The canonical group name + */ + public static String resolveCanonicalGroup(String group, String user) { + if (isUNIX()) { + return group; + } + + // we reach this code only for Windows + if (GenericUtils.isEmpty(group)) { + int pos = GenericUtils.isEmpty(user) ? -1 : user.lastIndexOf('\\'); + return (pos > 0) ? user.substring(0, pos) : group; + } + + int pos = group.indexOf(' '); + return (pos < 0) ? group : group.substring(0, pos).trim(); + } + + /** + * Can be used to programmatically set the username reported by {@link #getCurrentUser()} + * @param username The username to set - if {@code null} then {@link #CURRENT_USER_OVERRIDE_PROP} + * will be consulted + */ + public static void setCurrentUser(String username) { + synchronized (CURRENT_USER_HOLDER) { + CURRENT_USER_HOLDER.set(username); + } + } + + /** + * Resolves the reported Java version by consulting {@link #JAVA_VERSION_OVERRIDE_PROP}. + * If not set, then "java.version" property is used + * @return The resolved {@link VersionInfo} - never {@code null} + * @see #setJavaVersion(VersionInfo) + */ + public static VersionInfo getJavaVersion() { + VersionInfo version; + synchronized (JAVA_VERSION_HOLDER) { + version = JAVA_VERSION_HOLDER.get(); + if (version != null) { // first time ? + return version; + } + + String value = System.getProperty(JAVA_VERSION_OVERRIDE_PROP, System.getProperty("java.version")); + // e.g.: 1.7.5_30 + value = ValidateUtils.checkNotNullAndNotEmpty(value, "No configured Java version value").replace('_', '.'); + // clean up any non-digits - in case something like 1.6.8_25-b323 + for (int index = 0; index < value.length(); index++) { + char ch = value.charAt(index); + if ((ch == '.') || ((ch >= '0') && (ch <= '9'))) { + continue; + } + + value = value.substring(0, index); + break; + } + + version = ValidateUtils.checkNotNull(VersionInfo.parse(value), "No version parsed for %s", value); + JAVA_VERSION_HOLDER.set(version); + } + + return version; + } + + /** + * Set programmatically the reported Java version + * @param version The version - if {@code null} then it will be automatically resolved + */ + public static void setJavaVersion(VersionInfo version) { + synchronized (JAVA_VERSION_HOLDER) { + JAVA_VERSION_HOLDER.set(version); + } + } + + /** + * @param path The original path + * @return A path that can be compared with another one where case + * sensitivity of the underlying O/S has been taken into account - + * never {@code null} + */ + public static String getComparablePath(String path) { + String p = (path == null) ? "" : path; + return isWin32() ? p.toLowerCase() : p; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/Readable.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/Readable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/Readable.java new file mode 100644 index 0000000..3075eb3 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/Readable.java @@ -0,0 +1,53 @@ +/* + * 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.nio.ByteBuffer; +import java.util.Objects; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface Readable { + + int available(); + + void getRawBytes(byte[] data, int offset, int len); + + /** + * Wrap a {@link ByteBuffer} as a {@link Readable} instance + * + * @param buffer The {@link ByteBuffer} to wrap - never {@code null} + * @return The {@link Readable} wrapper + */ + static Readable readable(ByteBuffer buffer) { + Objects.requireNonNull(buffer, "No buffer to wrap"); + return new Readable() { + @Override + public int available() { + return buffer.remaining(); + } + + @Override + public void getRawBytes(byte[] data, int offset, int len) { + buffer.get(data, offset, len); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/ReflectionUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/ReflectionUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/ReflectionUtils.java new file mode 100644 index 0000000..84a84f9 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/ReflectionUtils.java @@ -0,0 +1,53 @@ +/* + * 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.lang.reflect.Field; +import java.util.Collection; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class ReflectionUtils { + public static final Function<Field, String> FIELD_NAME_EXTRACTOR = f -> (f == null) ? null : f.getName(); + + private ReflectionUtils() { + throw new UnsupportedOperationException("No instance"); + } + + public static Collection<Field> getMatchingFields(Class<?> clazz, Predicate<? super Field> acceptor) { + return GenericUtils.selectMatchingMembers(acceptor, clazz.getFields()); + } + + public static Collection<Field> getMatchingDeclaredFields(Class<?> clazz, Predicate<? super Field> acceptor) { + return GenericUtils.selectMatchingMembers(acceptor, clazz.getDeclaredFields()); + } + + public static boolean isClassAvailable(ClassLoader cl, String className) { + try { + cl.loadClass(className); + return true; + } catch (Throwable ignored) { + return false; + } + } +}
