| /* |
| * 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.beam.sdk.util.common; |
| |
| import static java.util.Arrays.asList; |
| import static org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkArgument; |
| import static org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkNotNull; |
| |
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.GenericArrayType; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| import java.lang.reflect.WildcardType; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.Queue; |
| import java.util.ServiceLoader; |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Function; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Joiner; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.FluentIterable; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableSet; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableSortedSet; |
| import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Queues; |
| |
| /** Utilities for working with with {@link Class Classes} and {@link Method Methods}. */ |
| public class ReflectHelpers { |
| |
| private static final Joiner COMMA_SEPARATOR = Joiner.on(", "); |
| |
| /** A {@link Function} that turns a method into a simple method signature. */ |
| public static final Function<Method, String> METHOD_FORMATTER = |
| new Function<Method, String>() { |
| @SuppressFBWarnings( |
| value = "NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION", |
| justification = "https://github.com/google/guava/issues/920") |
| @Override |
| public String apply(@Nonnull Method input) { |
| String parameterTypes = |
| FluentIterable.from(asList(input.getParameterTypes())) |
| .transform(CLASS_SIMPLE_NAME) |
| .join(COMMA_SEPARATOR); |
| return String.format("%s(%s)", input.getName(), parameterTypes); |
| } |
| }; |
| |
| /** A {@link Function} that turns a method into the declaring class + method signature. */ |
| public static final Function<Method, String> CLASS_AND_METHOD_FORMATTER = |
| new Function<Method, String>() { |
| @SuppressFBWarnings( |
| value = "NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION", |
| justification = "https://github.com/google/guava/issues/920") |
| @Override |
| public String apply(@Nonnull Method input) { |
| return String.format( |
| "%s#%s", CLASS_NAME.apply(input.getDeclaringClass()), METHOD_FORMATTER.apply(input)); |
| } |
| }; |
| |
| /** A {@link Function} with returns the classes name. */ |
| public static final Function<Class<?>, String> CLASS_NAME = Class::getName; |
| |
| /** A {@link Function} with returns the classes name. */ |
| public static final Function<Class<?>, String> CLASS_SIMPLE_NAME = Class::getSimpleName; |
| |
| /** A {@link Function} that returns a concise string for a {@link Annotation}. */ |
| public static final Function<Annotation, String> ANNOTATION_FORMATTER = |
| annotation -> { |
| String annotationName = annotation.annotationType().getName(); |
| String annotationNameWithoutPackage = |
| annotationName.substring(annotationName.lastIndexOf('.') + 1).replace('$', '.'); |
| String annotationToString = annotation.toString(); |
| String values = annotationToString.substring(annotationToString.indexOf('(')); |
| return String.format("%s%s", annotationNameWithoutPackage, values); |
| }; |
| |
| /** A {@link Function} that formats types. */ |
| public static final Function<Type, String> TYPE_SIMPLE_DESCRIPTION = |
| new Function<Type, String>() { |
| @SuppressFBWarnings( |
| value = "NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION", |
| justification = "https://github.com/google/guava/issues/920") |
| @Override |
| @Nullable |
| public String apply(@Nonnull Type input) { |
| StringBuilder builder = new StringBuilder(); |
| format(builder, input); |
| return builder.toString(); |
| } |
| |
| private void format(StringBuilder builder, Type t) { |
| if (t instanceof Class) { |
| formatClass(builder, (Class<?>) t); |
| } else if (t instanceof TypeVariable) { |
| formatTypeVariable(builder, (TypeVariable<?>) t); |
| } else if (t instanceof WildcardType) { |
| formatWildcardType(builder, (WildcardType) t); |
| } else if (t instanceof ParameterizedType) { |
| formatParameterizedType(builder, (ParameterizedType) t); |
| } else if (t instanceof GenericArrayType) { |
| formatGenericArrayType(builder, (GenericArrayType) t); |
| } else { |
| builder.append(t.toString()); |
| } |
| } |
| |
| private void formatClass(StringBuilder builder, Class<?> clazz) { |
| builder.append(clazz.getSimpleName()); |
| } |
| |
| private void formatTypeVariable(StringBuilder builder, TypeVariable<?> t) { |
| builder.append(t.getName()); |
| } |
| |
| private void formatWildcardType(StringBuilder builder, WildcardType t) { |
| builder.append("?"); |
| for (Type lowerBound : t.getLowerBounds()) { |
| builder.append(" super "); |
| format(builder, lowerBound); |
| } |
| for (Type upperBound : t.getUpperBounds()) { |
| if (!Object.class.equals(upperBound)) { |
| builder.append(" extends "); |
| format(builder, upperBound); |
| } |
| } |
| } |
| |
| private void formatParameterizedType(StringBuilder builder, ParameterizedType t) { |
| if (t.getOwnerType() != null) { |
| format(builder, t.getOwnerType()); |
| builder.append('.'); |
| } |
| format(builder, t.getRawType()); |
| if (t.getActualTypeArguments().length > 0) { |
| builder.append('<'); |
| COMMA_SEPARATOR.appendTo( |
| builder, |
| FluentIterable.from(asList(t.getActualTypeArguments())) |
| .transform(TYPE_SIMPLE_DESCRIPTION)); |
| builder.append('>'); |
| } |
| } |
| |
| private void formatGenericArrayType(StringBuilder builder, GenericArrayType t) { |
| format(builder, t.getGenericComponentType()); |
| builder.append("[]"); |
| } |
| }; |
| |
| /** A {@link Comparator} that uses the object's class' canonical name to compare them. */ |
| public static class ObjectsClassComparator implements Comparator<Object> { |
| public static final ObjectsClassComparator INSTANCE = new ObjectsClassComparator(); |
| |
| @Override |
| public int compare(Object o1, Object o2) { |
| return o1.getClass().getCanonicalName().compareTo(o2.getClass().getCanonicalName()); |
| } |
| } |
| |
| /** |
| * Returns all the methods visible from the provided interfaces. |
| * |
| * @param interfaces The interfaces to use when searching for all their methods. |
| * @return An iterable of {@link Method}s which interfaces expose. |
| */ |
| public static Iterable<Method> getClosureOfMethodsOnInterfaces( |
| Iterable<? extends Class<?>> interfaces) { |
| return FluentIterable.from(interfaces) |
| .transformAndConcat(ReflectHelpers::getClosureOfMethodsOnInterface); |
| } |
| |
| /** |
| * Returns all the methods visible from {@code iface}. |
| * |
| * @param iface The interface to use when searching for all its methods. |
| * @return An iterable of {@link Method}s which {@code iface} exposes. |
| */ |
| public static Iterable<Method> getClosureOfMethodsOnInterface(Class<?> iface) { |
| checkNotNull(iface); |
| checkArgument(iface.isInterface()); |
| ImmutableSet.Builder<Method> builder = ImmutableSet.builder(); |
| Queue<Class<?>> interfacesToProcess = Queues.newArrayDeque(); |
| interfacesToProcess.add(iface); |
| while (!interfacesToProcess.isEmpty()) { |
| Class<?> current = interfacesToProcess.remove(); |
| builder.add(current.getMethods()); |
| interfacesToProcess.addAll(Arrays.asList(current.getInterfaces())); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Returns instances of all implementations of the the specified {@code iface}. Instances are |
| * sorted by their class' name to ensure deterministic execution. |
| * |
| * @param iface The interface to load implementations of |
| * @param classLoader The class loader to use |
| * @param <T> The type of {@code iface} |
| * @return An iterable of instances of T, ordered by their class' canonical name |
| */ |
| public static <T> Iterable<T> loadServicesOrdered(Class<T> iface, ClassLoader classLoader) { |
| ServiceLoader<T> loader = ServiceLoader.load(iface, classLoader); |
| ImmutableSortedSet.Builder<T> builder = |
| new ImmutableSortedSet.Builder<>(ObjectsClassComparator.INSTANCE); |
| builder.addAll(loader); |
| return builder.build(); |
| } |
| |
| /** |
| * A version of {@code loadServicesOrdered} that uses a default class loader. |
| * |
| * @param iface The interface to load implementations of |
| * @param <T> The type of {@code iface} |
| * @return An iterable of instances of T, ordered by their class' canonical name |
| */ |
| public static <T> Iterable<T> loadServicesOrdered(Class<T> iface) { |
| return loadServicesOrdered(iface, ReflectHelpers.findClassLoader()); |
| } |
| |
| /** |
| * Finds the appropriate {@code ClassLoader} to be used by the {@link ServiceLoader#load} call, |
| * which by default would use the proposed {@code ClassLoader}, which can be null. The fallback is |
| * as follows: context ClassLoader, class ClassLoader and finaly the system ClassLoader. |
| */ |
| public static ClassLoader findClassLoader(final ClassLoader proposed) { |
| ClassLoader classLoader = proposed; |
| if (classLoader == null) { |
| classLoader = ReflectHelpers.class.getClassLoader(); |
| } |
| if (classLoader == null) { |
| classLoader = ClassLoader.getSystemClassLoader(); |
| } |
| return classLoader; |
| } |
| |
| /** Find the common classloader of all these classes. */ |
| public static ClassLoader findClassLoader(final Class<?>... classes) { |
| if (classes == null || classes.length == 0) { |
| throw new IllegalArgumentException("set of classes can't be null"); |
| } |
| ClassLoader current = null; |
| for (final Class<?> clazz : classes) { |
| final ClassLoader proposed = clazz.getClassLoader(); |
| if (proposed == null) { |
| continue; |
| } |
| if (current == null) { |
| current = proposed; |
| } else if (proposed != current && isParent(current, proposed)) { |
| current = proposed; |
| } |
| } |
| return current == null ? ClassLoader.getSystemClassLoader() : current; |
| } |
| |
| /** |
| * Finds the appropriate {@code ClassLoader} to be used by the {@link ServiceLoader#load} call, |
| * which by default would use the context {@code ClassLoader}, which can be null. The fallback is |
| * as follows: context ClassLoader, class ClassLoader and finaly the system ClassLoader. |
| */ |
| public static ClassLoader findClassLoader() { |
| return findClassLoader(Thread.currentThread().getContextClassLoader()); |
| } |
| |
| /** |
| * Checks if current is a parent of proposed. |
| * |
| * @param current the potential parent. |
| * @param proposed the potential child. |
| * @return true if current is in proposed hierarchy. |
| */ |
| private static boolean isParent(final ClassLoader current, final ClassLoader proposed) { |
| final Collection<ClassLoader> visited = new ArrayList<>(); |
| ClassLoader it = proposed.getParent(); |
| while (it != null) { |
| if (it == current) { |
| return true; |
| } |
| if (visited.contains(it)) { // avoid loops |
| return false; |
| } |
| visited.add(it); |
| it = it.getParent(); |
| } |
| return false; |
| } |
| } |