1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.lang3.function;
19
20 import java.lang.invoke.MethodHandle;
21 import java.lang.invoke.MethodHandleProxies;
22 import java.lang.invoke.MethodHandles;
23 import java.lang.reflect.Method;
24 import java.util.Objects;
25 import java.util.function.BiConsumer;
26 import java.util.function.BiFunction;
27 import java.util.function.Function;
28 import java.util.function.Supplier;
29
30 import org.apache.commons.lang3.exception.UncheckedIllegalAccessException;
31
32 /**
33 * Converts {@link Method} objects to lambdas.
34 * <p>
35 * More specifically, produces instances of single-method interfaces which redirect calls to methods; see
36 * {@link #asInterfaceInstance(Class, Method)}.
37 * </p>
38 * <h2>Calling supplier methods with no arguments</h2>
39 * <p>
40 * If the interface's single-method defines no arguments, use {@link #asFunction(Method)} and then apply the function
41 * passing in the object to receive the method call.
42 * </p>
43 * <p>
44 * For example to invoke {@link String#length()}:
45 * </p>
46 *
47 * <pre>{@code
48 * final Method method = String.class.getMethod("length");
49 * final Function<String, Integer> function = MethodInvokers.asFunction(method);
50 * assertEquals(3, function.apply("ABC"));
51 * }</pre>
52 *
53 * <h2>Calling function methods with one argument</h2>
54 * <p>
55 * If the interface's single-method defines one argument, use {@link #asBiFunction(Method)} and then apply the function
56 * passing in the object to receive the method call. The second argument to the function is the only argument to the
57 * method.
58 * </p>
59 * <p>
60 * For example to invoke {@link String#charAt(int)}:
61 * </p>
62 *
63 * <pre>{@code
64 * final Method method = String.class.getMethod("charAt", int.class);
65 * final BiFunction<String, Integer, Character> function = MethodInvokers.asBiFunction(method);
66 * assertEquals('C', function.apply("ABC", 2));
67 * }</pre>
68 *
69 * @since 3.13.0
70 */
71 public final class MethodInvokers {
72
73 /**
74 * Produces a {@link BiConsumer} for a given <em>consumer</em> Method. For example, a classic setter method (as opposed
75 * to a fluent setter). You call the BiConsumer with two arguments: (1) the object receiving the method call, and (2)
76 * the method argument.
77 *
78 * @param <T> the type of the first argument to the operation: The type containing the Method.
79 * @param <U> the type of the second argument to the operation: The type of the method argument.
80 * @param method the method to invoke.
81 * @return a correctly-typed wrapper for the given target.
82 */
83 @SuppressWarnings("unchecked")
84 public static <T, U> BiConsumer<T, U> asBiConsumer(final Method method) {
85 return asInterfaceInstance(BiConsumer.class, method);
86 }
87
88 /**
89 * Produces a {@link BiFunction} for a given a <em>function</em> Method. You call the BiFunction with two arguments: (1)
90 * the object receiving the method call, and (2) the method argument. The BiFunction return type must match the method's
91 * return type.
92 * <p>
93 * For example to invoke {@link String#charAt(int)}:
94 * </p>
95 *
96 * <pre>{@code
97 * final Method method = String.class.getMethod("charAt", int.class);
98 * final BiFunction<String, Integer, Character> function = MethodInvokers.asBiFunction(method);
99 * assertEquals('C', function.apply("ABC", 2));
100 * }</pre>
101 *
102 * @param <T> the type of the first argument to the function: The type containing the method.
103 * @param <U> the type of the second argument to the function: the method argument type.
104 * @param <R> the type of the result of the function: The method return type.
105 * @param method the method to invoke.
106 * @return a correctly-typed wrapper for the given target.
107 */
108 @SuppressWarnings("unchecked")
109 public static <T, U, R> BiFunction<T, U, R> asBiFunction(final Method method) {
110 return asInterfaceInstance(BiFunction.class, method);
111 }
112
113 /**
114 * Produces a {@link FailableBiConsumer} for a given <em>consumer</em> Method. For example, a classic setter method (as
115 * opposed to a fluent setter). You call the FailableBiConsumer with two arguments: (1) the object receiving the method
116 * call, and (2) the method argument.
117 *
118 * @param <T> the type of the first argument to the operation: The type containing the Method.
119 * @param <U> the type of the second argument to the operation: The type of the method argument.
120 * @param method the method to invoke.
121 * @return a correctly-typed wrapper for the given target.
122 */
123 @SuppressWarnings("unchecked")
124 public static <T, U> FailableBiConsumer<T, U, Throwable> asFailableBiConsumer(final Method method) {
125 return asInterfaceInstance(FailableBiConsumer.class, method);
126 }
127
128 /**
129 * Produces a {@link FailableBiFunction} for a given a <em>function</em> Method. You call the FailableBiFunction with
130 * two arguments: (1) the object receiving the method call, and (2) the method argument. The BiFunction return type must
131 * match the method's return type.
132 *
133 * @param <T> the type of the first argument to the function: The type containing the method.
134 * @param <U> the type of the second argument to the function: the method argument type.
135 * @param <R> the type of the result of the function: The method return type.
136 * @param method the method to invoke.
137 * @return a correctly-typed wrapper for the given target.
138 */
139 @SuppressWarnings("unchecked")
140 public static <T, U, R> FailableBiFunction<T, U, R, Throwable> asFailableBiFunction(final Method method) {
141 return asInterfaceInstance(FailableBiFunction.class, method);
142 }
143
144 /**
145 * Produces a {@link FailableFunction} for a given a <em>supplier</em> Method. You call the Function with one argument:
146 * the object receiving the method call. The FailableFunction return type must match the method's return type.
147 *
148 * @param <T> the type of the first argument to the function: The type containing the method.
149 * @param <R> the type of the result of the function: The method return type.
150 * @param method the method to invoke.
151 * @return a correctly-typed wrapper for the given target.
152 */
153 @SuppressWarnings("unchecked")
154 public static <T, R> FailableFunction<T, R, Throwable> asFailableFunction(final Method method) {
155 return asInterfaceInstance(FailableFunction.class, method);
156 }
157
158 /**
159 * Produces a {@link FailableSupplier} for a given a <em>supplier</em> Method. The FailableSupplier return type must
160 * match the method's return type.
161 * <p>
162 * Only works with static methods.
163 * </p>
164 *
165 * @param <R> The Method return type.
166 * @param method the method to invoke.
167 * @return a correctly-typed wrapper for the given target.
168 */
169 @SuppressWarnings("unchecked")
170 public static <R> FailableSupplier<R, Throwable> asFailableSupplier(final Method method) {
171 return asInterfaceInstance(FailableSupplier.class, method);
172 }
173
174 /**
175 * Produces a {@link Function} for a given a <em>supplier</em> Method. You call the Function with one argument: the
176 * object receiving the method call. The Function return type must match the method's return type.
177 * <p>
178 * For example to invoke {@link String#length()}:
179 * </p>
180 *
181 * <pre>{@code
182 * final Method method = String.class.getMethod("length");
183 * final Function<String, Integer> function = MethodInvokers.asFunction(method);
184 * assertEquals(3, function.apply("ABC"));
185 * }</pre>
186 *
187 * @param <T> the type of the first argument to the function: The type containing the method.
188 * @param <R> the type of the result of the function: The method return type.
189 * @param method the method to invoke.
190 * @return a correctly-typed wrapper for the given target.
191 */
192 @SuppressWarnings("unchecked")
193 public static <T, R> Function<T, R> asFunction(final Method method) {
194 return asInterfaceInstance(Function.class, method);
195 }
196
197 /**
198 * Produces an instance of the given single-method interface which redirects its calls to the given method.
199 * <p>
200 * For the definition of "single-method", see {@link MethodHandleProxies#asInterfaceInstance(Class, MethodHandle)}.
201 * </p>
202 *
203 * @param <T> The interface type.
204 * @param interfaceClass a class object representing {@code T}.
205 * @param method the method to invoke.
206 * @return a correctly-typed wrapper for the given target.
207 * @see MethodHandleProxies#asInterfaceInstance(Class, MethodHandle)
208 */
209 public static <T> T asInterfaceInstance(final Class<T> interfaceClass, final Method method) {
210 return MethodHandleProxies.asInterfaceInstance(Objects.requireNonNull(interfaceClass, "interfaceClass"), unreflectUnchecked(method));
211 }
212
213 /**
214 * Produces a {@link Supplier} for a given a <em>supplier</em> Method. The Supplier return type must match the method's
215 * return type.
216 * <p>
217 * Only works with static methods.
218 * </p>
219 *
220 * @param <R> The Method return type.
221 * @param method the method to invoke.
222 * @return a correctly-typed wrapper for the given target.
223 */
224 @SuppressWarnings("unchecked")
225 public static <R> Supplier<R> asSupplier(final Method method) {
226 return asInterfaceInstance(Supplier.class, method);
227 }
228
229 /**
230 * Throws NullPointerException if {@code method} is {@code null}.
231 *
232 * @param method The method to test.
233 * @return The given method.
234 * @throws NullPointerException if {@code method} is {@code null}.
235 */
236 private static Method requireMethod(final Method method) {
237 return Objects.requireNonNull(method, "method");
238 }
239
240 private static MethodHandle unreflect(final Method method) throws IllegalAccessException {
241 return MethodHandles.lookup().unreflect(requireMethod(method));
242 }
243
244 private static MethodHandle unreflectUnchecked(final Method method) {
245 try {
246 return unreflect(method);
247 } catch (final IllegalAccessException e) {
248 throw new UncheckedIllegalAccessException(e);
249 }
250 }
251
252 /**
253 * No need to create instances.
254 */
255 private MethodInvokers() {
256 // noop
257 }
258
259 }