1 /*
2 * Copyright (c) 2019. Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15 package com.amazonaws.util;
16
17 import java.lang.reflect.InvocationTargetException;
18 import java.lang.reflect.Method;
19
20 import com.amazonaws.SdkClientException;
21 import com.amazonaws.annotation.SdkInternalApi;
22
23 /**
24 * This class acts as a proxy to invoke a specific method on objects of a specific class. It will use the JDK's
25 * reflection library to find and invoke the method.
26 * <p>
27 * The relatively expensive call to find the correct method on the class is lazy and will not be performed until the
28 * first invocation or until the invoker is explicitly initialized. Once found, the method is cached so repeated
29 * calls to initialize() or invoke() will not incur the reflection cost of searching for the method on the class.
30 * <p>
31 * Example:
32 * {@code
33 * ReflectionMethodInvoker<String, Integer> invoker =
34 * new ReflectionMethodInvoker<String, Integer>(String.class, Integer.class, "indexOf", String.class, int.class);
35 * invoker.invoke("ababab", "ab", 1); // This is equivalent to calling "ababab".indexOf("ab", 1);
36 * }
37 * @param <T> The class type that has the method to be invoked.
38 * @param <R> The expected return type of the method invocation.
39 */
40 @SdkInternalApi
41 public class ReflectionMethodInvoker<T, R> {
42 private final Class<T> clazz;
43 private final String methodName;
44 private final Class<R> returnType;
45 private final Class<?>[] parameterTypes;
46
47 private Method targetMethod;
48
49 /**
50 * Construct an instance of {@code ReflectionMethodInvoker}.
51 * <p>
52 * This constructor will not make any reflection calls as part of initialization; i.e. no validation of the
53 * existence of the given method signature will occur.
54 * @param clazz The class that has the method to be invoked.
55 * @param returnType The expected return class of the method invocation. The object returned by the invocation
56 * will be cast to this class.
57 * @param methodName The name of the method to invoke.
58 * @param parameterTypes The classes of the parameters of the method to invoke.
59 */
60 public ReflectionMethodInvoker(Class<T> clazz,
61 Class<R> returnType,
62 String methodName,
63 Class<?>... parameterTypes) {
64 this.clazz = clazz;
65 this.methodName = methodName;
66 this.returnType = returnType;
67 this.parameterTypes = parameterTypes;
68 }
69
70 /**
71 * Attempt to invoke the method this proxy targets for on the given object with the given arguments. If the
72 * invoker has not yet been initialized, an attempt to initialize the invoker will be made first. If the call
73 * succeeds the invoker will be in an initialized state after this call and future calls to the same method will
74 * not incur an initialization cost. If the call fails because the target method could not be found, the invoker
75 * will remain in an uninitialized state after the exception is thrown.
76 * @param obj The object to invoke the method on.
77 * @param args The arguments to pass to the method. These arguments must match the signature of the method.
78 * @return The returned value of the method cast to the 'returnType' class that this proxy was initialized with.
79 * @throws NoSuchMethodException if the JVM could not find a method matching the signature specified in the
80 * initialization of this proxy.
81 * @throws SdkClientException if any other exception is thrown when attempting to invoke the method or by the
82 * method itself. The cause of this exception will be the exception that was actually thrown.
83 */
84 public R invoke(T obj, Object... args) throws NoSuchMethodException {
85 Method targetMethod = getTargetMethod();
86
87 try {
88 Object rawResult = targetMethod.invoke(obj, args);
89 return returnType.cast(rawResult);
90 } catch (IllegalAccessException e) {
91 throw new SdkClientException(e);
92 } catch (InvocationTargetException e) {
93 throw new SdkClientException(e);
94 }
95 }
96
97 /**
98 * Initializes the method invoker by finding and caching the target method from the target class that will be used
99 * for subsequent invoke operations. If the invoker is already initialized this call will do nothing.
100 * @throws NoSuchMethodException if the JVM could not find a method matching the signature specified in the
101 * initialization of this proxy.
102 */
103 public void initialize() throws NoSuchMethodException {
104 getTargetMethod();
105 }
106
107 /**
108 * Gets the initialization state of the invoker.
109 * @return true if the invoker has been initialized and the method has been cached for invocation; otherwise false.
110 */
111 public boolean isInitialized() {
112 return targetMethod != null;
113 }
114
115 private Method getTargetMethod() throws NoSuchMethodException {
116 if (targetMethod != null) {
117 return targetMethod;
118 }
119
120 try {
121 targetMethod = clazz.getMethod(methodName, parameterTypes);
122 return targetMethod;
123 } catch (NullPointerException e) {
124 throw new SdkClientException(e);
125 }
126 }
127 }
128