View Javadoc
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    *      http://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>
48   * final Method method = String.class.getMethod("length");
49   * final Function&lt;String, Integer&gt; 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>
64   * final Method method = String.class.getMethod("charAt", int.class);
65   * final BiFunction&lt;String, Integer, Character&gt; 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>
97       * final Method method = String.class.getMethod("charAt", int.class);
98       * final BiFunction&lt;String, Integer, Character&gt; 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>
182      * final Method method = String.class.getMethod("length");
183      * final Function&lt;String, Integer&gt; 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 {@linkplain 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 }