This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push: new e5aa6d084 Refactor string joining into AppendableJoiner (#1244) e5aa6d084 is described below commit e5aa6d0844e17362f7780cd5307dd16fd2681d6b Author: Gary Gregory <garydgreg...@users.noreply.github.com> AuthorDate: Thu Jul 11 08:32:58 2024 -0400 Refactor string joining into AppendableJoiner (#1244) --- .../org/apache/commons/lang3/AppendableJoiner.java | 313 ++++++++++++ .../apache/commons/lang3/reflect/TypeUtils.java | 553 +++++++++------------ .../apache/commons/lang3/AppendableJoinerTest.java | 142 ++++++ 3 files changed, 683 insertions(+), 325 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/AppendableJoiner.java b/src/main/java/org/apache/commons/lang3/AppendableJoiner.java new file mode 100644 index 000000000..e53288a2f --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/AppendableJoiner.java @@ -0,0 +1,313 @@ +/* + * 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.commons.lang3; + +import java.io.IOException; +import java.util.Iterator; +import java.util.StringJoiner; +import java.util.function.Supplier; + +import org.apache.commons.lang3.exception.UncheckedException; +import org.apache.commons.lang3.function.FailableBiConsumer; + +/** + * Joins an array or {@link Iterable} into an existing {@link Appendable} like a {@link StringBuilder}; with the goal for call sites to avoid creating + * intermediary Strings. This is like {@link String#join(CharSequence, CharSequence...)}, {@link String#join(CharSequence, Iterable)}, and {@link StringJoiner}. + * <p> + * Keep an instance in a (static) variable for efficient joining into an {@link Appendable} or {@link StringBuilder} without creating temporary Strings. + * </p> + * <p> + * Use the builder and instance methods to reuse the same kind of joining prefix, suffix, delimiter, and string conversion. + * </p> + * <p> + * For example: + * </p> + * + * <pre>{@code + * // A reuseable instance + * private static final AppendableJoiner<Object> JOINER = AppendableJoiner.builder() + * .setPrefix("[") + * .setSuffix("]") + * .setDelimiter(", ") + * .get(); + * } + * ... + * // Builds straight into a StringBuilder: + * StringBuilder sbuilder = new StringBuilder("1"); + * JOINER.join(sbuilder, "A", "B"); + * sbuilder.append("2"); + * JOINER.join(sbuilder, "C", "D"); + * sbuilder.append("3"); + * // Returns "1[A, B]2[C, D]3" + * return sbuilder.toString(); + * }</pre> + * <p> + * To provide a custom Object element to {@link CharSequence} converter, call {@link Builder#setElementAppender(FailableBiConsumer)}, for example: + * </p> + * + * <pre>{@code + * private static final AppendableJoiner<Item> JOINER = AppendableJoiner.builder() + * .setElementAppender(e -> (a, e) -> a.append(e.getFoo()) + * a.append(e.getBar()) + * a.append('!')) + * ... + * .get(); + * } + * }</pre> + * <p> + * This class is immutable and thread-safe. + * </p> + * + * @param <T> the type of elements to join. + * @see Appendable + * @see StringBuilder + * @see String#join(CharSequence, CharSequence...) + * @see String#join(CharSequence, Iterable) + * @see StringJoiner + * @since 3.15.0 + */ +public final class AppendableJoiner<T> { + + /** + * Builds instances of {@link AppendableJoiner}. + * + * @param <T> the type of elements to join. + */ + public static final class Builder<T> implements Supplier<AppendableJoiner<T>> { + + /** The sequence of characters to be used at the beginning. */ + private CharSequence prefix; + + /** The sequence of characters to be used at the end. */ + private CharSequence suffix; + + /** The delimiter that separates each element. */ + private CharSequence delimiter; + + /** The consumer used to render each element of type {@code T} onto an {@link Appendable}. */ + private FailableBiConsumer<Appendable, T, IOException> appender; + + /** + * Constructs a new instance. + */ + Builder() { + // empty + } + + /** + * Gets a new instance of {@link AppendableJoiner}. + */ + @Override + public AppendableJoiner<T> get() { + return new AppendableJoiner<>(prefix, suffix, delimiter, appender); + } + + /** + * Sets the delimiter that separates each element. + * + * @param delimiter The delimiter that separates each element. + * @return this instance. + */ + public Builder<T> setDelimiter(final CharSequence delimiter) { + this.delimiter = delimiter; + return this; + } + + /** + * Sets the consumer used to render each element of type {@code T} onto an {@link Appendable}. + * + * @param appender The consumer used to render each element of type {@code T} onto an {@link Appendable}. + * @return this instance. + */ + public Builder<T> setElementAppender(final FailableBiConsumer<Appendable, T, IOException> appender) { + this.appender = appender; + return this; + } + + /** + * Sets the sequence of characters to be used at the beginning. + * + * @param prefix The sequence of characters to be used at the beginning. + * @return this instance. + */ + public Builder<T> setPrefix(final CharSequence prefix) { + this.prefix = prefix; + return this; + } + + /** + * Sets the sequence of characters to be used at the end. + * + * @param suffix The sequence of characters to be used at the end. + * @return this instance. + */ + public Builder<T> setSuffix(final CharSequence suffix) { + this.suffix = suffix; + return this; + } + + } + + /** + * Creates a new builder. + * + * @param <T> The type of elements. + * @return a new builder. + */ + public static <T> Builder<T> builder() { + return new Builder<>(); + } + + /** Could be public in the future, in some form. */ + @SafeVarargs + static <A extends Appendable, T> A joinA(final A appendable, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter, + final FailableBiConsumer<Appendable, T, IOException> appender, final T... elements) throws IOException { + return joinArray(appendable, prefix, suffix, delimiter, appender, elements); + } + + private static <A extends Appendable, T> A joinArray(final A appendable, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter, + final FailableBiConsumer<Appendable, T, IOException> appender, final T[] elements) throws IOException { + appendable.append(prefix); + if (elements != null) { + if (elements.length > 0) { + appender.accept(appendable, elements[0]); + } + for (int i = 1; i < elements.length; i++) { + appendable.append(delimiter); + appender.accept(appendable, elements[i]); + } + } + appendable.append(suffix); + return appendable; + } + + /** Could be public in the future, in some form. */ + static <T> StringBuilder joinI(final StringBuilder stringBuilder, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter, + final FailableBiConsumer<Appendable, T, IOException> appender, final Iterable<T> elements) { + try { + return joinIterable(stringBuilder, prefix, suffix, delimiter, appender, elements); + } catch (final IOException e) { + // Cannot happen with a StringBuilder. + throw new UncheckedException(e); + } + } + + private static <A extends Appendable, T> A joinIterable(final A appendable, final CharSequence prefix, final CharSequence suffix, + final CharSequence delimiter, final FailableBiConsumer<Appendable, T, IOException> appender, final Iterable<T> elements) throws IOException { + appendable.append(prefix); + if (elements != null) { + final Iterator<T> iterator = elements.iterator(); + if (iterator.hasNext()) { + appender.accept(appendable, iterator.next()); + } + while (iterator.hasNext()) { + appendable.append(delimiter); + appender.accept(appendable, iterator.next()); + } + } + appendable.append(suffix); + return appendable; + } + + /** Could be public in the future, in some form. */ + @SafeVarargs + static <T> StringBuilder joinSB(final StringBuilder stringBuilder, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter, + final FailableBiConsumer<Appendable, T, IOException> appender, final T... elements) { + try { + return joinArray(stringBuilder, prefix, suffix, delimiter, appender, elements); + } catch (final IOException e) { + // Cannot happen with a StringBuilder. + throw new UncheckedException(e); + } + } + + private static CharSequence nonNull(final CharSequence value) { + return value != null ? value : StringUtils.EMPTY; + } + + /** The sequence of characters to be used at the beginning. */ + private final CharSequence prefix; + + /** The sequence of characters to be used at the end. */ + private final CharSequence suffix; + + /** The delimiter that separates each element. */ + private final CharSequence delimiter; + + private final FailableBiConsumer<Appendable, T, IOException> appender; + + /** + * Constructs a new instance. + */ + private AppendableJoiner(final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter, + final FailableBiConsumer<Appendable, T, IOException> appender) { + this.prefix = nonNull(prefix); + this.suffix = nonNull(suffix); + this.delimiter = nonNull(delimiter); + this.appender = appender != null ? appender : (a, e) -> a.append(String.valueOf(e)); + } + + /** + * Joins stringified objects from the given Iterable into a StringBuilder. + * + * @param stringBuilder The target. + * @param elements The source. + * @return The given StringBuilder. + */ + public StringBuilder join(final StringBuilder stringBuilder, final Iterable<T> elements) { + return joinI(stringBuilder, prefix, suffix, delimiter, appender, elements); + } + + /** + * Joins stringified objects from the given array into a StringBuilder. + * + * @param stringBuilder The target. + * @param elements The source. + * @return the given target StringBuilder. + */ + public StringBuilder join(final StringBuilder stringBuilder, @SuppressWarnings("unchecked") final T... elements) { + return joinSB(stringBuilder, prefix, suffix, delimiter, appender, elements); + } + + /** + * Joins stringified objects from the given Iterable into an Appendable. + * + * @param <A> the Appendable type. + * @param appendable The target. + * @param elements The source. + * @return The given StringBuilder. + * @throws IOException If an I/O error occurs + */ + public <A extends Appendable> A joinA(final A appendable, final Iterable<T> elements) throws IOException { + return joinIterable(appendable, prefix, suffix, delimiter, appender, elements); + } + + /** + * Joins stringified objects from the given array into an Appendable. + * + * @param <A> the Appendable type. + * @param appendable The target. + * @param elements The source. + * @return The given StringBuilder. + * @throws IOException If an I/O error occurs + */ + public <A extends Appendable> A joinA(final A appendable, @SuppressWarnings("unchecked") final T... elements) throws IOException { + return joinA(appendable, prefix, suffix, delimiter, appender, elements); + } + +} diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java index 9d4fbe1af..21b583e8f 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java @@ -35,6 +35,7 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; +import org.apache.commons.lang3.AppendableJoiner; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.ObjectUtils; @@ -42,13 +43,44 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.Builder; /** - * Utility methods focusing on type inspection, particularly with regard to - * generics. + * Utility methods focusing on type inspection, particularly with regard to generics. * * @since 3.0 */ public class TypeUtils { + /** + * Ampersand sign joiner. + */ + // @formatter:off + private static final AppendableJoiner<Type> AMP_JOINER = AppendableJoiner.<Type>builder() + .setDelimiter(" & ") + .setElementAppender((a, e) -> a.append(TypeUtils.toString(e))) + .get(); + // @formatter:on + + /** + * Method classToString joiner. + */ + // @formatter:off + private static final AppendableJoiner<TypeVariable<Class<?>>> CTJ_JOINER = AppendableJoiner.<TypeVariable<Class<?>>>builder() + .setDelimiter(", ") + .setElementAppender((a, e) -> a.append(TypeUtils.anyToString(e))) + .get(); + // @formatter:on + + /** + * Greater than and lesser than sign joiner. + */ + // @formatter:off + private static final AppendableJoiner<Object> GT_JOINER = AppendableJoiner.builder() + .setPrefix("<") + .setSuffix(">") + .setDelimiter(", ") + .setElementAppender((a, e) -> a.append(TypeUtils.anyToString(e))) + .get(); + // @formatter:on + /** * GenericArrayType implementation class. */ @@ -57,6 +89,7 @@ public class TypeUtils { /** * Constructor + * * @param componentType of this array type */ private GenericArrayTypeImpl(final Type componentType) { @@ -108,8 +141,9 @@ public class TypeUtils { /** * Constructor - * @param rawClass type - * @param useOwner owner type to use, if any + * + * @param rawClass type + * @param useOwner owner type to use, if any * @param typeArguments formal type arguments */ private ParameterizedTypeImpl(final Class<?> rawClass, final Type useOwner, final Type[] typeArguments) { @@ -182,6 +216,7 @@ public class TypeUtils { private Type[] upperBounds; private Type[] lowerBounds; + /** * Constructor */ @@ -198,6 +233,7 @@ public class TypeUtils { /** * Specify lower bounds of the wildcard type to build. + * * @param bounds to set * @return {@code this} */ @@ -208,6 +244,7 @@ public class TypeUtils { /** * Specify upper bounds of the wildcard type to build. + * * @param bounds to set * @return {@code this} */ @@ -226,6 +263,7 @@ public class TypeUtils { /** * Constructor + * * @param upperBounds of this type * @param lowerBounds of this type */ @@ -286,36 +324,14 @@ public class TypeUtils { */ public static final WildcardType WILDCARD_ALL = wildcardType().withUpperBounds(Object.class).build(); - /** - * Appends {@code types} to {@code builder} with separator {@code sep}. - * - * @param builder destination - * @param sep separator - * @param types to append - * @return {@code builder} - */ - private static <T> StringBuilder appendAllTo(final StringBuilder builder, final String sep, - @SuppressWarnings("unchecked") final T... types) { - Validate.notEmpty(Validate.noNullElements(types)); - if (types.length > 0) { - builder.append(toString(types[0])); - for (int i = 1; i < types.length; i++) { - builder.append(sep).append(toString(types[i])); - } - } - return builder; - } - - private static void appendRecursiveTypes(final StringBuilder builder, final int[] recursiveTypeIndexes, - final Type[] argumentTypes) { + private static void appendRecursiveTypes(final StringBuilder builder, final int[] recursiveTypeIndexes, final Type[] argumentTypes) { for (int i = 0; i < recursiveTypeIndexes.length; i++) { - appendAllTo(builder.append('<'), ", ", argumentTypes[i].toString()).append('>'); + // toString() or SO + GT_JOINER.join(builder, argumentTypes[i].toString()); } - final Type[] argumentsFiltered = ArrayUtils.removeAll(argumentTypes, recursiveTypeIndexes); - if (argumentsFiltered.length > 0) { - appendAllTo(builder.append('<'), ", ", argumentsFiltered).append('>'); + GT_JOINER.join(builder, (Object[]) argumentsFiltered); } } @@ -325,7 +341,7 @@ public class TypeUtils { * @param cls {@link Class} to format * @return String */ - private static String classToString(final Class<?> cls) { + private static <T> String classToString(final Class<T> cls) { if (cls.isArray()) { return toString(cls.getComponentType()) + "[]"; } @@ -339,9 +355,8 @@ public class TypeUtils { buf.append(cls.getName()); } if (cls.getTypeParameters().length > 0) { - buf.append('<'); - appendAllTo(buf, ", ", cls.getTypeParameters()); - buf.append('>'); + // AppendableJoiner.joinSB(buf, null, null, ", ", TypeUtils::anyToString, cls.getTypeParameters()); + CTJ_JOINER.join(buf, (TypeVariable[]) cls.getTypeParameters()); } return buf.toString(); } @@ -370,8 +385,7 @@ public class TypeUtils { } if (type instanceof WildcardType) { final WildcardType wild = (WildcardType) type; - return containsTypeVariables(getImplicitLowerBounds(wild)[0]) - || containsTypeVariables(getImplicitUpperBounds(wild)[0]); + return containsTypeVariables(getImplicitLowerBounds(wild)[0]) || containsTypeVariables(getImplicitUpperBounds(wild)[0]); } if (type instanceof GenericArrayType) { return containsTypeVariables(((GenericArrayType) type).getGenericComponentType()); @@ -379,45 +393,32 @@ public class TypeUtils { return false; } - private static boolean containsVariableTypeSameParametrizedTypeBound(final TypeVariable<?> typeVariable, - final ParameterizedType parameterizedType) { + private static boolean containsVariableTypeSameParametrizedTypeBound(final TypeVariable<?> typeVariable, final ParameterizedType parameterizedType) { return ArrayUtils.contains(typeVariable.getBounds(), parameterizedType); } /** - * Tries to determine the type arguments of a class/interface based on a - * super parameterized type's type arguments. This method is the inverse of - * {@link #getTypeArguments(Type, Class)} which gets a class/interface's - * type arguments based on a subtype. It is far more limited in determining - * the type arguments for the subject class's type variables in that it can - * only determine those parameters that map from the subject {@link Class} - * object to the supertype. + * Tries to determine the type arguments of a class/interface based on a super parameterized type's type arguments. This method is the inverse of + * {@link #getTypeArguments(Type, Class)} which gets a class/interface's type arguments based on a subtype. It is far more limited in determining the type + * arguments for the subject class's type variables in that it can only determine those parameters that map from the subject {@link Class} object to the + * supertype. * * <p> - * Example: {@link java.util.TreeSet - * TreeSet} sets its parameter as the parameter for - * {@link java.util.NavigableSet NavigableSet}, which in turn sets the - * parameter of {@link java.util.SortedSet}, which in turn sets the - * parameter of {@link Set}, which in turn sets the parameter of - * {@link java.util.Collection}, which in turn sets the parameter of - * {@link Iterable}. Since {@link TreeSet}'s parameter maps - * (indirectly) to {@link Iterable}'s parameter, it will be able to - * determine that based on the super type {@code Iterable<? extends - * Map<Integer, ? extends Collection<?>>>}, the parameter of - * {@link TreeSet} is {@code ? extends Map<Integer, ? extends + * Example: {@link java.util.TreeSet TreeSet} sets its parameter as the parameter for {@link java.util.NavigableSet NavigableSet}, which in turn sets the + * parameter of {@link java.util.SortedSet}, which in turn sets the parameter of {@link Set}, which in turn sets the parameter of + * {@link java.util.Collection}, which in turn sets the parameter of {@link Iterable}. Since {@link TreeSet}'s parameter maps (indirectly) to + * {@link Iterable}'s parameter, it will be able to determine that based on the super type {@code Iterable<? extends + * Map<Integer, ? extends Collection<?>>>}, the parameter of {@link TreeSet} is {@code ? extends Map<Integer, ? extends * Collection<?>>}. * </p> * - * @param cls the class whose type parameters are to be determined, not {@code null} - * @param superParameterizedType the super type from which {@code cls}'s type - * arguments are to be determined, not {@code null} - * @return a {@link Map} of the type assignments that could be determined - * for the type variables in each type in the inheritance hierarchy from - * {@code type} to {@code toClass} inclusive. + * @param cls the class whose type parameters are to be determined, not {@code null} + * @param superParameterizedType the super type from which {@code cls}'s type arguments are to be determined, not {@code null} + * @return a {@link Map} of the type assignments that could be determined for the type variables in each type in the inheritance hierarchy from {@code type} + * to {@code toClass} inclusive. * @throws NullPointerException if either {@code cls} or {@code superParameterizedType} is {@code null} */ - public static Map<TypeVariable<?>, Type> determineTypeArguments(final Class<?> cls, - final ParameterizedType superParameterizedType) { + public static Map<TypeVariable<?>, Type> determineTypeArguments(final Class<?> cls, final ParameterizedType superParameterizedType) { Objects.requireNonNull(cls, "cls"); Objects.requireNonNull(superParameterizedType, "superParameterizedType"); @@ -455,26 +456,24 @@ public class TypeUtils { * Tests whether {@code t} equals {@code a}. * * @param genericArrayType LHS - * @param type RHS + * @param type RHS * @return boolean */ private static boolean equals(final GenericArrayType genericArrayType, final Type type) { - return type instanceof GenericArrayType - && equals(genericArrayType.getGenericComponentType(), ((GenericArrayType) type).getGenericComponentType()); + return type instanceof GenericArrayType && equals(genericArrayType.getGenericComponentType(), ((GenericArrayType) type).getGenericComponentType()); } /** * Tests whether {@code t} equals {@code p}. * * @param parameterizedType LHS - * @param type RHS + * @param type RHS * @return boolean */ private static boolean equals(final ParameterizedType parameterizedType, final Type type) { if (type instanceof ParameterizedType) { final ParameterizedType other = (ParameterizedType) type; - if (equals(parameterizedType.getRawType(), other.getRawType()) - && equals(parameterizedType.getOwnerType(), other.getOwnerType())) { + if (equals(parameterizedType.getRawType(), other.getRawType()) && equals(parameterizedType.getOwnerType(), other.getOwnerType())) { return equals(parameterizedType.getActualTypeArguments(), other.getActualTypeArguments()); } } @@ -528,14 +527,14 @@ public class TypeUtils { * Tests whether {@code t} equals {@code w}. * * @param wildcardType LHS - * @param type RHS + * @param type RHS * @return boolean */ private static boolean equals(final WildcardType wildcardType, final Type type) { if (type instanceof WildcardType) { final WildcardType other = (WildcardType) type; return equals(getImplicitLowerBounds(wildcardType), getImplicitLowerBounds(other)) - && equals(getImplicitUpperBounds(wildcardType), getImplicitUpperBounds(other)); + && equals(getImplicitUpperBounds(wildcardType), getImplicitUpperBounds(other)); } return false; } @@ -543,7 +542,7 @@ public class TypeUtils { /** * Helper method to establish the formal parameters for a parameterized type. * - * @param mappings map containing the assignments + * @param mappings map containing the assignments * @param variables expected map keys * @return array of map values corresponding to specified keys */ @@ -558,12 +557,11 @@ public class TypeUtils { } private static int[] findRecursiveTypes(final ParameterizedType parameterizedType) { - final Type[] filteredArgumentTypes = Arrays.copyOf(parameterizedType.getActualTypeArguments(), - parameterizedType.getActualTypeArguments().length); + final Type[] filteredArgumentTypes = Arrays.copyOf(parameterizedType.getActualTypeArguments(), parameterizedType.getActualTypeArguments().length); int[] indexesToRemove = {}; for (int i = 0; i < filteredArgumentTypes.length; i++) { - if (filteredArgumentTypes[i] instanceof TypeVariable<?> && containsVariableTypeSameParametrizedTypeBound( - (TypeVariable<?>) filteredArgumentTypes[i], parameterizedType)) { + if (filteredArgumentTypes[i] instanceof TypeVariable<?> + && containsVariableTypeSameParametrizedTypeBound((TypeVariable<?>) filteredArgumentTypes[i], parameterizedType)) { indexesToRemove = ArrayUtils.add(indexesToRemove, i); } } @@ -573,8 +571,7 @@ public class TypeUtils { /** * Creates a generic array type instance. * - * @param componentType the type of the elements of the array. For example the component type of {@code boolean[]} - * is {@code boolean} + * @param componentType the type of the elements of the array. For example the component type of {@code boolean[]} is {@code boolean} * @return {@link GenericArrayType} * @since 3.2 */ @@ -610,10 +607,9 @@ public class TypeUtils { } /** - * Gets the closest parent type to the - * super class specified by {@code superClass}. + * Gets the closest parent type to the super class specified by {@code superClass}. * - * @param cls the class in question + * @param cls the class in question * @param superClass the super class * @return the closes parent type */ @@ -634,14 +630,12 @@ public class TypeUtils { } else if (midType instanceof Class<?>) { midClass = (Class<?>) midType; } else { - throw new IllegalStateException("Unexpected generic" - + " interface type found: " + midType); + throw new IllegalStateException("Unexpected generic" + " interface type found: " + midType); } // check if this interface is further up the inheritance chain // than the previously found match - if (isAssignable(midClass, superClass) - && isAssignable(genericInterface, (Type) midClass)) { + if (isAssignable(midClass, superClass) && isAssignable(genericInterface, (Type) midClass)) { genericInterface = midType; } } @@ -658,10 +652,8 @@ public class TypeUtils { } /** - * Gets an array containing the sole type of {@link Object} if - * {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it - * returns the result of {@link TypeVariable#getBounds()} passed into - * {@link #normalizeUpperBounds}. + * Gets an array containing the sole type of {@link Object} if {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it returns the result of + * {@link TypeVariable#getBounds()} passed into {@link #normalizeUpperBounds}. * * @param typeVariable the subject type variable, not {@code null} * @return a non-empty array containing the bounds of the type variable. @@ -675,13 +667,11 @@ public class TypeUtils { } /** - * Gets an array containing a single value of {@code null} if - * {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise, - * it returns the result of {@link WildcardType#getLowerBounds()}. + * Gets an array containing a single value of {@code null} if {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise, it returns the result + * of {@link WildcardType#getLowerBounds()}. * * @param wildcardType the subject wildcard type, not {@code null} - * @return a non-empty array containing the lower bounds of the wildcard - * type. + * @return a non-empty array containing the lower bounds of the wildcard type. * @throws NullPointerException if {@code wildcardType} is {@code null} */ public static Type[] getImplicitLowerBounds(final WildcardType wildcardType) { @@ -692,14 +682,11 @@ public class TypeUtils { } /** - * Gets an array containing the sole value of {@link Object} if - * {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise, - * it returns the result of {@link WildcardType#getUpperBounds()} - * passed into {@link #normalizeUpperBounds}. + * Gets an array containing the sole value of {@link Object} if {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise, it returns the + * result of {@link WildcardType#getUpperBounds()} passed into {@link #normalizeUpperBounds}. * * @param wildcardType the subject wildcard type, not {@code null} - * @return a non-empty array containing the upper bounds of the wildcard - * type. + * @return a non-empty array containing the upper bounds of the wildcard type. * @throws NullPointerException if {@code wildcardType} is {@code null} */ public static Type[] getImplicitUpperBounds(final WildcardType wildcardType) { @@ -732,16 +719,13 @@ public class TypeUtils { } /** - * Gets the raw type of a Java type, given its context. Primarily for use - * with {@link TypeVariable}s and {@link GenericArrayType}s, or when you do - * not know the runtime type of {@code type}: if you know you have a - * {@link Class} instance, it is already raw; if you know you have a - * {@link ParameterizedType}, its raw type is only a method call away. + * Gets the raw type of a Java type, given its context. Primarily for use with {@link TypeVariable}s and {@link GenericArrayType}s, or when you do not know + * the runtime type of {@code type}: if you know you have a {@link Class} instance, it is already raw; if you know you have a {@link ParameterizedType}, its + * raw type is only a method call away. * - * @param type to resolve + * @param type to resolve * @param assigningType type to be resolved against - * @return the resolved {@link Class} object or {@code null} if - * the type could not be resolved + * @return the resolved {@link Class} object or {@code null} if the type could not be resolved */ public static Class<?> getRawType(final Type type, final Type assigningType) { if (type instanceof Class<?>) { @@ -770,8 +754,7 @@ public class TypeUtils { // get the type arguments for the declaring class/interface based // on the enclosing type - final Map<TypeVariable<?>, Type> typeVarAssigns = getTypeArguments(assigningType, - (Class<?>) genericDeclaration); + final Map<TypeVariable<?>, Type> typeVarAssigns = getTypeArguments(assigningType, (Class<?>) genericDeclaration); // enclosingType has to be a subclass (or subinterface) of the // declaring type @@ -792,8 +775,7 @@ public class TypeUtils { if (type instanceof GenericArrayType) { // get raw component type - final Class<?> rawComponentType = getRawType(((GenericArrayType) type) - .getGenericComponentType(), assigningType); + final Class<?> rawComponentType = getRawType(((GenericArrayType) type).getGenericComponentType(), assigningType); // create array type from raw component type and return its class return rawComponentType != null ? Array.newInstance(rawComponentType, 0).getClass() : null; @@ -810,13 +792,12 @@ public class TypeUtils { /** * Gets a map of the type arguments of a class in the context of {@code toClass}. * - * @param cls the class in question - * @param toClass the context class + * @param cls the class in question + * @param toClass the context class * @param subtypeVarAssigns a map with type variables * @return the {@link Map} with type arguments */ - private static Map<TypeVariable<?>, Type> getTypeArguments(Class<?> cls, final Class<?> toClass, - final Map<TypeVariable<?>, Type> subtypeVarAssigns) { + private static Map<TypeVariable<?>, Type> getTypeArguments(Class<?> cls, final Class<?> toClass, final Map<TypeVariable<?>, Type> subtypeVarAssigns) { // make sure they're assignable if (!isAssignable(cls, toClass)) { return null; @@ -836,8 +817,7 @@ public class TypeUtils { } // create a copy of the incoming map, or an empty one if it's null - final HashMap<TypeVariable<?>, Type> typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>() - : new HashMap<>(subtypeVarAssigns); + final HashMap<TypeVariable<?>, Type> typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>() : new HashMap<>(subtypeVarAssigns); // has target class been reached? if (toClass.equals(cls)) { @@ -849,16 +829,11 @@ public class TypeUtils { } /** - * Gets all the type arguments for this parameterized type - * including owner hierarchy arguments such as - * {@code Outer<K, V>.Inner<T>.DeepInner<E>} . - * The arguments are returned in a - * {@link Map} specifying the argument type for each {@link TypeVariable}. + * Gets all the type arguments for this parameterized type including owner hierarchy arguments such as {@code Outer<K, V>.Inner<T>.DeepInner<E>} . The + * arguments are returned in a {@link Map} specifying the argument type for each {@link TypeVariable}. * - * @param type specifies the subject parameterized type from which to - * harvest the parameters. - * @return a {@link Map} of the type arguments to their respective type - * variables. + * @param type specifies the subject parameterized type from which to harvest the parameters. + * @return a {@link Map} of the type arguments to their respective type variables. */ public static Map<TypeVariable<?>, Type> getTypeArguments(final ParameterizedType type) { return getTypeArguments(type, getRawType(type), null); @@ -868,12 +843,11 @@ public class TypeUtils { * Gets a map of the type arguments of a parameterized type in the context of {@code toClass}. * * @param parameterizedType the parameterized type - * @param toClass the class + * @param toClass the class * @param subtypeVarAssigns a map with type variables * @return the {@link Map} with type arguments */ - private static Map<TypeVariable<?>, Type> getTypeArguments( - final ParameterizedType parameterizedType, final Class<?> toClass, + private static Map<TypeVariable<?>, Type> getTypeArguments(final ParameterizedType parameterizedType, final Class<?> toClass, final Map<TypeVariable<?>, Type> subtypeVarAssigns) { final Class<?> cls = getRawType(parameterizedType); @@ -888,12 +862,10 @@ public class TypeUtils { if (ownerType instanceof ParameterizedType) { // get the owner type arguments first final ParameterizedType parameterizedOwnerType = (ParameterizedType) ownerType; - typeVarAssigns = getTypeArguments(parameterizedOwnerType, - getRawType(parameterizedOwnerType), subtypeVarAssigns); + typeVarAssigns = getTypeArguments(parameterizedOwnerType, getRawType(parameterizedOwnerType), subtypeVarAssigns); } else { // no owner, prep the type variable assignments map - typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>() - : new HashMap<>(subtypeVarAssigns); + typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>() : new HashMap<>(subtypeVarAssigns); } // get the subject parameterized type's arguments @@ -904,10 +876,7 @@ public class TypeUtils { // map the arguments to their respective type variables for (int i = 0; i < typeParams.length; i++) { final Type typeArg = typeArgs[i]; - typeVarAssigns.put( - typeParams[i], - typeVarAssigns.getOrDefault(typeArg, typeArg) - ); + typeVarAssigns.put(typeParams[i], typeVarAssigns.getOrDefault(typeArg, typeArg)); } if (toClass.equals(cls)) { @@ -920,42 +889,29 @@ public class TypeUtils { } /** - * Gets the type arguments of a class/interface based on a subtype. For - * instance, this method will determine that both of the parameters for the - * interface {@link Map} are {@link Object} for the subtype - * {@link java.util.Properties Properties} even though the subtype does not - * directly implement the {@link Map} interface. + * Gets the type arguments of a class/interface based on a subtype. For instance, this method will determine that both of the parameters for the interface + * {@link Map} are {@link Object} for the subtype {@link java.util.Properties Properties} even though the subtype does not directly implement the + * {@link Map} interface. * * <p> - * This method returns {@code null} if {@code type} is not assignable to - * {@code toClass}. It returns an empty map if none of the classes or - * interfaces in its inheritance hierarchy specify any type arguments. + * This method returns {@code null} if {@code type} is not assignable to {@code toClass}. It returns an empty map if none of the classes or interfaces in + * its inheritance hierarchy specify any type arguments. * </p> * * <p> - * A side effect of this method is that it also retrieves the type - * arguments for the classes and interfaces that are part of the hierarchy - * between {@code type} and {@code toClass}. So with the above - * example, this method will also determine that the type arguments for - * {@link java.util.Hashtable Hashtable} are also both {@link Object}. - * In cases where the interface specified by {@code toClass} is - * (indirectly) implemented more than once (e.g. where {@code toClass} - * specifies the interface {@link Iterable Iterable} and - * {@code type} specifies a parameterized type that implements both - * {@link java.util.Set Set} and {@link java.util.Collection Collection}), - * this method will look at the inheritance hierarchy of only one of the - * implementations/subclasses; the first interface encountered that isn't a - * subinterface to one of the others in the {@code type} to - * {@code toClass} hierarchy. + * A side effect of this method is that it also retrieves the type arguments for the classes and interfaces that are part of the hierarchy between + * {@code type} and {@code toClass}. So with the above example, this method will also determine that the type arguments for {@link java.util.Hashtable + * Hashtable} are also both {@link Object}. In cases where the interface specified by {@code toClass} is (indirectly) implemented more than once (e.g. where + * {@code toClass} specifies the interface {@link Iterable Iterable} and {@code type} specifies a parameterized type that implements both + * {@link java.util.Set Set} and {@link java.util.Collection Collection}), this method will look at the inheritance hierarchy of only one of the + * implementations/subclasses; the first interface encountered that isn't a subinterface to one of the others in the {@code type} to {@code toClass} + * hierarchy. * </p> * - * @param type the type from which to determine the type parameters of - * {@code toClass} - * @param toClass the class whose type parameters are to be determined based - * on the subtype {@code type} - * @return a {@link Map} of the type assignments for the type variables in - * each type in the inheritance hierarchy from {@code type} to - * {@code toClass} inclusive. + * @param type the type from which to determine the type parameters of {@code toClass} + * @param toClass the class whose type parameters are to be determined based on the subtype {@code type} + * @return a {@link Map} of the type assignments for the type variables in each type in the inheritance hierarchy from {@code type} to {@code toClass} + * inclusive. */ public static Map<TypeVariable<?>, Type> getTypeArguments(final Type type, final Class<?> toClass) { return getTypeArguments(type, toClass, null); @@ -964,13 +920,12 @@ public class TypeUtils { /** * Gets a map of the type arguments of {@code type} in the context of {@code toClass}. * - * @param type the type in question - * @param toClass the class + * @param type the type in question + * @param toClass the class * @param subtypeVarAssigns a map with type variables * @return the {@link Map} with type arguments */ - private static Map<TypeVariable<?>, Type> getTypeArguments(final Type type, final Class<?> toClass, - final Map<TypeVariable<?>, Type> subtypeVarAssigns) { + private static Map<TypeVariable<?>, Type> getTypeArguments(final Type type, final Class<?> toClass, final Map<TypeVariable<?>, Type> subtypeVarAssigns) { if (type instanceof Class<?>) { return getTypeArguments((Class<?>) type, toClass, subtypeVarAssigns); } @@ -980,8 +935,8 @@ public class TypeUtils { } if (type instanceof GenericArrayType) { - return getTypeArguments(((GenericArrayType) type).getGenericComponentType(), toClass - .isArray() ? toClass.getComponentType() : toClass, subtypeVarAssigns); + return getTypeArguments(((GenericArrayType) type).getGenericComponentType(), toClass.isArray() ? toClass.getComponentType() : toClass, + subtypeVarAssigns); } // since wildcard types are not assignable to classes, should this just @@ -1021,10 +976,9 @@ public class TypeUtils { } /** - * Tests if the subject type may be implicitly cast to the target class - * following the Java generics rules. + * Tests if the subject type may be implicitly cast to the target class following the Java generics rules. * - * @param type the subject type to be assigned to the target type + * @param type the subject type to be assigned to the target type * @param toClass the target class * @return {@code true} if {@code type} is assignable to {@code toClass}. */ @@ -1072,9 +1026,7 @@ public class TypeUtils { // are class Object and array classes if (type instanceof GenericArrayType) { return toClass.equals(Object.class) - || toClass.isArray() - && isAssignable(((GenericArrayType) type).getGenericComponentType(), toClass - .getComponentType()); + || toClass.isArray() && isAssignable(((GenericArrayType) type).getGenericComponentType(), toClass.getComponentType()); } // wildcard types are not assignable to a class (though one would think @@ -1087,17 +1039,14 @@ public class TypeUtils { } /** - * Tests if the subject type may be implicitly cast to the target - * generic array type following the Java generics rules. + * Tests if the subject type may be implicitly cast to the target generic array type following the Java generics rules. * - * @param type the subject type to be assigned to the target type + * @param type the subject type to be assigned to the target type * @param toGenericArrayType the target generic array type - * @param typeVarAssigns a map with type variables - * @return {@code true} if {@code type} is assignable to - * {@code toGenericArrayType}. + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to {@code toGenericArrayType}. */ - private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType, - final Map<TypeVariable<?>, Type> typeVarAssigns) { + private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType, final Map<TypeVariable<?>, Type> typeVarAssigns) { if (type == null) { return true; } @@ -1119,14 +1068,12 @@ public class TypeUtils { final Class<?> cls = (Class<?>) type; // compare the component types - return cls.isArray() - && isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns); + return cls.isArray() && isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns); } if (type instanceof GenericArrayType) { // compare the component types - return isAssignable(((GenericArrayType) type).getGenericComponentType(), - toComponentType, typeVarAssigns); + return isAssignable(((GenericArrayType) type).getGenericComponentType(), toComponentType, typeVarAssigns); } if (type instanceof WildcardType) { @@ -1163,16 +1110,14 @@ public class TypeUtils { } /** - * Tests if the subject type may be implicitly cast to the target - * parameterized type following the Java generics rules. + * Tests if the subject type may be implicitly cast to the target parameterized type following the Java generics rules. * - * @param type the subject type to be assigned to the target type + * @param type the subject type to be assigned to the target type * @param toParameterizedType the target parameterized type - * @param typeVarAssigns a map with type variables + * @param typeVarAssigns a map with type variables * @return {@code true} if {@code type} is assignable to {@code toType}. */ - private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType, - final Map<TypeVariable<?>, Type> typeVarAssigns) { + private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType, final Map<TypeVariable<?>, Type> typeVarAssigns) { if (type == null) { return true; } @@ -1212,8 +1157,7 @@ public class TypeUtils { } // get the target type's type arguments including owner type arguments - final Map<TypeVariable<?>, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType, - toClass, typeVarAssigns); + final Map<TypeVariable<?>, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType, toClass, typeVarAssigns); // now to check each type argument for (final TypeVariable<?> var : toTypeVarAssigns.keySet()) { @@ -1227,10 +1171,8 @@ public class TypeUtils { // parameters must either be absent from the subject type, within // the bounds of the wildcard type, or be an exact match to the // parameters of the target type. - if (fromTypeArg != null && toTypeArg != null - && !toTypeArg.equals(fromTypeArg) - && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg, - typeVarAssigns))) { + if (fromTypeArg != null && toTypeArg != null && !toTypeArg.equals(fromTypeArg) + && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg, typeVarAssigns))) { return false; } } @@ -1238,12 +1180,10 @@ public class TypeUtils { } /** - * Tests if the subject type may be implicitly cast to the target type - * following the Java generics rules. If both types are {@link Class} - * objects, the method returns the result of - * {@link ClassUtils#isAssignable(Class, Class)}. + * Tests if the subject type may be implicitly cast to the target type following the Java generics rules. If both types are {@link Class} objects, the + * method returns the result of {@link ClassUtils#isAssignable(Class, Class)}. * - * @param type the subject type to be assigned to the target type + * @param type the subject type to be assigned to the target type * @param toType the target type * @return {@code true} if {@code type} is assignable to {@code toType}. */ @@ -1252,16 +1192,14 @@ public class TypeUtils { } /** - * Tests if the subject type may be implicitly cast to the target type - * following the Java generics rules. + * Tests if the subject type may be implicitly cast to the target type following the Java generics rules. * - * @param type the subject type to be assigned to the target type - * @param toType the target type + * @param type the subject type to be assigned to the target type + * @param toType the target type * @param typeVarAssigns optional map of type variable assignments * @return {@code true} if {@code type} is assignable to {@code toType}. */ - private static boolean isAssignable(final Type type, final Type toType, - final Map<TypeVariable<?>, Type> typeVarAssigns) { + private static boolean isAssignable(final Type type, final Type toType, final Map<TypeVariable<?>, Type> typeVarAssigns) { if (toType == null || toType instanceof Class<?>) { return isAssignable(type, (Class<?>) toType); } @@ -1286,17 +1224,14 @@ public class TypeUtils { } /** - * Tests if the subject type may be implicitly cast to the target type - * variable following the Java generics rules. + * Tests if the subject type may be implicitly cast to the target type variable following the Java generics rules. * - * @param type the subject type to be assigned to the target type + * @param type the subject type to be assigned to the target type * @param toTypeVariable the target type variable * @param typeVarAssigns a map with type variables - * @return {@code true} if {@code type} is assignable to - * {@code toTypeVariable}. + * @return {@code true} if {@code type} is assignable to {@code toTypeVariable}. */ - private static boolean isAssignable(final Type type, final TypeVariable<?> toTypeVariable, - final Map<TypeVariable<?>, Type> typeVarAssigns) { + private static boolean isAssignable(final Type type, final TypeVariable<?> toTypeVariable, final Map<TypeVariable<?>, Type> typeVarAssigns) { if (type == null) { return true; } @@ -1325,8 +1260,7 @@ public class TypeUtils { } } - if (type instanceof Class<?> || type instanceof ParameterizedType - || type instanceof GenericArrayType || type instanceof WildcardType) { + if (type instanceof Class<?> || type instanceof ParameterizedType || type instanceof GenericArrayType || type instanceof WildcardType) { return false; } @@ -1334,17 +1268,14 @@ public class TypeUtils { } /** - * Tests if the subject type may be implicitly cast to the target - * wildcard type following the Java generics rules. + * Tests if the subject type may be implicitly cast to the target wildcard type following the Java generics rules. * - * @param type the subject type to be assigned to the target type + * @param type the subject type to be assigned to the target type * @param toWildcardType the target wildcard type * @param typeVarAssigns a map with type variables - * @return {@code true} if {@code type} is assignable to - * {@code toWildcardType}. + * @return {@code true} if {@code type} is assignable to {@code toWildcardType}. */ - private static boolean isAssignable(final Type type, final WildcardType toWildcardType, - final Map<TypeVariable<?>, Type> typeVarAssigns) { + private static boolean isAssignable(final Type type, final WildcardType toWildcardType, final Map<TypeVariable<?>, Type> typeVarAssigns) { if (type == null) { return true; } @@ -1403,8 +1334,7 @@ public class TypeUtils { for (final Type toBound : toUpperBounds) { // if there are assignments for unresolved type variables, // now's the time to substitute them. - if (!isAssignable(type, substituteTypeVariables(toBound, typeVarAssigns), - typeVarAssigns)) { + if (!isAssignable(type, substituteTypeVariables(toBound, typeVarAssigns), typeVarAssigns)) { return false; } } @@ -1412,8 +1342,7 @@ public class TypeUtils { for (final Type toBound : toLowerBounds) { // if there are assignments for unresolved type variables, // now's the time to substitute them. - if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), type, - typeVarAssigns)) { + if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), type, typeVarAssigns)) { return false; } } @@ -1439,11 +1368,10 @@ public class TypeUtils { } /** - * Tests if the given value can be assigned to the target type - * following the Java generics rules. + * Tests if the given value can be assigned to the target type following the Java generics rules. * * @param value the value to be checked - * @param type the target type + * @param type the target type * @return {@code true} if {@code value} is an instance of {@code type}. */ public static boolean isInstance(final Object value, final Type type) { @@ -1451,20 +1379,19 @@ public class TypeUtils { return false; } - return value == null ? !(type instanceof Class<?>) || !((Class<?>) type).isPrimitive() - : isAssignable(value.getClass(), type, null); + return value == null ? !(type instanceof Class<?>) || !((Class<?>) type).isPrimitive() : isAssignable(value.getClass(), type, null); } /** * Maps type variables. * - * @param <T> the generic type of the class in question - * @param cls the class in question + * @param <T> the generic type of the class in question + * @param cls the class in question * @param parameterizedType the parameterized type - * @param typeVarAssigns the map to be filled + * @param typeVarAssigns the map to be filled */ - private static <T> void mapTypeVariablesToArguments(final Class<T> cls, - final ParameterizedType parameterizedType, final Map<TypeVariable<?>, Type> typeVarAssigns) { + private static <T> void mapTypeVariablesToArguments(final Class<T> cls, final ParameterizedType parameterizedType, + final Map<TypeVariable<?>, Type> typeVarAssigns) { // capture the type variables from the owner type that have assignments final Type ownerType = parameterizedType.getOwnerType(); @@ -1484,8 +1411,7 @@ public class TypeUtils { final TypeVariable<?>[] typeVars = getRawType(parameterizedType).getTypeParameters(); // use List view of type parameters of cls so the contains() method can be used: - final List<TypeVariable<Class<T>>> typeVarList = Arrays.asList(cls - .getTypeParameters()); + final List<TypeVariable<Class<T>>> typeVarList = Arrays.asList(cls.getTypeParameters()); for (int i = 0; i < typeArgs.length; i++) { final TypeVariable<?> typeVar = typeVars[i]; @@ -1493,7 +1419,7 @@ public class TypeUtils { // argument of parameterizedType is a type variable of cls if (typeVarList.contains(typeArg) - // type variable of parameterizedType has an assignment in + // type variable of parameterizedType has an assignment in // the super type. && typeVarAssigns.containsKey(typeVar)) { // map the assignment to the cls's type variable @@ -1503,28 +1429,28 @@ public class TypeUtils { } /** - * Strips out the redundant upper bound types in type - * variable types and wildcard types (or it would with wildcard types if - * multiple upper bounds were allowed). + * Strips out the redundant upper bound types in type variable types and wildcard types (or it would with wildcard types if multiple upper bounds were + * allowed). * * <p> * Example, with the variable type declaration: * </p> * - * <pre><K extends java.util.Collection<String> & - * java.util.List<String>></pre> + * <pre> + * <K extends java.util.Collection<String> & + * java.util.List<String>> + * </pre> * * <p> - * since {@link List} is a subinterface of {@link Collection}, - * this method will return the bounds as if the declaration had been: + * since {@link List} is a subinterface of {@link Collection}, this method will return the bounds as if the declaration had been: * </p> * - * <pre><K extends java.util.List<String>></pre> + * <pre> + * <K extends java.util.List<String>> + * </pre> * - * @param bounds an array of types representing the upper bounds of either - * {@link WildcardType} or {@link TypeVariable}, not {@code null}. - * @return an array containing the values from {@code bounds} minus the - * redundant types. + * @param bounds an array of types representing the upper bounds of either {@link WildcardType} or {@link TypeVariable}, not {@code null}. + * @return an array containing the values from {@code bounds} minus the redundant types. * @throws NullPointerException if {@code bounds} is {@code null} */ public static Type[] normalizeUpperBounds(final Type[] bounds) { @@ -1557,24 +1483,22 @@ public class TypeUtils { /** * Creates a parameterized type instance. * - * @param rawClass the raw class to create a parameterized type instance for + * @param rawClass the raw class to create a parameterized type instance for * @param typeVariableMap the map used for parameterization * @return {@link ParameterizedType} * @throws NullPointerException if either {@code rawClass} or {@code typeVariableMap} is {@code null} * @since 3.2 */ - public static final ParameterizedType parameterize(final Class<?> rawClass, - final Map<TypeVariable<?>, Type> typeVariableMap) { + public static final ParameterizedType parameterize(final Class<?> rawClass, final Map<TypeVariable<?>, Type> typeVariableMap) { Objects.requireNonNull(rawClass, "rawClass"); Objects.requireNonNull(typeVariableMap, "typeVariableMap"); - return parameterizeWithOwner(null, rawClass, - extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters())); + return parameterizeWithOwner(null, rawClass, extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters())); } /** * Creates a parameterized type instance. * - * @param rawClass the raw class to create a parameterized type instance for + * @param rawClass the raw class to create a parameterized type instance for * @param typeArguments the types used for parameterization * @return {@link ParameterizedType} * @throws NullPointerException if {@code rawClass} is {@code null} @@ -1592,10 +1516,8 @@ public class TypeUtils { */ private static String parameterizedTypeToString(final ParameterizedType parameterizedType) { final StringBuilder builder = new StringBuilder(); - final Type useOwner = parameterizedType.getOwnerType(); final Class<?> raw = (Class<?>) parameterizedType.getRawType(); - if (useOwner == null) { builder.append(raw.getName()); } else { @@ -1606,50 +1528,43 @@ public class TypeUtils { } builder.append('.').append(raw.getSimpleName()); } - final int[] recursiveTypeIndexes = findRecursiveTypes(parameterizedType); - if (recursiveTypeIndexes.length > 0) { appendRecursiveTypes(builder, recursiveTypeIndexes, parameterizedType.getActualTypeArguments()); } else { - appendAllTo(builder.append('<'), ", ", parameterizedType.getActualTypeArguments()).append('>'); + GT_JOINER.join(builder, parameterizedType.getActualTypeArguments()); } - return builder.toString(); } /** * Creates a parameterized type instance. * - * @param owner the owning type - * @param rawClass the raw class to create a parameterized type instance for + * @param owner the owning type + * @param rawClass the raw class to create a parameterized type instance for * @param typeVariableMap the map used for parameterization * @return {@link ParameterizedType} - * @throws NullPointerException if either {@code rawClass} or {@code typeVariableMap} - * is {@code null} + * @throws NullPointerException if either {@code rawClass} or {@code typeVariableMap} is {@code null} * @since 3.2 */ - public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass, - final Map<TypeVariable<?>, Type> typeVariableMap) { + public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass, final Map<TypeVariable<?>, Type> typeVariableMap) { Objects.requireNonNull(rawClass, "rawClass"); Objects.requireNonNull(typeVariableMap, "typeVariableMap"); - return parameterizeWithOwner(owner, rawClass, - extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters())); + return parameterizeWithOwner(owner, rawClass, extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters())); } /** * Creates a parameterized type instance. * - * @param owner the owning type - * @param rawClass the raw class to create a parameterized type instance for + * @param owner the owning type + * @param rawClass the raw class to create a parameterized type instance for * @param typeArguments the types used for parameterization * * @return {@link ParameterizedType} * @throws NullPointerException if {@code rawClass} is {@code null} * @since 3.2 */ - public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass, - final Type... typeArguments) { + public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass, final Type... typeArguments) { Objects.requireNonNull(rawClass, "rawClass"); final Type useOwner; if (rawClass.getEnclosingClass() == null) { @@ -1658,14 +1573,12 @@ public class TypeUtils { } else if (owner == null) { useOwner = rawClass.getEnclosingClass(); } else { - Validate.isTrue(isAssignable(owner, rawClass.getEnclosingClass()), - "%s is invalid owner type for parameterized %s", owner, rawClass); + Validate.isTrue(isAssignable(owner, rawClass.getEnclosingClass()), "%s is invalid owner type for parameterized %s", owner, rawClass); useOwner = owner; } Validate.noNullElements(typeArguments, "null type argument at index %s"); - Validate.isTrue(rawClass.getTypeParameters().length == typeArguments.length, - "invalid number of type parameters specified: expected %d, got %d", rawClass.getTypeParameters().length, - typeArguments.length); + Validate.isTrue(rawClass.getTypeParameters().length == typeArguments.length, "invalid number of type parameters specified: expected %d, got %d", + rawClass.getTypeParameters().length, typeArguments.length); return new ParameterizedTypeImpl(rawClass, useOwner, typeArguments); } @@ -1673,7 +1586,7 @@ public class TypeUtils { /** * Finds the mapping for {@code type} in {@code typeVarAssigns}. * - * @param type the type to be replaced + * @param type the type to be replaced * @param typeVarAssigns the map with type variables * @return the replaced type * @throws IllegalArgumentException if the type cannot be substituted @@ -1683,8 +1596,7 @@ public class TypeUtils { final Type replacementType = typeVarAssigns.get(type); if (replacementType == null) { - throw new IllegalArgumentException("missing assignment type for type variable " - + type); + throw new IllegalArgumentException("missing assignment type for type variable " + type); } return replacementType; } @@ -1721,7 +1633,7 @@ public class TypeUtils { return buf.append(':').append(typeVariableToString(typeVariable)).toString(); } - private static <T> String toString(final T object) { + private static <T> String anyToString(final T object) { return object instanceof Type ? toString((Type) object) : object.toString(); } @@ -1754,17 +1666,12 @@ public class TypeUtils { } /** - * Determines whether or not specified types satisfy the bounds of their - * mapped type variables. When a type parameter extends another (such as - * {@code <T, S extends T>}), uses another as a type parameter (such as - * {@code <T, S extends Comparable>>}), or otherwise depends on - * another type variable to be specified, the dependencies must be included - * in {@code typeVarAssigns}. + * Determines whether or not specified types satisfy the bounds of their mapped type variables. When a type parameter extends another (such as + * {@code <T, S extends T>}), uses another as a type parameter (such as {@code <T, S extends Comparable>>}), or otherwise depends on another type variable + * to be specified, the dependencies must be included in {@code typeVarAssigns}. * - * @param typeVariableMap specifies the potential types to be assigned to the - * type variables, not {@code null}. - * @return whether or not the types can be assigned to their respective type - * variables. + * @param typeVariableMap specifies the potential types to be assigned to the type variables, not {@code null}. + * @return whether or not the types can be assigned to their respective type variables. * @throws NullPointerException if {@code typeVariableMap} is {@code null} */ public static boolean typesSatisfyVariables(final Map<TypeVariable<?>, Type> typeVariableMap) { @@ -1776,8 +1683,7 @@ public class TypeUtils { final Type type = entry.getValue(); for (final Type bound : getImplicitBounds(typeVar)) { - if (!isAssignable(type, substituteTypeVariables(bound, typeVariableMap), - typeVariableMap)) { + if (!isAssignable(type, substituteTypeVariables(bound, typeVariableMap), typeVariableMap)) { return false; } } @@ -1792,20 +1698,20 @@ public class TypeUtils { * @return String */ private static String typeVariableToString(final TypeVariable<?> typeVariable) { - final StringBuilder buf = new StringBuilder(typeVariable.getName()); + final StringBuilder builder = new StringBuilder(typeVariable.getName()); final Type[] bounds = typeVariable.getBounds(); if (bounds.length > 0 && !(bounds.length == 1 && Object.class.equals(bounds[0]))) { - buf.append(" extends "); - appendAllTo(buf, " & ", typeVariable.getBounds()); + builder.append(" extends "); + AMP_JOINER.join(builder, typeVariable.getBounds()); } - return buf.toString(); + return builder.toString(); } /** * Unrolls variables in a type bounds array. * * @param typeArguments assignments {@link Map} - * @param bounds in which to expand variables + * @param bounds in which to expand variables * @return {@code bounds} with any variables reassigned */ private static Type[] unrollBounds(final Map<TypeVariable<?>, Type> typeArguments, final Type[] bounds) { @@ -1823,10 +1729,9 @@ public class TypeUtils { } /** - * Looks up {@code typeVariable} in {@code typeVarAssigns} <em>transitively</em>, i.e. keep looking until the value - * found is <em>not</em> a type variable. + * Looks up {@code typeVariable} in {@code typeVarAssigns} <em>transitively</em>, i.e. keep looking until the value found is <em>not</em> a type variable. * - * @param typeVariable the type variable to look up + * @param typeVariable the type variable to look up * @param typeVarAssigns the map used for the look-up * @return Type or {@code null} if some variable was not in the map */ @@ -1846,7 +1751,7 @@ public class TypeUtils { * Gets a type representing {@code type} with variable assignments "unrolled." * * @param typeArguments as from {@link TypeUtils#getTypeArguments(Type, Class)} - * @param type the type to unroll variable assignments for + * @param type the type to unroll variable assignments for * @return Type * @since 3.2 */ @@ -1879,7 +1784,7 @@ public class TypeUtils { if (type instanceof WildcardType) { final WildcardType wild = (WildcardType) type; return wildcardType().withUpperBounds(unrollBounds(typeArguments, wild.getUpperBounds())) - .withLowerBounds(unrollBounds(typeArguments, wild.getLowerBounds())).build(); + .withLowerBounds(unrollBounds(typeArguments, wild.getLowerBounds())).build(); } } return type; @@ -1902,21 +1807,21 @@ public class TypeUtils { * @return String */ private static String wildcardTypeToString(final WildcardType wildcardType) { - final StringBuilder buf = new StringBuilder().append('?'); + final StringBuilder builder = new StringBuilder().append('?'); final Type[] lowerBounds = wildcardType.getLowerBounds(); final Type[] upperBounds = wildcardType.getUpperBounds(); if (lowerBounds.length > 1 || lowerBounds.length == 1 && lowerBounds[0] != null) { - appendAllTo(buf.append(" super "), " & ", lowerBounds); + AMP_JOINER.join(builder.append(" super "), lowerBounds); } else if (upperBounds.length > 1 || upperBounds.length == 1 && !Object.class.equals(upperBounds[0])) { - appendAllTo(buf.append(" extends "), " & ", upperBounds); + AMP_JOINER.join(builder.append(" extends "), upperBounds); } - return buf.toString(); + return builder.toString(); } /** * Wraps the specified {@link Class} in a {@link Typed} wrapper. * - * @param <T> generic type + * @param <T> generic type * @param type to wrap * @return Typed<T> * @since 3.2 @@ -1928,7 +1833,7 @@ public class TypeUtils { /** * Wraps the specified {@link Type} in a {@link Typed} wrapper. * - * @param <T> inferred generic type + * @param <T> inferred generic type * @param type to wrap * @return Typed<T> * @since 3.2 @@ -1938,12 +1843,10 @@ public class TypeUtils { } /** - * {@link TypeUtils} instances should NOT be constructed in standard - * programming. Instead, the class should be used as + * {@link TypeUtils} instances should NOT be constructed in standard programming. Instead, the class should be used as * {@code TypeUtils.isAssignable(cls, toClass)}. * <p> - * This constructor is public to permit tools that require a JavaBean instance - * to operate. + * This constructor is public to permit tools that require a JavaBean instance to operate. * </p> * * @deprecated TODO Make private in 4.0. diff --git a/src/test/java/org/apache/commons/lang3/AppendableJoinerTest.java b/src/test/java/org/apache/commons/lang3/AppendableJoinerTest.java new file mode 100644 index 000000000..cb1fa3574 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/AppendableJoinerTest.java @@ -0,0 +1,142 @@ +/* + * 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.commons.lang3; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Objects; + +import org.apache.commons.lang3.AppendableJoiner.Builder; +import org.apache.commons.lang3.text.StrBuilder; +import org.apache.commons.text.TextStringBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Tests {@link AppendableJoiner}. + */ +public class AppendableJoinerTest { + + static class Fixture { + + private final String name; + + Fixture(final String name) { + this.name = name; + } + + /** + * Renders myself onto an Appendable to avoid creating intermediary strings. + */ + void render(final Appendable appendable) throws IOException { + appendable.append(name); + appendable.append('!'); + } + } + + @Test + public void testAllBuilderPropertiesStringBuilder() { + // @formatter:off + final AppendableJoiner<Object> joiner = AppendableJoiner.builder() + .setPrefix("<") + .setDelimiter(".") + .setSuffix(">") + .setElementAppender((a, e) -> a.append(String.valueOf(e))) + .get(); + // @formatter:on + final StringBuilder sbuilder = new StringBuilder("A"); + assertEquals("A<B.C>", joiner.join(sbuilder, "B", "C").toString()); + sbuilder.append("1"); + assertEquals("A<B.C>1<D.E>", joiner.join(sbuilder, Arrays.asList("D", "E")).toString()); + } + + @Test + public void testBuildDefaultStringBuilder() { + final Builder<Object> builder = AppendableJoiner.builder(); + assertNotSame(builder.get(), builder.get()); + final AppendableJoiner<Object> joiner = builder.get(); + final StringBuilder sbuilder = new StringBuilder("A"); + assertEquals("ABC", joiner.join(sbuilder, "B", "C").toString()); + sbuilder.append("1"); + assertEquals("ABC1DE", joiner.join(sbuilder, "D", "E").toString()); + } + + @Test + public void testBuilder() { + assertNotSame(AppendableJoiner.builder(), AppendableJoiner.builder()); + } + + @SuppressWarnings("deprecation") // Test own StrBuilder + @ParameterizedTest + @ValueSource(classes = { StringBuilder.class, StringBuffer.class, StringWriter.class, StrBuilder.class, TextStringBuilder.class }) + public void testDelimiterAppendable(final Class<? extends Appendable> clazz) throws Exception { + final AppendableJoiner<Object> joiner = AppendableJoiner.builder().setDelimiter(".").get(); + final Appendable sbuilder = clazz.newInstance(); + sbuilder.append("A"); + // throws IOException + assertEquals("AB.C", joiner.joinA(sbuilder, "B", "C").toString()); + sbuilder.append("1"); + // throws IOException + assertEquals("AB.C1D.E", joiner.joinA(sbuilder, Arrays.asList("D", "E")).toString()); + } + + @Test + public void testDelimiterStringBuilder() { + final AppendableJoiner<Object> joiner = AppendableJoiner.builder().setDelimiter(".").get(); + final StringBuilder sbuilder = new StringBuilder("A"); + // does not throw IOException + assertEquals("AB.C", joiner.join(sbuilder, "B", "C").toString()); + sbuilder.append("1"); + // does not throw IOException + assertEquals("AB.C1D.E", joiner.join(sbuilder, Arrays.asList("D", "E")).toString()); + } + + @Test + public void testToCharSequenceStringBuilder1() { + // @formatter:off + final AppendableJoiner<Object> joiner = AppendableJoiner.builder() + .setPrefix("<") + .setDelimiter(".") + .setSuffix(">") + .setElementAppender((a, e) -> a.append("|").append(Objects.toString(e))) + .get(); + // @formatter:on + final StringBuilder sbuilder = new StringBuilder("A"); + assertEquals("A<|B.|C>", joiner.join(sbuilder, "B", "C").toString()); + sbuilder.append("1"); + assertEquals("A<|B.|C>1<|D.|E>", joiner.join(sbuilder, Arrays.asList("D", "E")).toString()); + } + + @Test + public void testToCharSequenceStringBuilder2() { + // @formatter:off + final AppendableJoiner<Fixture> joiner = AppendableJoiner.<Fixture>builder() + .setElementAppender((a, e) -> e.render(a)) + .get(); + // @formatter:on + final StringBuilder sbuilder = new StringBuilder("["); + assertEquals("[B!C!", joiner.join(sbuilder, new Fixture("B"), new Fixture("C")).toString()); + sbuilder.append("]"); + assertEquals("[B!C!]D!E!", joiner.join(sbuilder, Arrays.asList(new Fixture("D"), new Fixture("E"))).toString()); + } +}