ReflectionTypes.java
package net.florianschoppmann.java.reflect;
import net.florianschoppmann.java.type.AbstractTypes;
import net.florianschoppmann.java.type.IntersectionType;
import javax.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
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.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Implementation of {@link javax.lang.model.util.Types} backed by the Java Reflection API.
*
* <p>All {@link Element} and {@link TypeMirror} objects returned by this class are immutable and therefore thread-safe.
* Likewise, only a stateless (and thus thread-safe) singleton instance of this class is available via
* {@link #getInstance()}.
*
* <p>Currently unsupported are (resulting in an {@link UnsupportedOperationException}):
* <ul><li>
* Type parameters in method declarations. See {@link #typeMirror(Type)} for details.
* </li><li>
* {@link #directSupertypes(TypeMirror)}
* </li><li>
* {@link #asMemberOf(DeclaredType, Element)}
* </li><li>
* {@link #isAssignable(TypeMirror, TypeMirror)}
* </li><li>
* {@link #isSubsignature(ExecutableType, ExecutableType)}
* </li></ul>
*/
public final class ReflectionTypes extends AbstractTypes {
private static final ReflectionTypes INSTANCE = new ReflectionTypes();
private final ImmutableList<PrimitiveTypeImpl> primitiveTypes;
private final ImmutableList<TypeElementImpl> boxedTypeDeclarations;
{
List<PrimitiveTypeImpl> newTypes = new ArrayList<>(TypeKind.values().length);
List<TypeElementImpl> newDeclarations = new ArrayList<>(TypeKind.values().length);
newTypes.addAll(Collections.<PrimitiveTypeImpl>nCopies(TypeKind.values().length, null));
newDeclarations.addAll(Collections.<TypeElementImpl>nCopies(TypeKind.values().length, null));
addPrimitive(newTypes, newDeclarations, TypeKind.DOUBLE, PrimitiveTypeImpl.DOUBLE, Double.class);
addPrimitive(newTypes, newDeclarations, TypeKind.FLOAT, PrimitiveTypeImpl.FLOAT, Float.class);
addPrimitive(newTypes, newDeclarations, TypeKind.LONG, PrimitiveTypeImpl.LONG, Long.class);
addPrimitive(newTypes, newDeclarations, TypeKind.INT, PrimitiveTypeImpl.INT, Integer.class);
addPrimitive(newTypes, newDeclarations, TypeKind.SHORT, PrimitiveTypeImpl.SHORT, Short.class);
addPrimitive(newTypes, newDeclarations, TypeKind.BYTE, PrimitiveTypeImpl.BYTE, Byte.class);
addPrimitive(newTypes, newDeclarations, TypeKind.CHAR, PrimitiveTypeImpl.CHAR, Character.class);
addPrimitive(newTypes, newDeclarations, TypeKind.BOOLEAN, PrimitiveTypeImpl.BOOLEAN, Boolean.class);
primitiveTypes = ImmutableList.copyOf(newTypes);
boxedTypeDeclarations = ImmutableList.copyOf(newDeclarations);
}
private ReflectionTypes() { }
/**
* Returns the singleton instance of this class.
*
* <p>Since this class does not contain any state, and since it is immutable, the returned instance is thread-safe.
*
* @return the singleton instance of this class
*/
public static ReflectionTypes getInstance() {
return INSTANCE;
}
@Override
protected void requireValidElement(Element element) {
Objects.requireNonNull(element);
if (!(element instanceof ReflectionElement)) {
throw new IllegalArgumentException(String.format(
"Expected %s instance that was created by %s, but got instance of %s.",
Element.class.getSimpleName(), ReflectionTypes.class, element.getClass()
));
}
}
@Override
protected void requireValidType(@Nullable TypeMirror type) {
if (!(type instanceof ReflectionTypeMirror) && type != null) {
throw new IllegalArgumentException(String.format(
"Expected %s instance that was created by %s, but got instance of %s.",
TypeMirror.class.getSimpleName(), ReflectionTypes.class, type.getClass()
));
}
}
private void addPrimitive(List<PrimitiveTypeImpl> newPrimitiveTypes, List<TypeElementImpl> newBoxedTypeDeclarations,
TypeKind kind, PrimitiveTypeImpl primitiveType, Class<?> clazz) {
newPrimitiveTypes.set(kind.ordinal(), primitiveType);
newBoxedTypeDeclarations.set(kind.ordinal(), ((DeclaredTypeImpl) typeMirror(clazz)).asElement());
}
@Override
public TypeElement boxedClass(PrimitiveType primitiveType) {
requireValidType(Objects.requireNonNull(primitiveType));
@Nullable TypeElementImpl typeElement = boxedTypeDeclarations.get(primitiveType.getKind().ordinal());
assert typeElement != null : "Array with boxed type declarations incorrectly initialized.";
return typeElement;
}
@Override
public PrimitiveType unboxedType(TypeMirror type) {
requireValidType(Objects.requireNonNull(type));
if (type.getKind() != TypeKind.DECLARED) {
throw new IllegalArgumentException(String.format(
"Expected type mirror of kind %s, but got %s.", TypeKind.DECLARED, type
));
}
Name name = ((DeclaredTypeImpl) type).asElement().getQualifiedName();
if (name.contentEquals(Double.class.getName())) {
return PrimitiveTypeImpl.DOUBLE;
} else if (name.contentEquals(Float.class.getName())) {
return PrimitiveTypeImpl.FLOAT;
} else if (name.contentEquals(Long.class.getName())) {
return PrimitiveTypeImpl.LONG;
} else if (name.contentEquals(Integer.class.getName())) {
return PrimitiveTypeImpl.INT;
} else if (name.contentEquals(Short.class.getName())) {
return PrimitiveTypeImpl.SHORT;
} else if (name.contentEquals(Byte.class.getName())) {
return PrimitiveTypeImpl.BYTE;
} else if (name.contentEquals(Character.class.getName())) {
return PrimitiveTypeImpl.CHAR;
} else if (name.contentEquals(Boolean.class.getName())) {
return PrimitiveTypeImpl.BOOLEAN;
} else {
throw new IllegalArgumentException(String.format("Expected boxed type, but got %s.", type));
}
}
/**
* Returns a type mirror for the given {@link Class} object.
*/
private ReflectionTypeMirror mirrorClass(Class<?> clazz, MirrorContext mirrorContext) {
if (clazz.isArray()) {
return new ArrayTypeImpl(mirrorContext.mirror(clazz.getComponentType()));
} else if (clazz.isPrimitive()) {
return (ReflectionTypeMirror) getPrimitiveType(TypeKind.valueOf(clazz.getName().toUpperCase()));
} else {
// raw type
@Nullable Class<?> enclosingClass = clazz.getEnclosingClass();
ReflectionTypeMirror enclosingType = enclosingClass == null
? NoTypeImpl.NONE
: mirrorContext.mirror(enclosingClass);
return new DeclaredTypeImpl(enclosingType, mirrorContext.typeDeclaration(clazz),
Collections.<ReflectionTypeMirror>emptyList());
}
}
/**
* Returns a type mirror for the given {@link ParameterizedType} object.
*/
private static DeclaredTypeImpl mirrorParameterizedType(ParameterizedType parameterizedType,
MirrorContext mirrorContext) {
Class<?> rawClass = (Class<?>) parameterizedType.getRawType();
TypeElementImpl typeDeclaration = mirrorContext.typeDeclaration(rawClass);
@Nullable Type ownerType = parameterizedType.getOwnerType();
ReflectionTypeMirror ownerTypeMirror = ownerType == null
? NoTypeImpl.NONE
: mirrorContext.mirror(ownerType);
return new DeclaredTypeImpl(ownerTypeMirror, typeDeclaration,
mirrorContext.mirror(parameterizedType.getActualTypeArguments()));
}
/**
* Returns a type mirror for the given {@link WildcardType} object.
*
* <p>The following preconditions are guaranteed by the JLS and the JavaDoc of package {@link java.lang.reflect}:
* <ul><li>
* {@link WildcardType#getUpperBounds()} specifies: "Note that if no upper bound is explicitly
* declared, the upper bound is {@code Object}."
* </li><li>
* While {@link WildcardType#getUpperBounds()} and
* {@link WildcardType#getLowerBounds()} return an arrays, JLS ยง4.5.1 (at least up to
* version 8) only supports a single ReferenceType for both bounds.
* </li></ul>
*/
private static WildcardTypeImpl mirrorWildcardType(WildcardType wildcardType, MirrorContext mirrorContext) {
Type[] upperBounds = wildcardType.getUpperBounds();
Type[] lowerBounds = wildcardType.getLowerBounds();
// See JavaDoc for an explanation of the following assert statement.
assert upperBounds.length == 1 && lowerBounds.length <= 1
&& (lowerBounds.length == 0 || Object.class.equals(upperBounds[0]));
@Nullable ReflectionTypeMirror extendsBounds;
if (Object.class.equals(upperBounds[0])) {
extendsBounds = null;
} else {
extendsBounds = mirrorContext.mirror(upperBounds[0]);
}
@Nullable ReflectionTypeMirror superBound;
if (lowerBounds.length == 0) {
superBound = null;
} else {
superBound = mirrorContext.mirror(lowerBounds[0]);
}
return new WildcardTypeImpl(extendsBounds, superBound);
}
static void requireCondition(boolean condition, String formatString, Object argument) {
if (!condition) {
throw new IllegalStateException(String.format(formatString, argument));
}
}
private static TypeVariableImpl mirrorTypeVariable(TypeVariable<?> typeVariable, MirrorContext mirrorContext) {
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
if (genericDeclaration instanceof Class<?>) {
TypeElementImpl typeDeclaration = mirrorContext.typeDeclaration((Class<?>) genericDeclaration);
@Nullable TypeParameterElementImpl element = null;
for (TypeParameterElementImpl typeParameter: typeDeclaration.getTypeParameters()) {
if (typeParameter.getSimpleName().contentEquals(typeVariable.getName())) {
element = typeParameter;
break;
}
}
requireCondition(element != null,
"Could not find the type-parameter element that corresponds to type variable %s.", typeVariable);
assert element != null : "redundant check for FindBugs";
return element.asType();
} else {
throw new UnsupportedOperationException("Method or constructor type parameters not supported.");
}
}
ReflectionTypeMirror mirrorInternal(Type type, MirrorContext mirrorContext) {
@Nullable ReflectionTypeMirror typeMirror = null;
if (type instanceof Class<?>) {
typeMirror = mirrorClass((Class<?>) type, mirrorContext);
} else if (type instanceof ParameterizedType) {
typeMirror = mirrorParameterizedType((ParameterizedType) type, mirrorContext);
} else if (type instanceof GenericArrayType) {
typeMirror = new ArrayTypeImpl(
mirrorContext.mirror(((GenericArrayType) type).getGenericComponentType())
);
} else if (type instanceof WildcardType) {
typeMirror = mirrorWildcardType((WildcardType) type, mirrorContext);
} else if (type instanceof TypeVariable<?>) {
typeMirror = mirrorTypeVariable((TypeVariable<?>) type, mirrorContext);
}
requireCondition(typeMirror != null,
"Expected Class, ParameterizedType, GenericArrayType, WildcardType, or TypeVariable instance, but got %s.",
type);
assert typeMirror != null : "redundant check for FindBugs";
return typeMirror;
}
/**
* Returns a type element corresponding to the given {@link Class} object.
*
* @param clazz class object
* @return type element corresponding to the given {@link Class} object
* @throws IllegalArgumentException if the given class represents a primitive or array type
* @throws UnsupportedOperationException if a generic declaration is referenced that is not of type {@link Class},
* see {@link #typeMirror(Type)} for details
*/
public TypeElement typeElement(Class<?> clazz) {
if (clazz.isArray() || clazz.isPrimitive()) {
throw new IllegalArgumentException(String.format("Expected class or interface type, but got %s.", clazz));
}
return ((DeclaredTypeImpl) typeMirror(clazz)).asElement();
}
/**
* Returns a type mirror corresponding to the given Java reflection type.
*
* <p>Type parameters in method declarations are not currently supported. That is, if the given type references a
* {@link TypeVariable} instance that has a {@link java.lang.reflect.Constructor} or
* {@link java.lang.reflect.Method} as generic declaration, an {@link UnsupportedOperationException}
* will be thrown.
*
* @param type type as represented by Java Reflection API
* @return type mirror corresponding to the given Java reflection type
* @throws UnsupportedOperationException if a generic declaration is referenced that is not of type {@link Class}
*/
@Override
public TypeMirror typeMirror(Type type) {
Map<Class<?>, TypeElementImpl> typeDeclarations = new LinkedHashMap<>();
Map<Class<?>, TypeElementImpl> newTypeDeclarations = new LinkedHashMap<>();
MirrorContext mirrorContext = new MirrorContext(this, typeDeclarations, newTypeDeclarations);
TypeMirror typeMirror = mirrorInternal(type, mirrorContext);
while (!newTypeDeclarations.isEmpty()) {
List<TypeElementImpl> typeDeclarationsToFinish = new ArrayList<>(newTypeDeclarations.values());
typeDeclarations.putAll(newTypeDeclarations);
newTypeDeclarations.clear();
for (TypeElementImpl typeDeclaration: typeDeclarationsToFinish) {
typeDeclaration.finish(mirrorContext);
}
}
return typeMirror;
}
private static ImmutableList<ReflectionTypeMirror> toList(TypeMirror[] types) {
List<ReflectionTypeMirror> list = new ArrayList<>(types.length);
for (TypeMirror type: types) {
list.add((ReflectionTypeMirror) type);
}
return ImmutableList.copyOf(list);
}
@Override
public DeclaredType getDeclaredType(@Nullable DeclaredType containing, TypeElement typeElem,
TypeMirror... typeArgs) {
requireValidType(containing);
requireValidElement(typeElem);
requireValidTypes(typeArgs);
ReflectionTypeMirror newContainingType = containing == null
? NoTypeImpl.NONE
: (ReflectionTypeMirror) containing;
return new DeclaredTypeImpl(newContainingType, (TypeElementImpl) typeElem, toList(typeArgs));
}
@Override
public DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) {
requireValidElement(typeElem);
requireValidTypes(typeArgs);
return new DeclaredTypeImpl(NoTypeImpl.NONE, (TypeElementImpl) typeElem, toList(typeArgs));
}
@Override
public NoType getNoType(TypeKind kind) {
Objects.requireNonNull(kind);
if (kind == TypeKind.VOID) {
return NoTypeImpl.VOID;
} else if (kind == TypeKind.NONE) {
return NoTypeImpl.NONE;
} else {
throw new IllegalArgumentException(String.format("Expected one of %s, but got %s.",
Arrays.asList(TypeKind.VOID, TypeKind.NONE), kind));
}
}
@Override
public NullType getNullType() {
return NullTypeImpl.INSTANCE;
}
@Override
public ArrayType getArrayType(TypeMirror componentType) {
Objects.requireNonNull(componentType);
requireValidType(componentType);
return new ArrayTypeImpl((ReflectionTypeMirror) componentType);
}
@Override
protected javax.lang.model.type.TypeVariable createTypeVariable(TypeParameterElement typeParameter,
@Nullable javax.lang.model.type.WildcardType capturedTypeArgument) {
requireValidElement(Objects.requireNonNull(typeParameter));
requireValidType(capturedTypeArgument);
return new TypeVariableImpl((TypeParameterElementImpl) typeParameter, (WildcardTypeImpl) capturedTypeArgument);
}
@Override
protected javax.lang.model.type.WildcardType capturedTypeArgument(javax.lang.model.type.TypeVariable typeVariable) {
requireValidType(Objects.requireNonNull(typeVariable));
return ((TypeVariableImpl) typeVariable).getCapturedTypeArgument();
}
@Override
public IntersectionType getIntersectionType(TypeMirror... bounds) {
Objects.requireNonNull(bounds);
if (bounds.length == 0) {
throw new IllegalArgumentException("Expected at least one bound.");
}
requireValidTypes(bounds);
List<ReflectionTypeMirror> newBounds = new ArrayList<>(bounds.length);
for (TypeMirror bound: bounds) {
newBounds.add((ReflectionTypeMirror) bound);
}
return new IntersectionTypeImpl(newBounds);
}
@Override
protected void setTypeVariableBounds(javax.lang.model.type.TypeVariable typeVariable, TypeMirror upperBound,
TypeMirror lowerBound) {
requireValidType(Objects.requireNonNull(typeVariable));
requireValidType(Objects.requireNonNull(upperBound));
requireValidType(Objects.requireNonNull(lowerBound));
((TypeVariableImpl) typeVariable).setUpperAndLowerBounds(
(ReflectionTypeMirror) upperBound,
(ReflectionTypeMirror) lowerBound
);
}
@Override
public PrimitiveType getPrimitiveType(TypeKind kind) {
@Nullable PrimitiveTypeImpl primitiveType = primitiveTypes.get(kind.ordinal());
if (primitiveType == null) {
throw new IllegalArgumentException(String.format("Expected primitive kind, but got %s.", kind));
}
return primitiveType;
}
@Override
public javax.lang.model.type.WildcardType getWildcardType(@Nullable TypeMirror extendsBound,
@Nullable TypeMirror superBound) {
requireValidType(extendsBound);
requireValidType(superBound);
return new WildcardTypeImpl((ReflectionTypeMirror) extendsBound, (ReflectionTypeMirror) superBound);
}
private static UnsupportedOperationException unsupportedException() {
return new UnsupportedOperationException(
"isAssignable(), isSubsignature(), asMemberOf(), and directSupertypes() not currently supported."
);
}
@Override
public boolean isAssignable(TypeMirror t1, TypeMirror t2) {
throw unsupportedException();
}
@Override
public boolean isSubsignature(ExecutableType m1, ExecutableType m2) {
throw unsupportedException();
}
@Override
public TypeMirror asMemberOf(DeclaredType containing, Element element) {
throw unsupportedException();
}
/**
* @throws UnsupportedOperationException whenever this method is called
*/
@Override
public List<? extends TypeMirror> directSupertypes(TypeMirror t) {
throw unsupportedException();
}
}