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