001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.reflect;
018
019import static com.google.common.base.Preconditions.checkNotNull;
020
021import com.google.common.annotations.Beta;
022import com.google.common.collect.ImmutableList;
023
024import java.lang.annotation.Annotation;
025import java.lang.reflect.AccessibleObject;
026import java.lang.reflect.Constructor;
027import java.lang.reflect.GenericDeclaration;
028import java.lang.reflect.InvocationTargetException;
029import java.lang.reflect.Member;
030import java.lang.reflect.Method;
031import java.lang.reflect.Modifier;
032import java.lang.reflect.Type;
033import java.lang.reflect.TypeVariable;
034import java.util.Arrays;
035
036import javax.annotation.Nullable;
037
038/**
039 * Wrapper around either a {@link Method} or a {@link Constructor}.
040 * Convenience API is provided to make common reflective operation easier to deal with,
041 * such as {@link #isPublic}, {@link #getParameters} etc.
042 *
043 * <p>In addition to convenience methods, {@link TypeToken#method} and {@link
044 * TypeToken#constructor} will resolve the type parameters of the method or constructor in the
045 * context of the owner type, which may be a subtype of the declaring class. For example:
046 * <pre>   {@code
047 *
048 *   Method getMethod = List.class.getMethod("get", int.class);
049 *   Invokable<List<String>, ?> invokable = new TypeToken<List<String>>() {}.method(getMethod);
050 *   assertEquals(TypeToken.of(String.class), invokable.getReturnType()); // Not Object.class!
051 *   assertEquals(new TypeToken<List<String>>() {}, invokable.getOwnerType());}</pre>
052 * 
053 * @param <T> the type that owns this method or constructor.
054 * @param <R> the return type of (or supertype thereof) the method or the declaring type of the
055 *            constructor.
056 * @author Ben Yu
057 * @since 14.0
058 */
059@Beta
060public abstract class Invokable<T, R> extends Element implements GenericDeclaration {
061
062  <M extends AccessibleObject & Member> Invokable(M member) {
063    super(member);
064  }
065
066  /** Returns {@link Invokable} of {@code method}. */
067  public static Invokable<?, Object> from(Method method) {
068    return new MethodInvokable<Object>(method);
069  }
070
071  /** Returns {@link Invokable} of {@code constructor}. */
072  public static <T> Invokable<T, T> from(Constructor<T> constructor) {
073    return new ConstructorInvokable<T>(constructor);
074  }
075
076  /**
077   * Returns {@code true} if this is an overridable method. Constructors, private, static or final
078   * methods, or methods declared by final classes are not overridable.
079   */
080  public abstract boolean isOverridable();
081
082  /** Returns {@code true} if this was declared to take a variable number of arguments. */
083  public abstract boolean isVarArgs();
084
085  /**
086   * Invokes with {@code receiver} as 'this' and {@code args} passed to the underlying method
087   * and returns the return value; or calls the underlying constructor with {@code args} and returns
088   * the constructed instance.
089   *
090   * @throws IllegalAccessException if this {@code Constructor} object enforces Java language
091   *         access control and the underlying method or constructor is inaccessible.
092   * @throws IllegalArgumentException if the number of actual and formal parameters differ;
093   *         if an unwrapping conversion for primitive arguments fails; or if, after possible
094   *         unwrapping, a parameter value cannot be converted to the corresponding formal
095   *         parameter type by a method invocation conversion.
096   * @throws InvocationTargetException if the underlying method or constructor throws an exception.
097   */
098  // All subclasses are owned by us and we'll make sure to get the R type right.
099  @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);
146   * }</pre>
147   */
148  public final <R1 extends R> Invokable<T, R1> returning(Class<R1> returnType) {
149    return returning(TypeToken.of(returnType));
150  }
151
152  /** Explicitly specifies the return type of this {@code Invokable}. */
153  public final <R1 extends R> Invokable<T, R1> returning(TypeToken<R1> returnType) {
154    if (!returnType.isAssignableFrom(getReturnType())) {
155      throw new IllegalArgumentException(
156          "Invokable is known to return " + getReturnType() + ", not " + returnType);
157    }
158    @SuppressWarnings("unchecked") // guarded by previous check
159    Invokable<T, R1> specialized = (Invokable<T, R1>) this;
160    return specialized;
161  }
162
163  @SuppressWarnings("unchecked") // The declaring class is T's raw class, or one of its supertypes.
164  @Override public final Class<? super T> getDeclaringClass() {
165    return (Class<? super T>) super.getDeclaringClass();
166  }
167
168  /** Returns the type of {@code T}. */
169  // Overridden in TypeToken#method() and TypeToken#constructor()
170  @SuppressWarnings("unchecked") // The declaring class is T.
171  public TypeToken<T> getOwnerType() {
172    return (TypeToken<T>) TypeToken.of(getDeclaringClass());
173  }
174
175  abstract Object invokeInternal(@Nullable Object receiver, Object[] args)
176      throws InvocationTargetException, IllegalAccessException;
177
178  abstract Type[] getGenericParameterTypes();
179
180  /** This should never return a type that's not a subtype of Throwable. */
181  abstract Type[] getGenericExceptionTypes();
182
183  abstract Annotation[][] getParameterAnnotations();
184
185  abstract Type getGenericReturnType();
186  
187  static class MethodInvokable<T> extends Invokable<T, Object> {
188
189    private final Method method;
190
191    MethodInvokable(Method method) {
192      super(method);
193      this.method = method;
194    }
195
196    @Override final Object invokeInternal(@Nullable Object receiver, Object[] args)
197        throws InvocationTargetException, IllegalAccessException {
198      return method.invoke(receiver, args);
199    }
200
201    @Override Type getGenericReturnType() {
202      return method.getGenericReturnType();
203    }
204
205    @Override Type[] getGenericParameterTypes() {
206      return method.getGenericParameterTypes();
207    }
208
209    @Override Type[] getGenericExceptionTypes() {
210      return method.getGenericExceptionTypes();
211    }
212
213    @Override final Annotation[][] getParameterAnnotations() {
214      return method.getParameterAnnotations();
215    }
216
217    @Override public final TypeVariable<?>[] getTypeParameters() {
218      return method.getTypeParameters();
219    }
220
221    @Override public final boolean isOverridable() {
222      return  !(isFinal() || isPrivate() || isStatic()
223          || Modifier.isFinal(getDeclaringClass().getModifiers()));
224    }
225
226    @Override public final boolean isVarArgs() {
227      return method.isVarArgs();
228    }
229  }
230
231  static class ConstructorInvokable<T> extends Invokable<T, T> {
232
233    private final Constructor<?> constructor;
234
235    ConstructorInvokable(Constructor<?> constructor) {
236      super(constructor);
237      this.constructor = constructor;
238    }
239
240    @Override final Object invokeInternal(@Nullable Object receiver, Object[] args)
241        throws InvocationTargetException, IllegalAccessException {
242      try {
243        return constructor.newInstance(args);
244      } catch (InstantiationException e) {
245        throw new RuntimeException(constructor + " failed.", e);
246      }
247    }
248
249    @Override Type getGenericReturnType() {
250      return constructor.getDeclaringClass();
251    }
252
253    @Override Type[] getGenericParameterTypes() {
254      Type[] types = constructor.getGenericParameterTypes();
255      Class<?> declaringClass = constructor.getDeclaringClass();
256      if (!Modifier.isStatic(declaringClass.getModifiers())
257          && declaringClass.getEnclosingClass() != null) {
258        if (types.length == constructor.getParameterTypes().length) {
259          // first parameter is the hidden 'this'
260          return Arrays.copyOfRange(types, 1, types.length);
261        }
262      }
263      return types;
264    }
265
266    @Override Type[] getGenericExceptionTypes() {
267      return constructor.getGenericExceptionTypes();
268    }
269
270    @Override final Annotation[][] getParameterAnnotations() {
271      return constructor.getParameterAnnotations();
272    }
273
274    @Override public final TypeVariable<?>[] getTypeParameters() {
275      return constructor.getTypeParameters();
276    }
277
278    @Override public final boolean isOverridable() {
279      return false;
280    }
281
282    @Override public final boolean isVarArgs() {
283      return constructor.isVarArgs();
284    }
285  }
286}