com.google.gwt.inject.rebind.reflect.ReflectUtil Maven / Gradle / Ivy
/*
* Copyright 2010 Google Inc.
*
* Licensed 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 com.google.gwt.inject.rebind.reflect;
import static com.google.gwt.inject.rebind.util.SourceWriteUtil.join;
import com.google.gwt.inject.rebind.util.Preconditions;
import com.google.gwt.inject.rebind.util.PrettyPrinter;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Member;
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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Utility providing helper methods around reflection.
*/
public class ReflectUtil {
/**
* Alternate toString method for TypeLiterals that fixes a JDK bug that was
* replicated in Guice. See
*
* the related Guice bug for details.
*
* Also replaces all binary with source names in the types involved (base
* type and type parameters).
*
* @param typeLiteral type for which string will be returned
* @return String representation of type
* @throws NoSourceNameException if source name is not available for type
*/
public static String getSourceName(TypeLiteral> typeLiteral) throws NoSourceNameException {
return getSourceName(typeLiteral.getType());
}
/**
* Returns a string representation of the passed type's name while ensuring
* that all type names (base and parameters) are converted to source type
* names.
*
* @param type type for which string will be returned
* @return String representation of type
* @throws NoSourceNameException if source name is not available for type
*/
public static String getSourceName(Type type) throws NoSourceNameException {
if (type instanceof Class>) {
Class> clazz = (Class>) type;
if (clazz.isPrimitive()) {
return clazz.getName(); // Returns "int" for integer etc.
}
String name = clazz.getCanonicalName();
// We get a null for anonymous inner classes or other types that don't
// have source names.
if (name == null) {
throw new NoSourceNameException(type);
}
return name;
}
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] arguments = parameterizedType.getActualTypeArguments();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(getSourceName(parameterizedType.getRawType()));
if (arguments.length == 0) {
return stringBuilder.toString();
}
stringBuilder.append("<").append(getSourceName(arguments[0]));
for (int i = 1; i < arguments.length; i++) {
stringBuilder.append(", ").append(getSourceName(arguments[i]));
}
return stringBuilder.append(">").toString();
}
if (type instanceof GenericArrayType) {
return getSourceName(((GenericArrayType) type).getGenericComponentType()) + "[]";
}
if (type instanceof WildcardType) {
Type[] lowerBounds = ((WildcardType) type).getLowerBounds();
Type[] upperBounds = ((WildcardType) type).getUpperBounds();
if (lowerBounds.length > 0 && upperBounds.length > 0
&& lowerBounds.length != 1 && lowerBounds[0] != Object.class) {
throw new NoSourceNameException(type);
}
if (lowerBounds.length > 0) {
return getBoundedSourceName("?", lowerBounds, "super");
} else if (upperBounds.length == 1 && upperBounds[0] == Object.class) {
return "?";
} else {
return getBoundedSourceName("?", upperBounds, "extends");
}
}
// This returns the name of a reference to a type variable, not the
// definition thereof. For the latter see #getTypeVariableDefinition(..).
if (type instanceof TypeVariable) {
return ((TypeVariable) type).getName();
}
throw new NoSourceNameException(type);
}
/**
* Return the name of the package from which the given type can be used.
*
* Returns a package from which all the type names contained in the given
* type literal are visible. Throws {@link IllegalArgumentException} if there
* is no such package. If there are multiple such packages, then the type
* name can be used from any package; the package containing the outermost
* class is used arbitrarily.
*
*
This method is intentionally not overloaded on Class, because it's
* normally an error to use a raw Class token to determine the package in
* which to manipulate a type.
*/
public static String getUserPackageName(TypeLiteral> typeLiteral) {
Map> packageNames = new LinkedHashMap>();
getTypePackageNames(typeLiteral.getType(), packageNames);
if (packageNames.size() == 0) {
// All type names are public, so typeLiteral is visible from any package.
// Arbitrarily put it in the package declaring the top-level class.
return typeLiteral.getRawType().getPackage().getName();
} else if (packageNames.size() == 1) {
// The type contains names that are private to exactly one package; it
// must be referenced from that package.
return packageNames.keySet().iterator().next();
} else {
// The type literal contains types that are private to two or more
// different packages. This can happen if a class uses a type that is
// protected in its parent, and its parent is from another package. For
// instance:
//
// package pkg1:
// public class Parent {
// protected static class ForSubclasses {
// }
// }
//
// Here the type ForSubclasses is accessible to anything in the package
// "pkg1", but it can't be used in another package:
//
// package pkg2:
// class Foo {
// }
//
// class Child extends Parent {
// @Inject Child(Foo) {}
// }
//
// There's no package in which we can place code that can create
// Foo, even though the user was able to write that type,
// because we would have to subclass Parent to do so. (theoretically we
// could write static helper methods inside a subclass, but that seems
// like too much trouble to support this sort of weirdness)
StringBuilder packageNamesListBuilder = new StringBuilder();
for (Class> entry : packageNames.values()) {
packageNamesListBuilder.append(entry.getCanonicalName()).append("\n");
}
throw new IllegalArgumentException(PrettyPrinter.format(
"Unable to inject an instance of %s because it references protected classes"
+ " from multiple packages:\n%s",
typeLiteral,
packageNamesListBuilder));
}
}
/**
* Visits all the components of a type, collecting a map taking the name of
* each package in which package-private types are defined to one of the
* classes contained in {@code type} that belongs to that package. If any
* private types are encountered, an {@link IllegalArgumentException} is
* thrown.
*
* This is required to deal with situations like Generic where
* Generic is publically defined in package A and Private is private to
* package B; we need to place code that uses this type in package B, even
* though the top-level class is from A.
*/
private static void getTypePackageNames(Type type, Map> packageNames) {
if (type instanceof Class>) {
getClassPackageNames((Class>) type, packageNames);
} else if (type instanceof GenericArrayType) {
getTypePackageNames(((GenericArrayType) type).getGenericComponentType(), packageNames);
} else if (type instanceof ParameterizedType) {
getParameterizedTypePackageNames((ParameterizedType) type, packageNames);
} else if (type instanceof TypeVariable) {
getTypeVariablePackageNames((TypeVariable) type, packageNames);
} else if (type instanceof WildcardType) {
getWildcardTypePackageNames((WildcardType) type, packageNames);
}
}
/**
* Visits classes to collect package names.
*
* @see {@link #getTypePackageNames}.
*/
private static void getClassPackageNames(Class> clazz, Map> packageNames) {
if (isPrivate(clazz)) {
throw new IllegalArgumentException(PrettyPrinter.format(
"Unable to inject an instance of %s because it is a private class.", clazz));
} else if (!isPublic(clazz)) {
packageNames.put(clazz.getPackage().getName(), clazz);
}
Class> enclosingClass = clazz.getEnclosingClass();
if (enclosingClass != null) {
getClassPackageNames(enclosingClass, packageNames);
}
}
/**
* Visits parameterized types to collect package names.
*
* @see {@link #getTypePackageNames}.
*/
private static void getParameterizedTypePackageNames(ParameterizedType type,
Map> packageNames) {
for (Type argumentType : type.getActualTypeArguments()) {
getTypePackageNames(argumentType, packageNames);
}
getTypePackageNames(type.getRawType(), packageNames);
Type ownerType = type.getOwnerType();
if (ownerType != null) {
getTypePackageNames(ownerType, packageNames);
}
}
/**
* Visits type variables to collect package names.
*
* @see {@link #getTypeVariablePackageNames}.
*/
private static void getTypeVariablePackageNames(TypeVariable type,
Map> packageNames) {
for (Type boundType : type.getBounds()) {
getTypePackageNames(boundType, packageNames);
}
}
/**
* Visits wildcard types to collect package names.
*
* @see {@link #getTypeVariablePackageNames}.
*/
private static void getWildcardTypePackageNames(WildcardType type,
Map> packageNames) {
for (Type boundType : type.getUpperBounds()) {
getTypePackageNames(boundType, packageNames);
}
for (Type boundType : type.getLowerBounds()) {
getTypePackageNames(boundType, packageNames);
}
}
/**
* Return the name of the package from which the given key can be used.
*
* Returns a package from which all the type names contained in the given
* key are visible. Throws {@link IllegalArgumentException} if there is no
* such package.
*/
public static String getUserPackageName(Key> key) {
return getUserPackageName(key.getTypeLiteral());
}
/**
* Creates a bounded source name of the form {@code T extends Foo & Bar},
* {@code ? super Baz} or {@code ?} as appropriate.
*/
private static String getBoundedSourceName(String token, Type[] bounds, String direction)
throws NoSourceNameException {
List boundNames = new ArrayList();
for (Type boundary : bounds) {
boundNames.add(getSourceName(boundary));
}
return String.format("%s %s %s", token, direction, join(" & ", boundNames));
}
/**
* Returns a type variable's definition, e.g. {@code M extends Foo}.
*/
// Visible for testing
static String getTypeVariableDefinition(TypeVariable variable)
throws NoSourceNameException {
Type[] bounds = variable.getBounds();
if (bounds.length == 0 || (bounds.length == 1 && bounds[0].equals(Object.class))) {
return variable.getName();
}
return getBoundedSourceName(variable.getName(), bounds, "extends");
}
/**
* Returns {@code true} if the passed type's visibility is {@code public}.
*/
public static boolean isPublic(Class> type) {
return Modifier.isPublic(type.getModifiers());
}
/**
* Returns {@code true} if the passed type's visibility is {@code private}.
*/
public static boolean isPrivate(Class> type) {
return Modifier.isPrivate(type.getModifiers());
}
/**
* Returns {@code true} if the passed type's visibility is {@code private}.
*/
public static boolean isPrivate(TypeLiteral> type) {
return isPrivate(type.getRawType());
}
/**
* Returns {@code true} if the passed member's visibility is {@code private}.
*/
public static boolean isPrivate(Member member) {
return Modifier.isPrivate(member.getModifiers());
}
/**
* Builds the signature of a method with all types in source form.
*/
public static SignatureBuilder signatureBuilder(MethodLiteral, ?> method) {
return new SignatureBuilder(method);
}
/**
* Builder that produces the signature of a method.
*/
public static class SignatureBuilder {
private final MethodLiteral, ?> method;
private String methodName;
private int modifiers;
private String[] parameterNames;
private SignatureBuilder(MethodLiteral, ?> method) {
this.method = method;
// Set up defaults:
this.methodName = method.getName();
this.modifiers = method.getModifiers();
this.parameterNames = getDefaultParameterNames(method.getParameterTypes().size());
}
/**
* Builds the method signature with all types in source form.
*
* @see #getSourceName(Type)
* @throws NoSourceNameException if any type's source name cannot be
* determined.
*/
public String build() throws NoSourceNameException {
return getSignature(method, parameterNames, methodName, modifiers);
}
/**
* Removes the abstract modifier from the current modifiers (either the last
* modifiers set by {@link #withModifiers}, or the modifiers of the method
* passed to {@link #signatureBuilder}).
*/
public SignatureBuilder removeAbstractModifier() {
return withModifiers(modifiers & ~Modifier.ABSTRACT);
}
/**
* Sets the method name used in the signature. If not set, defaults to the
* method's name.
*/
public SignatureBuilder withMethodName(String methodName) {
this.methodName = methodName;
return this;
}
/**
* Sets the modifiers used in the method signature. If not set, defaults to
* the method's modifiers.
*/
public SignatureBuilder withModifiers(int modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* Sets the names to use for the method's parameters. The length of
* parameterNames must be the same as the method's parameter count. If not
* set, default names are chosen.
*/
public SignatureBuilder withParameterNames(String[] parameterNames) {
this.parameterNames = parameterNames;
return this;
}
}
/**
* Returns the passed method's signature with all types in source form.
*
* @param method method for which signature is created
* @param paramNames names to be used for the method's parameters. Length
* must be the same as method's parameter count
* @param overrideName name used instead of the method's name
* @param overrideModifiers modifiers used instead of the method's original
* modifiers, see also {@link Modifier}
* @return method's signature
* @see #getSourceName(Type)
* @throws NoSourceNameException if any type's source name cannot be
* determined
*/
// TODO(schmitt): Print method and parameter annotations.
private static String getSignature(MethodLiteral, ?> method, String[] paramNames,
String overrideName, int overrideModifiers) throws NoSourceNameException {
if (paramNames.length != method.getParameterTypes().size()) {
throw new IllegalArgumentException(
String.format("Wrong number of parameters provided for method signature, "
+ "expected %d but got %d.", method.getParameterTypes().size(), paramNames.length));
}
StringBuilder sb = new StringBuilder();
if (overrideModifiers != 0) {
sb.append(Modifier.toString(overrideModifiers)).append(" ");
}
if (method.getTypeParameters().length > 0) {
List typeParameters = new ArrayList();
for (TypeVariable> typeVariable : method.getTypeParameters()) {
typeParameters.add(getTypeVariableDefinition(typeVariable));
}
sb.append("<").append(join(", ", typeParameters)).append("> ");
}
if (!method.isConstructor()) {
sb.append(getSourceName(method.getReturnType())).append(" ");
if (overrideName != null) {
sb.append(overrideName);
} else {
sb.append(method.getName());
}
} else {
sb.append(getSourceName(method.getRawDeclaringType()));
}
sb.append("(");
// TODO(schmitt): We are not respecting varargs here.
List parameters = new ArrayList();
int i = 0;
for (TypeLiteral> parameterType : method.getParameterTypes()) {
parameters.add(
String.format("%s %s", getSourceName(parameterType), paramNames[i]));
i++;
}
sb.append(join(", ", parameters)).append(")");
if (method.getExceptionTypes().size() > 0) {
List exceptions = new ArrayList();
for (TypeLiteral> exceptionType : method.getExceptionTypes()) {
exceptions.add(getSourceName(exceptionType));
}
sb.append(" throws ").append(join(", ", exceptions));
}
return sb.toString();
}
private static String[] getDefaultParameterNames(int count) {
String[] paramNames = new String[count];
for (int i = 0; i < paramNames.length; i++) {
paramNames[i] = formatParameterName(i);
}
return paramNames;
}
/**
* Returns a string representing a parameter name for a method signature.
*
* Use this method to keep parameter names the same throughout Gin code.
* Creating synthetic parameter names is necessary since java reflection does
* not expose source parameter names.
*
* @param position position of the parameter in the signature
* @return parameter name
*/
public static String formatParameterName(int position) {
return "_" + position;
}
/**
* If present, strips the "abstract" modifier from the passed method's
* modifiers.
*
*
Useful since interface methods are abstract but we're often writing an
* implementation for them.
*/
public static int nonAbstractModifiers(MethodLiteral, Method> method) {
return method.getModifiers() & ~Modifier.ABSTRACT;
}
/**
* Returns {@code true} if the passed type is either a class or an interface
* (but not a primitive, enum or similar).
*
* @param type class to be checked
* @return {@code true} if the passed type is a class or interface
*/
public static boolean isClassOrInterface(Type type) {
if (!(type instanceof Class)) {
return false;
}
Class clazz = (Class) type;
return !clazz.isPrimitive() && !clazz.isAnnotation() && !clazz.isArray() && !clazz.isEnum()
&& !clazz.isAnonymousClass();
}
/**
* Given a parameterized type (such as a {@code Provider}) return the
* parameter ({@code Foo}).
*/
public static Key> getProvidedKey(Key> key) {
Type providerType = key.getTypeLiteral().getType();
// If the Provider has no type parameter (raw Provider)...
Preconditions.checkArgument(providerType instanceof ParameterizedType,
"Expected all providers to be parameterized, but %s wasn't", key);
Type entryType = ((ParameterizedType) providerType).getActualTypeArguments()[0];
return key.ofType(entryType);
}
/**
* Returns {@code true} if the given class has a non-private default
* constructor, or has no constructor at all.
*/
public static boolean hasAccessibleDefaultConstructor(Class> clazz) {
Constructor> constructor;
try {
constructor = clazz.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
return clazz.getDeclaredConstructors().length == 0;
}
return !isPrivate(constructor);
}
}