MethodInvokers.java

  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. package org.apache.commons.lang3.function;

  18. import java.lang.invoke.MethodHandle;
  19. import java.lang.invoke.MethodHandleProxies;
  20. import java.lang.invoke.MethodHandles;
  21. import java.lang.reflect.Method;
  22. import java.util.Objects;
  23. import java.util.function.BiConsumer;
  24. import java.util.function.BiFunction;
  25. import java.util.function.Function;
  26. import java.util.function.Supplier;

  27. import org.apache.commons.lang3.exception.UncheckedIllegalAccessException;

  28. /**
  29.  * Converts {@link Method} objects to lambdas.
  30.  * <p>
  31.  * More specifically, produces instances of single-method interfaces which redirect calls to methods; see
  32.  * {@link #asInterfaceInstance(Class, Method)}.
  33.  * </p>
  34.  * <h2>Calling supplier methods with no arguments</h2>
  35.  * <p>
  36.  * If the interface's single-method defines no arguments, use {@link #asFunction(Method)} and then apply the function
  37.  * passing in the object to receive the method call.
  38.  * </p>
  39.  * <p>
  40.  * For example to invoke {@link String#length()}:
  41.  * </p>
  42.  *
  43.  * <pre>{@code
  44.  * final Method method = String.class.getMethod("length");
  45.  * final Function<String, Integer> function = MethodInvokers.asFunction(method);
  46.  * assertEquals(3, function.apply("ABC"));
  47.  * }</pre>
  48.  *
  49.  * <h2>Calling function methods with one argument</h2>
  50.  * <p>
  51.  * If the interface's single-method defines one argument, use {@link #asBiFunction(Method)} and then apply the function
  52.  * passing in the object to receive the method call. The second argument to the function is the only argument to the
  53.  * method.
  54.  * </p>
  55.  * <p>
  56.  * For example to invoke {@link String#charAt(int)}:
  57.  * </p>
  58.  *
  59.  * <pre>{@code
  60.  * final Method method = String.class.getMethod("charAt", int.class);
  61.  * final BiFunction<String, Integer, Character> function = MethodInvokers.asBiFunction(method);
  62.  * assertEquals('C', function.apply("ABC", 2));
  63.  * }</pre>
  64.  *
  65.  * @since 3.13.0
  66.  */
  67. public final class MethodInvokers {

  68.     /**
  69.      * Produces a {@link BiConsumer} for a given <em>consumer</em> Method. For example, a classic setter method (as opposed
  70.      * to a fluent setter). You call the BiConsumer with two arguments: (1) the object receiving the method call, and (2)
  71.      * the method argument.
  72.      *
  73.      * @param <T> the type of the first argument to the operation: The type containing the Method.
  74.      * @param <U> the type of the second argument to the operation: The type of the method argument.
  75.      * @param method the method to invoke.
  76.      * @return a correctly-typed wrapper for the given target.
  77.      */
  78.     @SuppressWarnings("unchecked")
  79.     public static <T, U> BiConsumer<T, U> asBiConsumer(final Method method) {
  80.         return asInterfaceInstance(BiConsumer.class, method);
  81.     }

  82.     /**
  83.      * Produces a {@link BiFunction} for a given a <em>function</em> Method. You call the BiFunction with two arguments: (1)
  84.      * the object receiving the method call, and (2) the method argument. The BiFunction return type must match the method's
  85.      * return type.
  86.      * <p>
  87.      * For example to invoke {@link String#charAt(int)}:
  88.      * </p>
  89.      *
  90.      * <pre>{@code
  91.      * final Method method = String.class.getMethod("charAt", int.class);
  92.      * final BiFunction<String, Integer, Character> function = MethodInvokers.asBiFunction(method);
  93.      * assertEquals('C', function.apply("ABC", 2));
  94.      * }</pre>
  95.      *
  96.      * @param <T> the type of the first argument to the function: The type containing the method.
  97.      * @param <U> the type of the second argument to the function: the method argument type.
  98.      * @param <R> the type of the result of the function: The method return type.
  99.      * @param method the method to invoke.
  100.      * @return a correctly-typed wrapper for the given target.
  101.      */
  102.     @SuppressWarnings("unchecked")
  103.     public static <T, U, R> BiFunction<T, U, R> asBiFunction(final Method method) {
  104.         return asInterfaceInstance(BiFunction.class, method);
  105.     }

  106.     /**
  107.      * Produces a {@link FailableBiConsumer} for a given <em>consumer</em> Method. For example, a classic setter method (as
  108.      * opposed to a fluent setter). You call the FailableBiConsumer with two arguments: (1) the object receiving the method
  109.      * call, and (2) the method argument.
  110.      *
  111.      * @param <T> the type of the first argument to the operation: The type containing the Method.
  112.      * @param <U> the type of the second argument to the operation: The type of the method argument.
  113.      * @param method the method to invoke.
  114.      * @return a correctly-typed wrapper for the given target.
  115.      */
  116.     @SuppressWarnings("unchecked")
  117.     public static <T, U> FailableBiConsumer<T, U, Throwable> asFailableBiConsumer(final Method method) {
  118.         return asInterfaceInstance(FailableBiConsumer.class, method);
  119.     }

  120.     /**
  121.      * Produces a {@link FailableBiFunction} for a given a <em>function</em> Method. You call the FailableBiFunction with
  122.      * two arguments: (1) the object receiving the method call, and (2) the method argument. The BiFunction return type must
  123.      * match the method's return type.
  124.      *
  125.      * @param <T> the type of the first argument to the function: The type containing the method.
  126.      * @param <U> the type of the second argument to the function: the method argument type.
  127.      * @param <R> the type of the result of the function: The method return type.
  128.      * @param method the method to invoke.
  129.      * @return a correctly-typed wrapper for the given target.
  130.      */
  131.     @SuppressWarnings("unchecked")
  132.     public static <T, U, R> FailableBiFunction<T, U, R, Throwable> asFailableBiFunction(final Method method) {
  133.         return asInterfaceInstance(FailableBiFunction.class, method);
  134.     }

  135.     /**
  136.      * Produces a {@link FailableFunction} for a given a <em>supplier</em> Method. You call the Function with one argument:
  137.      * the object receiving the method call. The FailableFunction return type must match the method's return type.
  138.      *
  139.      * @param <T> the type of the first argument to the function: The type containing the method.
  140.      * @param <R> the type of the result of the function: The method return type.
  141.      * @param method the method to invoke.
  142.      * @return a correctly-typed wrapper for the given target.
  143.      */
  144.     @SuppressWarnings("unchecked")
  145.     public static <T, R> FailableFunction<T, R, Throwable> asFailableFunction(final Method method) {
  146.         return asInterfaceInstance(FailableFunction.class, method);
  147.     }

  148.     /**
  149.      * Produces a {@link FailableSupplier} for a given a <em>supplier</em> Method. The FailableSupplier return type must
  150.      * match the method's return type.
  151.      * <p>
  152.      * Only works with static methods.
  153.      * </p>
  154.      *
  155.      * @param <R> The Method return type.
  156.      * @param method the method to invoke.
  157.      * @return a correctly-typed wrapper for the given target.
  158.      */
  159.     @SuppressWarnings("unchecked")
  160.     public static <R> FailableSupplier<R, Throwable> asFailableSupplier(final Method method) {
  161.         return asInterfaceInstance(FailableSupplier.class, method);
  162.     }

  163.     /**
  164.      * Produces a {@link Function} for a given a <em>supplier</em> Method. You call the Function with one argument: the
  165.      * object receiving the method call. The Function return type must match the method's return type.
  166.      * <p>
  167.      * For example to invoke {@link String#length()}:
  168.      * </p>
  169.      *
  170.      * <pre>{@code
  171.      * final Method method = String.class.getMethod("length");
  172.      * final Function<String, Integer> function = MethodInvokers.asFunction(method);
  173.      * assertEquals(3, function.apply("ABC"));
  174.      * }</pre>
  175.      *
  176.      * @param <T> the type of the first argument to the function: The type containing the method.
  177.      * @param <R> the type of the result of the function: The method return type.
  178.      * @param method the method to invoke.
  179.      * @return a correctly-typed wrapper for the given target.
  180.      */
  181.     @SuppressWarnings("unchecked")
  182.     public static <T, R> Function<T, R> asFunction(final Method method) {
  183.         return asInterfaceInstance(Function.class, method);
  184.     }

  185.     /**
  186.      * Produces an instance of the given single-method interface which redirects its calls to the given method.
  187.      * <p>
  188.      * For the definition of "single-method", see {@link MethodHandleProxies#asInterfaceInstance(Class, MethodHandle)}.
  189.      * </p>
  190.      *
  191.      * @param <T> The interface type.
  192.      * @param interfaceClass a class object representing {@code T}.
  193.      * @param method the method to invoke.
  194.      * @return a correctly-typed wrapper for the given target.
  195.      * @see MethodHandleProxies#asInterfaceInstance(Class, MethodHandle)
  196.      */
  197.     public static <T> T asInterfaceInstance(final Class<T> interfaceClass, final Method method) {
  198.         return MethodHandleProxies.asInterfaceInstance(Objects.requireNonNull(interfaceClass, "interfaceClass"), unreflectUnchecked(method));
  199.     }

  200.     /**
  201.      * Produces a {@link Supplier} for a given a <em>supplier</em> Method. The Supplier return type must match the method's
  202.      * return type.
  203.      * <p>
  204.      * Only works with static methods.
  205.      * </p>
  206.      *
  207.      * @param <R> The Method return type.
  208.      * @param method the method to invoke.
  209.      * @return a correctly-typed wrapper for the given target.
  210.      */
  211.     @SuppressWarnings("unchecked")
  212.     public static <R> Supplier<R> asSupplier(final Method method) {
  213.         return asInterfaceInstance(Supplier.class, method);
  214.     }

  215.     /**
  216.      * Throws NullPointerException if {@code method} is {@code null}.
  217.      *
  218.      * @param method The method to test.
  219.      * @return The given method.
  220.      * @throws NullPointerException if {@code method} is {@code null}.
  221.      */
  222.     private static Method requireMethod(final Method method) {
  223.         return Objects.requireNonNull(method, "method");
  224.     }

  225.     private static MethodHandle unreflect(final Method method) throws IllegalAccessException {
  226.         return MethodHandles.lookup().unreflect(requireMethod(method));
  227.     }

  228.     private static MethodHandle unreflectUnchecked(final Method method) {
  229.         try {
  230.             return unreflect(method);
  231.         } catch (final IllegalAccessException e) {
  232.             throw new UncheckedIllegalAccessException(e);
  233.         }
  234.     }

  235.     /**
  236.      * No need to create instances.
  237.      */
  238.     private MethodInvokers() {
  239.         // noop
  240.     }

  241. }