blob: 7add6e34ec2123766a833e75c20e1527f97c4886 [file] [log] [blame]
/*
* 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;
import static org.hamcrest.Matchers.anyOf;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
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.base.Predicate;
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.ImmutableList;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMap;
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.Lists;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Maps;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Multimap;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Multimaps;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Ordering;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Sets;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.reflect.Invokable;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.reflect.Parameter;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.reflect.TypeToken;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents the API surface of a package prefix. Used for accessing public classes, methods, and
* the types they reference, to control what dependencies are re-exported.
*
* <p>For the purposes of calculating the public API surface, exposure includes any public or
* protected occurrence of:
*
* <ul>
* <li>superclasses
* <li>interfaces implemented
* <li>actual type arguments to generic types
* <li>array component types
* <li>method return types
* <li>method parameter types
* <li>type variable bounds
* <li>wildcard bounds
* </ul>
*
* <p>Exposure is a transitive property. The resulting map excludes primitives and array classes
* themselves.
*
* <p>It is prudent (though not required) to prune prefixes like "java" via the builder method
* {@link #pruningPrefix} to halt the traversal so it does not uselessly catalog references that are
* not interesting.
*/
@SuppressWarnings("rawtypes")
public class ApiSurface {
private static final Logger LOG = LoggerFactory.getLogger(ApiSurface.class);
/** A factory method to create a {@link Class} matcher for classes residing in a given package. */
public static Matcher<Class<?>> classesInPackage(final String packageName) {
return new Matchers.ClassInPackage(packageName);
}
/**
* A factory method to create an {@link ApiSurface} matcher, producing a positive match if the
* queried api surface contains ONLY classes described by the provided matchers.
*/
public static Matcher<ApiSurface> containsOnlyClassesMatching(
final Set<Matcher<Class<?>>> classMatchers) {
return new Matchers.ClassesInSurfaceMatcher(classMatchers);
}
/** See {@link ApiSurface#containsOnlyClassesMatching(Set)}. */
@SafeVarargs
public static Matcher<ApiSurface> containsOnlyClassesMatching(
final Matcher<Class<?>>... classMatchers) {
return new Matchers.ClassesInSurfaceMatcher(Sets.newHashSet(classMatchers));
}
/** See {@link ApiSurface#containsOnlyPackages(Set)}. */
public static Matcher<ApiSurface> containsOnlyPackages(final String... packageNames) {
return containsOnlyPackages(Sets.newHashSet(packageNames));
}
/**
* A factory method to create an {@link ApiSurface} matcher, producing a positive match if the
* queried api surface contains classes ONLY from specified package names.
*/
public static Matcher<ApiSurface> containsOnlyPackages(final Set<String> packageNames) {
final Function<String, Matcher<Class<?>>> packageNameToClassMatcher =
ApiSurface::classesInPackage;
final ImmutableSet<Matcher<Class<?>>> classesInPackages =
FluentIterable.from(packageNames).transform(packageNameToClassMatcher).toSet();
return containsOnlyClassesMatching(classesInPackages);
}
/**
* {@link Matcher}s for use in {@link ApiSurface} related tests that aim to keep the public API
* conformant to a hard-coded policy by controlling what classes are allowed to be exposed by an
* API surface.
*/
// based on previous code by @kennknowles and others.
private static class Matchers {
private static class ClassInPackage extends TypeSafeDiagnosingMatcher<Class<?>> {
private final String packageName;
private ClassInPackage(final String packageName) {
this.packageName = packageName;
}
@Override
public void describeTo(final Description description) {
description.appendText("Classes in package \"");
description.appendText(packageName);
description.appendText("\"");
}
@Override
protected boolean matchesSafely(final Class<?> clazz, final Description mismatchDescription) {
return clazz.getName().startsWith(packageName + ".");
}
}
private static class ClassesInSurfaceMatcher extends TypeSafeDiagnosingMatcher<ApiSurface> {
private final Set<Matcher<Class<?>>> classMatchers;
private ClassesInSurfaceMatcher(final Set<Matcher<Class<?>>> classMatchers) {
this.classMatchers = classMatchers;
}
private boolean verifyNoAbandoned(
final ApiSurface checkedApiSurface,
final Set<Matcher<Class<?>>> allowedClasses,
final Description mismatchDescription) {
// <helper_lambdas>
final Function<Matcher<Class<?>>, String> toMessage =
abandonedClassMacther -> {
final StringDescription description = new StringDescription();
description.appendText("No ");
abandonedClassMacther.describeTo(description);
return description.toString();
};
final Predicate<Matcher<Class<?>>> matchedByExposedClasses =
classMatcher ->
FluentIterable.from(checkedApiSurface.getExposedClasses())
.anyMatch(classMatcher::matches);
// </helper_lambdas>
final ImmutableSet<Matcher<Class<?>>> matchedClassMatchers =
FluentIterable.from(allowedClasses).filter(matchedByExposedClasses).toSet();
final Sets.SetView<Matcher<Class<?>>> abandonedClassMatchers =
Sets.difference(allowedClasses, matchedClassMatchers);
final ImmutableList<String> messages =
FluentIterable.from(abandonedClassMatchers)
.transform(toMessage)
.toSortedList(Ordering.natural());
if (!messages.isEmpty()) {
mismatchDescription.appendText(
"The following white-listed scopes did not have matching classes on the API surface:"
+ "\n\t"
+ Joiner.on("\n\t").join(messages));
}
return messages.isEmpty();
}
private boolean verifyNoDisallowed(
final ApiSurface checkedApiSurface,
final Set<Matcher<Class<?>>> allowedClasses,
final Description mismatchDescription) {
/* <helper_lambdas> */
final Function<Class<?>, List<Class<?>>> toExposure = checkedApiSurface::getAnyExposurePath;
final Maps.EntryTransformer<Class<?>, List<Class<?>>, String> toMessage =
(aClass, exposure) ->
aClass + " exposed via:\n\t\t" + Joiner.on("\n\t\t").join(exposure);
final Predicate<Class<?>> disallowed = aClass -> !classIsAllowed(aClass, allowedClasses);
/* </helper_lambdas> */
final FluentIterable<Class<?>> disallowedClasses =
FluentIterable.from(checkedApiSurface.getExposedClasses()).filter(disallowed);
final ImmutableMap<Class<?>, List<Class<?>>> exposures =
Maps.toMap(disallowedClasses, toExposure);
final ImmutableList<String> messages =
FluentIterable.from(Maps.transformEntries(exposures, toMessage).values())
.toSortedList(Ordering.natural());
if (!messages.isEmpty()) {
mismatchDescription.appendText(
"The following disallowed classes appeared on the API surface:\n\t"
+ Joiner.on("\n\t").join(messages));
}
return messages.isEmpty();
}
@SuppressWarnings({"rawtypes", "unchecked"})
private boolean classIsAllowed(
final Class<?> clazz, final Set<Matcher<Class<?>>> allowedClasses) {
// Safe cast inexpressible in Java without rawtypes
return anyOf((Iterable) allowedClasses).matches(clazz);
}
@Override
protected boolean matchesSafely(
final ApiSurface apiSurface, final Description mismatchDescription) {
final boolean noDisallowed =
verifyNoDisallowed(apiSurface, classMatchers, mismatchDescription);
final boolean noAbandoned =
verifyNoAbandoned(apiSurface, classMatchers, mismatchDescription);
return noDisallowed && noAbandoned;
}
@Override
public void describeTo(final Description description) {
description.appendText("API surface to include only:" + "\n\t");
for (final Matcher<Class<?>> classMatcher : classMatchers) {
classMatcher.describeTo(description);
description.appendText("\n\t");
}
}
}
}
///////////////
/** Returns an empty {@link ApiSurface}. */
public static ApiSurface empty() {
LOG.debug("Returning an empty ApiSurface");
return new ApiSurface(Collections.emptySet(), Collections.emptySet());
}
/** Returns an {@link ApiSurface} object representing the given package and all subpackages. */
public static ApiSurface ofPackage(String packageName, ClassLoader classLoader)
throws IOException {
return ApiSurface.empty().includingPackage(packageName, classLoader);
}
/** Returns an {@link ApiSurface} object representing the given package and all subpackages. */
public static ApiSurface ofPackage(Package aPackage, ClassLoader classLoader) throws IOException {
return ofPackage(aPackage.getName(), classLoader);
}
/** Returns an {@link ApiSurface} object representing just the surface of the given class. */
public static ApiSurface ofClass(Class<?> clazz) {
return ApiSurface.empty().includingClass(clazz);
}
/**
* Returns an {@link ApiSurface} like this one, but also including the named package and all of
* its subpackages.
*/
public ApiSurface includingPackage(String packageName, ClassLoader classLoader)
throws IOException {
ClassPath classPath = ClassPath.from(classLoader);
Set<Class<?>> newRootClasses = Sets.newHashSet();
for (ClassPath.ClassInfo classInfo : classPath.getTopLevelClassesRecursive(packageName)) {
Class clazz = null;
try {
clazz = classInfo.load();
} catch (NoClassDefFoundError e) {
// TODO: Ignore any NoClassDefFoundError errors as a workaround. (BEAM-2231)
LOG.warn("Failed to load class: {}", classInfo.toString(), e);
continue;
}
if (exposed(clazz.getModifiers())) {
newRootClasses.add(clazz);
}
}
LOG.debug("Including package {} and subpackages: {}", packageName, newRootClasses);
newRootClasses.addAll(rootClasses);
return new ApiSurface(newRootClasses, patternsToPrune);
}
/** Returns an {@link ApiSurface} like this one, but also including the given class. */
public ApiSurface includingClass(Class<?> clazz) {
Set<Class<?>> newRootClasses = Sets.newHashSet();
LOG.debug("Including class {}", clazz);
newRootClasses.add(clazz);
newRootClasses.addAll(rootClasses);
return new ApiSurface(newRootClasses, patternsToPrune);
}
/**
* Returns an {@link ApiSurface} like this one, but pruning transitive references from classes
* whose full name (including package) begins with the provided prefix.
*/
public ApiSurface pruningPrefix(String prefix) {
return pruningPattern(Pattern.compile(Pattern.quote(prefix) + ".*"));
}
/** Returns an {@link ApiSurface} like this one, but pruning references from the named class. */
public ApiSurface pruningClassName(String className) {
return pruningPattern(Pattern.compile(Pattern.quote(className)));
}
/**
* Returns an {@link ApiSurface} like this one, but pruning references from the provided class.
*/
public ApiSurface pruningClass(Class<?> clazz) {
return pruningClassName(clazz.getName());
}
/**
* Returns an {@link ApiSurface} like this one, but pruning transitive references from classes
* whose full name (including package) begins with the provided prefix.
*/
public ApiSurface pruningPattern(Pattern pattern) {
Set<Pattern> newPatterns = Sets.newHashSet();
newPatterns.addAll(patternsToPrune);
newPatterns.add(pattern);
return new ApiSurface(rootClasses, newPatterns);
}
/** See {@link #pruningPattern(Pattern)}. */
public ApiSurface pruningPattern(String patternString) {
return pruningPattern(Pattern.compile(patternString));
}
/** Returns all public classes originally belonging to the package in the {@link ApiSurface}. */
public Set<Class<?>> getRootClasses() {
return rootClasses;
}
/** Returns exposed types in this set, including arrays and primitives as specified. */
public Set<Class<?>> getExposedClasses() {
return getExposedToExposers().keySet();
}
/**
* Returns a path from an exposed class to a root class. There may be many, but this gives only
* one.
*
* <p>If there are only cycles, with no path back to a root class, throws IllegalStateException.
*/
public List<Class<?>> getAnyExposurePath(Class<?> exposedClass) {
Set<Class<?>> excluded = Sets.newHashSet();
excluded.add(exposedClass);
List<Class<?>> path = getAnyExposurePath(exposedClass, excluded);
if (path == null) {
throw new IllegalArgumentException(
"Class "
+ exposedClass
+ " has no path back to any root class."
+ " It should never have been considered exposed.");
} else {
return path;
}
}
/**
* Returns a path from an exposed class to a root class. There may be many, but this gives only
* one. It will not return a path that crosses the excluded classes.
*
* <p>If there are only cycles or paths through the excluded classes, returns null.
*
* <p>If the class is not actually in the exposure map, throws IllegalArgumentException
*/
private List<Class<?>> getAnyExposurePath(Class<?> exposedClass, Set<Class<?>> excluded) {
List<Class<?>> exposurePath = Lists.newArrayList();
exposurePath.add(exposedClass);
Collection<Class<?>> exposers = getExposedToExposers().get(exposedClass);
if (exposers.isEmpty()) {
throw new IllegalArgumentException("Class " + exposedClass + " is not exposed.");
}
for (Class<?> exposer : exposers) {
if (excluded.contains(exposer)) {
continue;
}
// A null exposer means this is already a root class.
if (exposer == null) {
return exposurePath;
}
List<Class<?>> restOfPath =
getAnyExposurePath(exposer, Sets.union(excluded, Sets.newHashSet(exposer)));
if (restOfPath != null) {
exposurePath.addAll(restOfPath);
return exposurePath;
}
}
return null;
}
////////////////////////////////////////////////////////////////////
// Fields initialized upon construction
private final Set<Class<?>> rootClasses;
private final Set<Pattern> patternsToPrune;
// Fields computed on-demand
private Multimap<Class<?>, Class<?>> exposedToExposers = null;
private Pattern prunedPattern = null;
private Set<Type> visited = null;
private ApiSurface(Set<Class<?>> rootClasses, Set<Pattern> patternsToPrune) {
this.rootClasses = rootClasses;
this.patternsToPrune = patternsToPrune;
}
/**
* A map from exposed types to place where they are exposed, in the sense of being a part of a
* public-facing API surface.
*
* <p>This map is the adjencency list representation of a directed graph, where an edge from type
* {@code T1} to type {@code T2} indicates that {@code T2} directly exposes {@code T1} in its API
* surface.
*
* <p>The traversal methods in this class are designed to avoid repeatedly processing types, since
* there will almost always be cyclic references.
*/
private Multimap<Class<?>, Class<?>> getExposedToExposers() {
if (exposedToExposers == null) {
constructExposedToExposers();
}
return exposedToExposers;
}
/** See {@link #getExposedToExposers}. */
private void constructExposedToExposers() {
visited = Sets.newHashSet();
exposedToExposers =
Multimaps.newSetMultimap(
Maps.<Class<?>, Collection<Class<?>>>newHashMap(), Sets::newHashSet);
for (Class<?> clazz : rootClasses) {
addExposedTypes(clazz, null);
}
}
/** A combined {@code Pattern} that implements all the pruning specified. */
private Pattern getPrunedPattern() {
if (prunedPattern == null) {
constructPrunedPattern();
}
return prunedPattern;
}
/** See {@link #getPrunedPattern}. */
private void constructPrunedPattern() {
Set<String> prunedPatternStrings = Sets.newHashSet();
for (Pattern patternToPrune : patternsToPrune) {
prunedPatternStrings.add(patternToPrune.pattern());
}
prunedPattern = Pattern.compile("(" + Joiner.on(")|(").join(prunedPatternStrings) + ")");
}
/** Whether a type and all that it references should be pruned from the graph. */
private boolean pruned(Type type) {
return pruned(TypeToken.of(type).getRawType());
}
/** Whether a class and all that it references should be pruned from the graph. */
private boolean pruned(Class<?> clazz) {
return clazz.isPrimitive()
|| clazz.isArray()
|| getPrunedPattern().matcher(clazz.getName()).matches();
}
/** Whether a type has already beens sufficiently processed. */
private boolean done(Type type) {
return visited.contains(type);
}
private void recordExposure(Class<?> exposed, Class<?> cause) {
exposedToExposers.put(exposed, cause);
}
private void recordExposure(Type exposed, Class<?> cause) {
exposedToExposers.put(TypeToken.of(exposed).getRawType(), cause);
}
private void visit(Type type) {
visited.add(type);
}
/** See {@link #addExposedTypes(Type, Class)}. */
private void addExposedTypes(TypeToken type, Class<?> cause) {
LOG.debug(
"Adding exposed types from {}, which is the type in type token {}", type.getType(), type);
addExposedTypes(type.getType(), cause);
}
/**
* Adds any references learned by following a link from {@code cause} to {@code type}. This will
* dispatch according to the concrete {@code Type} implementation. See the other overloads of
* {@code addExposedTypes} for their details.
*/
private void addExposedTypes(Type type, Class<?> cause) {
if (type instanceof TypeVariable) {
LOG.debug("Adding exposed types from {}, which is a type variable", type);
addExposedTypes((TypeVariable) type, cause);
} else if (type instanceof WildcardType) {
LOG.debug("Adding exposed types from {}, which is a wildcard type", type);
addExposedTypes((WildcardType) type, cause);
} else if (type instanceof GenericArrayType) {
LOG.debug("Adding exposed types from {}, which is a generic array type", type);
addExposedTypes((GenericArrayType) type, cause);
} else if (type instanceof ParameterizedType) {
LOG.debug("Adding exposed types from {}, which is a parameterized type", type);
addExposedTypes((ParameterizedType) type, cause);
} else if (type instanceof Class) {
LOG.debug("Adding exposed types from {}, which is a class", type);
addExposedTypes((Class) type, cause);
} else {
throw new IllegalArgumentException("Unknown implementation of Type");
}
}
/**
* Adds any types exposed to this set. These will come from the (possibly absent) bounds on the
* type variable.
*/
private void addExposedTypes(TypeVariable type, Class<?> cause) {
if (done(type)) {
return;
}
visit(type);
for (Type bound : type.getBounds()) {
LOG.debug("Adding exposed types from {}, which is a type bound on {}", bound, type);
addExposedTypes(bound, cause);
}
}
/**
* Adds any types exposed to this set. These will come from the (possibly absent) bounds on the
* wildcard.
*/
private void addExposedTypes(WildcardType type, Class<?> cause) {
visit(type);
for (Type lowerBound : type.getLowerBounds()) {
LOG.debug(
"Adding exposed types from {}, which is a type lower bound on wildcard type {}",
lowerBound,
type);
addExposedTypes(lowerBound, cause);
}
for (Type upperBound : type.getUpperBounds()) {
LOG.debug(
"Adding exposed types from {}, which is a type upper bound on wildcard type {}",
upperBound,
type);
addExposedTypes(upperBound, cause);
}
}
/**
* Adds any types exposed from the given array type. The array type itself is not added. The cause
* of the exposure of the underlying type is considered whatever type exposed the array type.
*/
private void addExposedTypes(GenericArrayType type, Class<?> cause) {
if (done(type)) {
return;
}
visit(type);
LOG.debug(
"Adding exposed types from {}, which is the component type on generic array type {}",
type.getGenericComponentType(),
type);
addExposedTypes(type.getGenericComponentType(), cause);
}
/**
* Adds any types exposed to this set. Even if the root type is to be pruned, the actual type
* arguments are processed.
*/
private void addExposedTypes(ParameterizedType type, Class<?> cause) {
// Even if the type is already done, this link to it may be new
boolean alreadyDone = done(type);
if (!pruned(type)) {
visit(type);
recordExposure(type, cause);
}
if (alreadyDone) {
return;
}
// For a parameterized type, pruning does not take place
// here, only for the raw class.
// The type parameters themselves may not be pruned,
// for example with List<MyApiType> probably the
// standard List is pruned, but MyApiType is not.
LOG.debug(
"Adding exposed types from {}, which is the raw type on parameterized type {}",
type.getRawType(),
type);
addExposedTypes(type.getRawType(), cause);
for (Type typeArg : type.getActualTypeArguments()) {
LOG.debug(
"Adding exposed types from {}, which is a type argument on parameterized type {}",
typeArg,
type);
addExposedTypes(typeArg, cause);
}
}
/**
* Adds a class and all of the types it exposes. The cause of the class being exposed is given,
* and the cause of everything within the class is that class itself.
*/
private void addExposedTypes(Class<?> clazz, Class<?> cause) {
if (pruned(clazz)) {
return;
}
// Even if `clazz` has been visited, the link from `cause` may be new
boolean alreadyDone = done(clazz);
visit(clazz);
recordExposure(clazz, cause);
if (alreadyDone || pruned(clazz)) {
return;
}
TypeToken<?> token = TypeToken.of(clazz);
for (TypeToken<?> superType : token.getTypes()) {
if (!superType.equals(token)) {
LOG.debug(
"Adding exposed types from {}, which is a super type token on {}", superType, clazz);
addExposedTypes(superType, clazz);
}
}
for (Class innerClass : clazz.getDeclaredClasses()) {
if (exposed(innerClass.getModifiers())) {
LOG.debug(
"Adding exposed types from {}, which is an exposed inner class of {}",
innerClass,
clazz);
addExposedTypes(innerClass, clazz);
}
}
for (Field field : clazz.getDeclaredFields()) {
if (exposed(field.getModifiers())) {
LOG.debug("Adding exposed types from {}, which is an exposed field on {}", field, clazz);
addExposedTypes(field, clazz);
}
}
for (Invokable invokable : getExposedInvokables(token)) {
LOG.debug(
"Adding exposed types from {}, which is an exposed invokable on {}", invokable, clazz);
addExposedTypes(invokable, clazz);
}
}
private void addExposedTypes(Invokable<?, ?> invokable, Class<?> cause) {
addExposedTypes(invokable.getReturnType(), cause);
for (Annotation annotation : invokable.getAnnotations()) {
LOG.debug(
"Adding exposed types from {}, which is an annotation on invokable {}",
annotation,
invokable);
addExposedTypes(annotation.annotationType(), cause);
}
for (Parameter parameter : invokable.getParameters()) {
LOG.debug(
"Adding exposed types from {}, which is a parameter on invokable {}",
parameter,
invokable);
addExposedTypes(parameter, cause);
}
for (TypeToken<?> exceptionType : invokable.getExceptionTypes()) {
LOG.debug(
"Adding exposed types from {}, which is an exception type on invokable {}",
exceptionType,
invokable);
addExposedTypes(exceptionType, cause);
}
}
private void addExposedTypes(Parameter parameter, Class<?> cause) {
LOG.debug(
"Adding exposed types from {}, which is the type of parameter {}",
parameter.getType(),
parameter);
addExposedTypes(parameter.getType(), cause);
for (Annotation annotation : parameter.getAnnotations()) {
LOG.debug(
"Adding exposed types from {}, which is an annotation on parameter {}",
annotation,
parameter);
addExposedTypes(annotation.annotationType(), cause);
}
}
private void addExposedTypes(Field field, Class<?> cause) {
addExposedTypes(field.getGenericType(), cause);
for (Annotation annotation : field.getDeclaredAnnotations()) {
LOG.debug(
"Adding exposed types from {}, which is an annotation on field {}", annotation, field);
addExposedTypes(annotation.annotationType(), cause);
}
}
/** Returns an {@link Invokable} for each public methods or constructors of a type. */
private Set<Invokable> getExposedInvokables(TypeToken<?> type) {
Set<Invokable> invokables = Sets.newHashSet();
for (Constructor constructor : type.getRawType().getConstructors()) {
if (0 != (constructor.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED))) {
invokables.add(type.constructor(constructor));
}
}
for (Method method : type.getRawType().getMethods()) {
if (0 != (method.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED))) {
invokables.add(type.method(method));
}
}
return invokables;
}
/** Returns true of the given modifier bitmap indicates exposure (public or protected access). */
private boolean exposed(int modifiers) {
return 0 != (modifiers & (Modifier.PUBLIC | Modifier.PROTECTED));
}
////////////////////////////////////////////////////////////////////////////
}