001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.reflect;
016
017import static com.google.common.base.Preconditions.checkNotNull;
018
019import com.google.common.annotations.Beta;
020import com.google.common.collect.ImmutableList;
021import com.google.errorprone.annotations.CanIgnoreReturnValue;
022import java.lang.annotation.Annotation;
023import java.lang.reflect.AccessibleObject;
024import java.lang.reflect.Constructor;
025import java.lang.reflect.GenericDeclaration;
026import java.lang.reflect.InvocationTargetException;
027import java.lang.reflect.Member;
028import java.lang.reflect.Method;
029import java.lang.reflect.Modifier;
030import java.lang.reflect.Type;
031import java.lang.reflect.TypeVariable;
032import java.util.Arrays;
033import javax.annotation.Nullable;
034
035/**
036 * Wrapper around either a {@link Method} or a {@link Constructor}. Convenience API is provided to
037 * make common reflective operation easier to deal with, such as {@link #isPublic},
038 * {@link #getParameters} etc.
039 *
040 * <p>In addition to convenience methods, {@link TypeToken#method} and {@link TypeToken#constructor}
041 * will resolve the type parameters of the method or constructor in the context of the owner type,
042 * which may be a subtype of the declaring class. For example:
043 *
044 * <pre>   {@code
045 *   Method getMethod = List.class.getMethod("get", int.class);
046 *   Invokable<List<String>, ?> invokable = new TypeToken<List<String>>() {}.method(getMethod);
047 *   assertEquals(TypeToken.of(String.class), invokable.getReturnType()); // Not Object.class!
048 *   assertEquals(new TypeToken<List<String>>() {}, invokable.getOwnerType());}</pre>
049 *
050 * @param <T> the type that owns this method or constructor.
051 * @param <R> the return type of (or supertype thereof) the method or the declaring type of the
052 *     constructor.
053 * @author Ben Yu
054 * @since 14.0
055 */
056@Beta
057public abstract class Invokable<T, R> extends Element implements GenericDeclaration {
058
059  <M extends AccessibleObject & Member> Invokable(M member) {
060    super(member);
061  }
062
063  /** Returns {@link Invokable} of {@code method}. */
064  public static Invokable<?, Object> from(Method method) {
065    return new MethodInvokable<Object>(method);
066  }
067
068  /** Returns {@link Invokable} of {@code constructor}. */
069  public static <T> Invokable<T, T> from(Constructor<T> constructor) {
070    return new ConstructorInvokable<T>(constructor);
071  }
072
073  /**
074   * Returns {@code true} if this is an overridable method. Constructors, private, static or final
075   * methods, or methods declared by final classes are not overridable.
076   */
077  public abstract boolean isOverridable();
078
079  /** Returns {@code true} if this was declared to take a variable number of arguments. */
080  public abstract boolean isVarArgs();
081
082  /**
083   * Invokes with {@code receiver} as 'this' and {@code args} passed to the underlying method and
084   * returns the return value; or calls the underlying constructor with {@code args} and returns the
085   * constructed instance.
086   *
087   * @throws IllegalAccessException if this {@code Constructor} object enforces Java language access
088   *     control and the underlying method or constructor is inaccessible.
089   * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an
090   *     unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a
091   *     parameter value cannot be converted to the corresponding formal parameter type by a method
092   *     invocation conversion.
093   * @throws InvocationTargetException if the underlying method or constructor throws an exception.
094   */
095  // All subclasses are owned by us and we'll make sure to get the R type right.
096  @SuppressWarnings("unchecked")
097  @CanIgnoreReturnValue
098  public final R invoke(@Nullable T receiver, Object... args)
099      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 ArrayList}, this returns {@code ArrayList<E>}.
260     */
261    @Override
262    Type getGenericReturnType() {
263      Class<?> declaringClass = getDeclaringClass();
264      TypeVariable<?>[] typeParams = declaringClass.getTypeParameters();
265      if (typeParams.length > 0) {
266        return Types.newParameterizedType(declaringClass, typeParams);
267      } else {
268        return declaringClass;
269      }
270    }
271
272    @Override
273    Type[] getGenericParameterTypes() {
274      Type[] types = constructor.getGenericParameterTypes();
275      if (types.length > 0 && mayNeedHiddenThis()) {
276        Class<?>[] rawParamTypes = constructor.getParameterTypes();
277        if (types.length == rawParamTypes.length
278            && rawParamTypes[0] == getDeclaringClass().getEnclosingClass()) {
279          // first parameter is the hidden 'this'
280          return Arrays.copyOfRange(types, 1, types.length);
281        }
282      }
283      return types;
284    }
285
286    @Override
287    Type[] getGenericExceptionTypes() {
288      return constructor.getGenericExceptionTypes();
289    }
290
291    @Override
292    final Annotation[][] getParameterAnnotations() {
293      return constructor.getParameterAnnotations();
294    }
295
296    /**
297     * {@inheritDoc}
298     *
299     * {@code [<E>]} will be returned for ArrayList's constructor. When both the class and the
300     * constructor have type parameters, the class parameters are prepended before those of the
301     * constructor's. This is an arbitrary rule since no existing language spec mandates one way or
302     * the other. From the declaration syntax, the class type parameter appears first, but the call
303     * syntax may show up in opposite order such as {@code new <A>Foo<B>()}.
304     */
305    @Override
306    public final TypeVariable<?>[] getTypeParameters() {
307      TypeVariable<?>[] declaredByClass = getDeclaringClass().getTypeParameters();
308      TypeVariable<?>[] declaredByConstructor = constructor.getTypeParameters();
309      TypeVariable<?>[] result =
310          new TypeVariable<?>[declaredByClass.length + declaredByConstructor.length];
311      System.arraycopy(declaredByClass, 0, result, 0, declaredByClass.length);
312      System.arraycopy(
313          declaredByConstructor, 0,
314          result, declaredByClass.length,
315          declaredByConstructor.length);
316      return result;
317    }
318
319    @Override
320    public final boolean isOverridable() {
321      return false;
322    }
323
324    @Override
325    public final boolean isVarArgs() {
326      return constructor.isVarArgs();
327    }
328
329    private boolean mayNeedHiddenThis() {
330      Class<?> declaringClass = constructor.getDeclaringClass();
331      if (declaringClass.getEnclosingConstructor() != null) {
332        // Enclosed in a constructor, needs hidden this
333        return true;
334      }
335      Method enclosingMethod = declaringClass.getEnclosingMethod();
336      if (enclosingMethod != null) {
337        // Enclosed in a method, if it's not static, must need hidden this.
338        return !Modifier.isStatic(enclosingMethod.getModifiers());
339      } else {
340        // Strictly, this doesn't necessarily indicate a hidden 'this' in the case of
341        // static initializer. But there seems no way to tell in that case. :(
342        // This may cause issues when an anonymous class is created inside a static initializer,
343        // and the class's constructor's first parameter happens to be the enclosing class.
344        // In such case, we may mistakenly think that the class is within a non-static context
345        // and the first parameter is the hidden 'this'.
346        return declaringClass.getEnclosingClass() != null
347            && !Modifier.isStatic(declaringClass.getModifiers());
348      }
349    }
350  }
351}