View Javadoc
1   /*
2    * Copyright (C) 2012 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  
15  package com.google.common.reflect;
16  
17  import static com.google.common.base.Preconditions.checkNotNull;
18  
19  import com.google.common.annotations.Beta;
20  import com.google.common.collect.ImmutableList;
21  import com.google.errorprone.annotations.CanIgnoreReturnValue;
22  import java.lang.annotation.Annotation;
23  import java.lang.reflect.AccessibleObject;
24  import java.lang.reflect.Constructor;
25  import java.lang.reflect.GenericDeclaration;
26  import java.lang.reflect.InvocationTargetException;
27  import java.lang.reflect.Member;
28  import java.lang.reflect.Method;
29  import java.lang.reflect.Modifier;
30  import java.lang.reflect.Type;
31  import java.lang.reflect.TypeVariable;
32  import java.util.Arrays;
33  import javax.annotation.Nullable;
34  
35  /**
36   * Wrapper around either a {@link Method} or a {@link Constructor}. Convenience API is provided to
37   * make common reflective operation easier to deal with, such as {@link #isPublic},
38   * {@link #getParameters} etc.
39   *
40   * <p>In addition to convenience methods, {@link TypeToken#method} and {@link TypeToken#constructor}
41   * will resolve the type parameters of the method or constructor in the context of the owner type,
42   * which may be a subtype of the declaring class. For example:
43   *
44   * <pre>   {@code
45   *   Method getMethod = List.class.getMethod("get", int.class);
46   *   Invokable<List<String>, ?> invokable = new TypeToken<List<String>>() {}.method(getMethod);
47   *   assertEquals(TypeToken.of(String.class), invokable.getReturnType()); // Not Object.class!
48   *   assertEquals(new TypeToken<List<String>>() {}, invokable.getOwnerType());}</pre>
49   *
50   * @param <T> the type that owns this method or constructor.
51   * @param <R> the return type of (or supertype thereof) the method or the declaring type of the
52   *     constructor.
53   * @author Ben Yu
54   * @since 14.0
55   */
56  @Beta
57  public abstract class Invokable<T, R> extends Element implements GenericDeclaration {
58  
59    <M extends AccessibleObject & Member> Invokable(M member) {
60      super(member);
61    }
62  
63    /** Returns {@link Invokable} of {@code method}. */
64    public static Invokable<?, Object> from(Method method) {
65      return new MethodInvokable<>(method);
66    }
67  
68    /** Returns {@link Invokable} of {@code constructor}. */
69    public static <T> Invokable<T, T> from(Constructor<T> constructor) {
70      return new ConstructorInvokable<T>(constructor);
71    }
72  
73    /**
74     * Returns {@code true} if this is an overridable method. Constructors, private, static or final
75     * methods, or methods declared by final classes are not overridable.
76     */
77    public abstract boolean isOverridable();
78  
79    /** Returns {@code true} if this was declared to take a variable number of arguments. */
80    public abstract boolean isVarArgs();
81  
82    /**
83     * Invokes with {@code receiver} as 'this' and {@code args} passed to the underlying method and
84     * returns the return value; or calls the underlying constructor with {@code args} and returns the
85     * constructed instance.
86     *
87     * @throws IllegalAccessException if this {@code Constructor} object enforces Java language access
88     *     control and the underlying method or constructor is inaccessible.
89     * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an
90     *     unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a
91     *     parameter value cannot be converted to the corresponding formal parameter type by a method
92     *     invocation conversion.
93     * @throws InvocationTargetException if the underlying method or constructor throws an exception.
94     */
95    // All subclasses are owned by us and we'll make sure to get the R type right.
96    @SuppressWarnings("unchecked")
97    @CanIgnoreReturnValue
98    public final R invoke(@Nullable T receiver, Object... args)
99        throws InvocationTargetException, IllegalAccessException {
100     return (R) invokeInternal(receiver, checkNotNull(args));
101   }
102 
103   /** Returns the return type of this {@code Invokable}. */
104   // All subclasses are owned by us and we'll make sure to get the R type right.
105   @SuppressWarnings("unchecked")
106   public final TypeToken<? extends R> getReturnType() {
107     return (TypeToken<? extends R>) TypeToken.of(getGenericReturnType());
108   }
109 
110   /**
111    * Returns all declared parameters of this {@code Invokable}. Note that if this is a constructor
112    * of a non-static inner class, unlike {@link Constructor#getParameterTypes}, the hidden
113    * {@code this} parameter of the enclosing class is excluded from the returned parameters.
114    */
115   public final ImmutableList<Parameter> getParameters() {
116     Type[] parameterTypes = getGenericParameterTypes();
117     Annotation[][] annotations = getParameterAnnotations();
118     ImmutableList.Builder<Parameter> builder = ImmutableList.builder();
119     for (int i = 0; i < parameterTypes.length; i++) {
120       builder.add(new Parameter(this, i, TypeToken.of(parameterTypes[i]), annotations[i]));
121     }
122     return builder.build();
123   }
124 
125   /** Returns all declared exception types of this {@code Invokable}. */
126   public final ImmutableList<TypeToken<? extends Throwable>> getExceptionTypes() {
127     ImmutableList.Builder<TypeToken<? extends Throwable>> builder = ImmutableList.builder();
128     for (Type type : getGenericExceptionTypes()) {
129       // getGenericExceptionTypes() will never return a type that's not exception
130       @SuppressWarnings("unchecked")
131       TypeToken<? extends Throwable> exceptionType =
132           (TypeToken<? extends Throwable>) TypeToken.of(type);
133       builder.add(exceptionType);
134     }
135     return builder.build();
136   }
137 
138   /**
139    * Explicitly specifies the return type of this {@code Invokable}. For example:
140    * <pre>   {@code
141    *   Method factoryMethod = Person.class.getMethod("create");
142    *   Invokable<?, Person> factory = Invokable.of(getNameMethod).returning(Person.class);}</pre>
143    */
144   public final <R1 extends R> Invokable<T, R1> returning(Class<R1> returnType) {
145     return returning(TypeToken.of(returnType));
146   }
147 
148   /** Explicitly specifies the return type of this {@code Invokable}. */
149   public final <R1 extends R> Invokable<T, R1> returning(TypeToken<R1> returnType) {
150     if (!returnType.isSupertypeOf(getReturnType())) {
151       throw new IllegalArgumentException(
152           "Invokable is known to return " + getReturnType() + ", not " + returnType);
153     }
154     @SuppressWarnings("unchecked") // guarded by previous check
155     Invokable<T, R1> specialized = (Invokable<T, R1>) this;
156     return specialized;
157   }
158 
159   @SuppressWarnings("unchecked") // The declaring class is T's raw class, or one of its supertypes.
160   @Override
161   public final Class<? super T> getDeclaringClass() {
162     return (Class<? super T>) super.getDeclaringClass();
163   }
164 
165   /** Returns the type of {@code T}. */
166   // Overridden in TypeToken#method() and TypeToken#constructor()
167   @SuppressWarnings("unchecked") // The declaring class is T.
168   @Override
169   public TypeToken<T> getOwnerType() {
170     return (TypeToken<T>) TypeToken.of(getDeclaringClass());
171   }
172 
173   abstract Object invokeInternal(@Nullable Object receiver, Object[] args)
174       throws InvocationTargetException, IllegalAccessException;
175 
176   abstract Type[] getGenericParameterTypes();
177 
178   /** This should never return a type that's not a subtype of Throwable. */
179   abstract Type[] getGenericExceptionTypes();
180 
181   abstract Annotation[][] getParameterAnnotations();
182 
183   abstract Type getGenericReturnType();
184 
185   static class MethodInvokable<T> extends Invokable<T, Object> {
186 
187     final Method method;
188 
189     MethodInvokable(Method method) {
190       super(method);
191       this.method = method;
192     }
193 
194     @Override
195     final Object invokeInternal(@Nullable Object receiver, Object[] args)
196         throws InvocationTargetException, IllegalAccessException {
197       return method.invoke(receiver, args);
198     }
199 
200     @Override
201     Type getGenericReturnType() {
202       return method.getGenericReturnType();
203     }
204 
205     @Override
206     Type[] getGenericParameterTypes() {
207       return method.getGenericParameterTypes();
208     }
209 
210     @Override
211     Type[] getGenericExceptionTypes() {
212       return method.getGenericExceptionTypes();
213     }
214 
215     @Override
216     final Annotation[][] getParameterAnnotations() {
217       return method.getParameterAnnotations();
218     }
219 
220     @Override
221     public final TypeVariable<?>[] getTypeParameters() {
222       return method.getTypeParameters();
223     }
224 
225     @Override
226     public final boolean isOverridable() {
227       return !(isFinal()
228           || isPrivate()
229           || isStatic()
230           || Modifier.isFinal(getDeclaringClass().getModifiers()));
231     }
232 
233     @Override
234     public final boolean isVarArgs() {
235       return method.isVarArgs();
236     }
237   }
238 
239   static class ConstructorInvokable<T> extends Invokable<T, T> {
240 
241     final Constructor<?> constructor;
242 
243     ConstructorInvokable(Constructor<?> constructor) {
244       super(constructor);
245       this.constructor = constructor;
246     }
247 
248     @Override
249     final Object invokeInternal(@Nullable Object receiver, Object[] args)
250         throws InvocationTargetException, IllegalAccessException {
251       try {
252         return constructor.newInstance(args);
253       } catch (InstantiationException e) {
254         throw new RuntimeException(constructor + " failed.", e);
255       }
256     }
257 
258     /**
259      * If the class is parameterized, such as {@link java.util.ArrayList ArrayList}, this returns
260      * {@code ArrayList<E>}.
261      */
262     @Override
263     Type getGenericReturnType() {
264       Class<?> declaringClass = getDeclaringClass();
265       TypeVariable<?>[] typeParams = declaringClass.getTypeParameters();
266       if (typeParams.length > 0) {
267         return Types.newParameterizedType(declaringClass, typeParams);
268       } else {
269         return declaringClass;
270       }
271     }
272 
273     @Override
274     Type[] getGenericParameterTypes() {
275       Type[] types = constructor.getGenericParameterTypes();
276       if (types.length > 0 && mayNeedHiddenThis()) {
277         Class<?>[] rawParamTypes = constructor.getParameterTypes();
278         if (types.length == rawParamTypes.length
279             && rawParamTypes[0] == getDeclaringClass().getEnclosingClass()) {
280           // first parameter is the hidden 'this'
281           return Arrays.copyOfRange(types, 1, types.length);
282         }
283       }
284       return types;
285     }
286 
287     @Override
288     Type[] getGenericExceptionTypes() {
289       return constructor.getGenericExceptionTypes();
290     }
291 
292     @Override
293     final Annotation[][] getParameterAnnotations() {
294       return constructor.getParameterAnnotations();
295     }
296 
297     /**
298      * {@inheritDoc}
299      *
300      * {@code [<E>]} will be returned for ArrayList's constructor. When both the class and the
301      * constructor have type parameters, the class parameters are prepended before those of the
302      * constructor's. This is an arbitrary rule since no existing language spec mandates one way or
303      * the other. From the declaration syntax, the class type parameter appears first, but the call
304      * syntax may show up in opposite order such as {@code new <A>Foo<B>()}.
305      */
306     @Override
307     public final TypeVariable<?>[] getTypeParameters() {
308       TypeVariable<?>[] declaredByClass = getDeclaringClass().getTypeParameters();
309       TypeVariable<?>[] declaredByConstructor = constructor.getTypeParameters();
310       TypeVariable<?>[] result =
311           new TypeVariable<?>[declaredByClass.length + declaredByConstructor.length];
312       System.arraycopy(declaredByClass, 0, result, 0, declaredByClass.length);
313       System.arraycopy(
314           declaredByConstructor, 0,
315           result, declaredByClass.length,
316           declaredByConstructor.length);
317       return result;
318     }
319 
320     @Override
321     public final boolean isOverridable() {
322       return false;
323     }
324 
325     @Override
326     public final boolean isVarArgs() {
327       return constructor.isVarArgs();
328     }
329 
330     private boolean mayNeedHiddenThis() {
331       Class<?> declaringClass = constructor.getDeclaringClass();
332       if (declaringClass.getEnclosingConstructor() != null) {
333         // Enclosed in a constructor, needs hidden this
334         return true;
335       }
336       Method enclosingMethod = declaringClass.getEnclosingMethod();
337       if (enclosingMethod != null) {
338         // Enclosed in a method, if it's not static, must need hidden this.
339         return !Modifier.isStatic(enclosingMethod.getModifiers());
340       } else {
341         // Strictly, this doesn't necessarily indicate a hidden 'this' in the case of
342         // static initializer. But there seems no way to tell in that case. :(
343         // This may cause issues when an anonymous class is created inside a static initializer,
344         // and the class's constructor's first parameter happens to be the enclosing class.
345         // In such case, we may mistakenly think that the class is within a non-static context
346         // and the first parameter is the hidden 'this'.
347         return declaringClass.getEnclosingClass() != null
348             && !Modifier.isStatic(declaringClass.getModifiers());
349       }
350     }
351   }
352 }