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.classint.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