| /* |
| * Copyright (C) 2012 The Guava Authors |
| * |
| * 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.common.reflect; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.collect.ImmutableList; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.AccessibleObject; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.GenericDeclaration; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Member; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| import java.util.Arrays; |
| import org.checkerframework.checker.nullness.compatqual.NullableDecl; |
| |
| /** |
| * Wrapper around either a {@link Method} or a {@link Constructor}. Convenience API is provided to |
| * make common reflective operation easier to deal with, such as {@link #isPublic}, {@link |
| * #getParameters} etc. |
| * |
| * <p>In addition to convenience methods, {@link TypeToken#method} and {@link TypeToken#constructor} |
| * will resolve the type parameters of the method or constructor in the context of the owner type, |
| * which may be a subtype of the declaring class. For example: |
| * |
| * <pre>{@code |
| * Method getMethod = List.class.getMethod("get", int.class); |
| * Invokable<List<String>, ?> invokable = new TypeToken<List<String>>() {}.method(getMethod); |
| * assertEquals(TypeToken.of(String.class), invokable.getReturnType()); // Not Object.class! |
| * assertEquals(new TypeToken<List<String>>() {}, invokable.getOwnerType()); |
| * }</pre> |
| * |
| * @param <T> the type that owns this method or constructor. |
| * @param <R> the return type of (or supertype thereof) the method or the declaring type of the |
| * constructor. |
| * @author Ben Yu |
| * @since 14.0 |
| */ |
| @Beta |
| public abstract class Invokable<T, R> extends Element implements GenericDeclaration { |
| |
| <M extends AccessibleObject & Member> Invokable(M member) { |
| super(member); |
| } |
| |
| /** Returns {@link Invokable} of {@code method}. */ |
| public static Invokable<?, Object> from(Method method) { |
| return new MethodInvokable<>(method); |
| } |
| |
| /** Returns {@link Invokable} of {@code constructor}. */ |
| public static <T> Invokable<T, T> from(Constructor<T> constructor) { |
| return new ConstructorInvokable<T>(constructor); |
| } |
| |
| /** |
| * Returns {@code true} if this is an overridable method. Constructors, private, static or final |
| * methods, or methods declared by final classes are not overridable. |
| */ |
| public abstract boolean isOverridable(); |
| |
| /** Returns {@code true} if this was declared to take a variable number of arguments. */ |
| public abstract boolean isVarArgs(); |
| |
| /** |
| * Invokes with {@code receiver} as 'this' and {@code args} passed to the underlying method and |
| * returns the return value; or calls the underlying constructor with {@code args} and returns the |
| * constructed instance. |
| * |
| * @throws IllegalAccessException if this {@code Constructor} object enforces Java language access |
| * control and the underlying method or constructor is inaccessible. |
| * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an |
| * unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a |
| * parameter value cannot be converted to the corresponding formal parameter type by a method |
| * invocation conversion. |
| * @throws InvocationTargetException if the underlying method or constructor throws an exception. |
| */ |
| // All subclasses are owned by us and we'll make sure to get the R type right. |
| @SuppressWarnings("unchecked") |
| @CanIgnoreReturnValue |
| public final R invoke(@NullableDecl T receiver, Object... args) |
| throws InvocationTargetException, IllegalAccessException { |
| return (R) invokeInternal(receiver, checkNotNull(args)); |
| } |
| |
| /** Returns the return type of this {@code Invokable}. */ |
| // All subclasses are owned by us and we'll make sure to get the R type right. |
| @SuppressWarnings("unchecked") |
| public final TypeToken<? extends R> getReturnType() { |
| return (TypeToken<? extends R>) TypeToken.of(getGenericReturnType()); |
| } |
| |
| /** |
| * Returns all declared parameters of this {@code Invokable}. Note that if this is a constructor |
| * of a non-static inner class, unlike {@link Constructor#getParameterTypes}, the hidden {@code |
| * this} parameter of the enclosing class is excluded from the returned parameters. |
| */ |
| public final ImmutableList<Parameter> getParameters() { |
| Type[] parameterTypes = getGenericParameterTypes(); |
| Annotation[][] annotations = getParameterAnnotations(); |
| ImmutableList.Builder<Parameter> builder = ImmutableList.builder(); |
| for (int i = 0; i < parameterTypes.length; i++) { |
| builder.add(new Parameter(this, i, TypeToken.of(parameterTypes[i]), annotations[i])); |
| } |
| return builder.build(); |
| } |
| |
| /** Returns all declared exception types of this {@code Invokable}. */ |
| public final ImmutableList<TypeToken<? extends Throwable>> getExceptionTypes() { |
| ImmutableList.Builder<TypeToken<? extends Throwable>> builder = ImmutableList.builder(); |
| for (Type type : getGenericExceptionTypes()) { |
| // getGenericExceptionTypes() will never return a type that's not exception |
| @SuppressWarnings("unchecked") |
| TypeToken<? extends Throwable> exceptionType = |
| (TypeToken<? extends Throwable>) TypeToken.of(type); |
| builder.add(exceptionType); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Explicitly specifies the return type of this {@code Invokable}. For example: |
| * |
| * <pre>{@code |
| * Method factoryMethod = Person.class.getMethod("create"); |
| * Invokable<?, Person> factory = Invokable.of(getNameMethod).returning(Person.class); |
| * }</pre> |
| */ |
| public final <R1 extends R> Invokable<T, R1> returning(Class<R1> returnType) { |
| return returning(TypeToken.of(returnType)); |
| } |
| |
| /** Explicitly specifies the return type of this {@code Invokable}. */ |
| public final <R1 extends R> Invokable<T, R1> returning(TypeToken<R1> returnType) { |
| if (!returnType.isSupertypeOf(getReturnType())) { |
| throw new IllegalArgumentException( |
| "Invokable is known to return " + getReturnType() + ", not " + returnType); |
| } |
| @SuppressWarnings("unchecked") // guarded by previous check |
| Invokable<T, R1> specialized = (Invokable<T, R1>) this; |
| return specialized; |
| } |
| |
| @SuppressWarnings("unchecked") // The declaring class is T's raw class, or one of its supertypes. |
| @Override |
| public final Class<? super T> getDeclaringClass() { |
| return (Class<? super T>) super.getDeclaringClass(); |
| } |
| |
| /** Returns the type of {@code T}. */ |
| // Overridden in TypeToken#method() and TypeToken#constructor() |
| @SuppressWarnings("unchecked") // The declaring class is T. |
| @Override |
| public TypeToken<T> getOwnerType() { |
| return (TypeToken<T>) TypeToken.of(getDeclaringClass()); |
| } |
| |
| abstract Object invokeInternal(@NullableDecl Object receiver, Object[] args) |
| throws InvocationTargetException, IllegalAccessException; |
| |
| abstract Type[] getGenericParameterTypes(); |
| |
| /** This should never return a type that's not a subtype of Throwable. */ |
| abstract Type[] getGenericExceptionTypes(); |
| |
| abstract Annotation[][] getParameterAnnotations(); |
| |
| abstract Type getGenericReturnType(); |
| |
| static class MethodInvokable<T> extends Invokable<T, Object> { |
| |
| final Method method; |
| |
| MethodInvokable(Method method) { |
| super(method); |
| this.method = method; |
| } |
| |
| @Override |
| final Object invokeInternal(@NullableDecl Object receiver, Object[] args) |
| throws InvocationTargetException, IllegalAccessException { |
| return method.invoke(receiver, args); |
| } |
| |
| @Override |
| Type getGenericReturnType() { |
| return method.getGenericReturnType(); |
| } |
| |
| @Override |
| Type[] getGenericParameterTypes() { |
| return method.getGenericParameterTypes(); |
| } |
| |
| @Override |
| Type[] getGenericExceptionTypes() { |
| return method.getGenericExceptionTypes(); |
| } |
| |
| @Override |
| final Annotation[][] getParameterAnnotations() { |
| return method.getParameterAnnotations(); |
| } |
| |
| @Override |
| public final TypeVariable<?>[] getTypeParameters() { |
| return method.getTypeParameters(); |
| } |
| |
| @Override |
| public final boolean isOverridable() { |
| return !(isFinal() |
| || isPrivate() |
| || isStatic() |
| || Modifier.isFinal(getDeclaringClass().getModifiers())); |
| } |
| |
| @Override |
| public final boolean isVarArgs() { |
| return method.isVarArgs(); |
| } |
| } |
| |
| static class ConstructorInvokable<T> extends Invokable<T, T> { |
| |
| final Constructor<?> constructor; |
| |
| ConstructorInvokable(Constructor<?> constructor) { |
| super(constructor); |
| this.constructor = constructor; |
| } |
| |
| @Override |
| final Object invokeInternal(@NullableDecl Object receiver, Object[] args) |
| throws InvocationTargetException, IllegalAccessException { |
| try { |
| return constructor.newInstance(args); |
| } catch (InstantiationException e) { |
| throw new RuntimeException(constructor + " failed.", e); |
| } |
| } |
| |
| /** |
| * If the class is parameterized, such as {@link java.util.ArrayList ArrayList}, this returns |
| * {@code ArrayList<E>}. |
| */ |
| @Override |
| Type getGenericReturnType() { |
| Class<?> declaringClass = getDeclaringClass(); |
| TypeVariable<?>[] typeParams = declaringClass.getTypeParameters(); |
| if (typeParams.length > 0) { |
| return Types.newParameterizedType(declaringClass, typeParams); |
| } else { |
| return declaringClass; |
| } |
| } |
| |
| @Override |
| Type[] getGenericParameterTypes() { |
| Type[] types = constructor.getGenericParameterTypes(); |
| if (types.length > 0 && mayNeedHiddenThis()) { |
| Class<?>[] rawParamTypes = constructor.getParameterTypes(); |
| if (types.length == rawParamTypes.length |
| && rawParamTypes[0] == getDeclaringClass().getEnclosingClass()) { |
| // first parameter is the hidden 'this' |
| return Arrays.copyOfRange(types, 1, types.length); |
| } |
| } |
| return types; |
| } |
| |
| @Override |
| Type[] getGenericExceptionTypes() { |
| return constructor.getGenericExceptionTypes(); |
| } |
| |
| @Override |
| final Annotation[][] getParameterAnnotations() { |
| return constructor.getParameterAnnotations(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p>{@code [<E>]} will be returned for ArrayList's constructor. When both the class and the |
| * constructor have type parameters, the class parameters are prepended before those of the |
| * constructor's. This is an arbitrary rule since no existing language spec mandates one way or |
| * the other. From the declaration syntax, the class type parameter appears first, but the call |
| * syntax may show up in opposite order such as {@code new <A>Foo<B>()}. |
| */ |
| @Override |
| public final TypeVariable<?>[] getTypeParameters() { |
| TypeVariable<?>[] declaredByClass = getDeclaringClass().getTypeParameters(); |
| TypeVariable<?>[] declaredByConstructor = constructor.getTypeParameters(); |
| TypeVariable<?>[] result = |
| new TypeVariable<?>[declaredByClass.length + declaredByConstructor.length]; |
| System.arraycopy(declaredByClass, 0, result, 0, declaredByClass.length); |
| System.arraycopy( |
| declaredByConstructor, 0, result, declaredByClass.length, declaredByConstructor.length); |
| return result; |
| } |
| |
| @Override |
| public final boolean isOverridable() { |
| return false; |
| } |
| |
| @Override |
| public final boolean isVarArgs() { |
| return constructor.isVarArgs(); |
| } |
| |
| private boolean mayNeedHiddenThis() { |
| Class<?> declaringClass = constructor.getDeclaringClass(); |
| if (declaringClass.getEnclosingConstructor() != null) { |
| // Enclosed in a constructor, needs hidden this |
| return true; |
| } |
| Method enclosingMethod = declaringClass.getEnclosingMethod(); |
| if (enclosingMethod != null) { |
| // Enclosed in a method, if it's not static, must need hidden this. |
| return !Modifier.isStatic(enclosingMethod.getModifiers()); |
| } else { |
| // Strictly, this doesn't necessarily indicate a hidden 'this' in the case of |
| // static initializer. But there seems no way to tell in that case. :( |
| // This may cause issues when an anonymous class is created inside a static initializer, |
| // and the class's constructor's first parameter happens to be the enclosing class. |
| // In such case, we may mistakenly think that the class is within a non-static context |
| // and the first parameter is the hidden 'this'. |
| return declaringClass.getEnclosingClass() != null |
| && !Modifier.isStatic(declaringClass.getModifiers()); |
| } |
| } |
| } |
| } |