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    *      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  package org.apache.commons.collections4.functors;
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.util.Objects;
22  
23  import org.apache.commons.collections4.FunctorException;
24  import org.apache.commons.collections4.Transformer;
25  
26  /**
27   * Transformer implementation that creates a new object instance by reflection.
28   * <p>
29   * <strong>WARNING:</strong> from v4.1 onwards this class will <strong>not</strong> be serializable anymore
30   * in order to prevent potential remote code execution exploits. Please refer to
31   * <a href="https://issues.apache.org/jira/browse/COLLECTIONS-580">COLLECTIONS-580</a>
32   * for more details.
33   * </p>
34   *
35   * @param <T> The type of the input to the function.
36   * @param <R> The type of the result of the function.
37   * @since 3.0
38   */
39  public class InvokerTransformer<T, R> implements Transformer<T, R> {
40  
41      /**
42       * Gets an instance of this transformer calling a specific method with no arguments.
43       *
44       * @param <I>  the input type
45       * @param <O>  the output type
46       * @param methodName  The method name to call
47       * @return An invoker transformer
48       * @throws NullPointerException if methodName is null
49       * @since 3.1
50       */
51      public static <I, O> Transformer<I, O> invokerTransformer(final String methodName) {
52          return new InvokerTransformer<>(Objects.requireNonNull(methodName, "methodName"));
53      }
54  
55      /**
56       * Gets an instance of this transformer calling a specific method with specific values.
57       *
58       * @param <I>  the input type
59       * @param <O>  the output type
60       * @param methodName  The method name to call
61       * @param paramTypes  The parameter types of the method
62       * @param args  The arguments to pass to the method
63       * @return An invoker transformer
64       * @throws NullPointerException if methodName is null
65       * @throws IllegalArgumentException if paramTypes does not match args
66       */
67      public static <I, O> Transformer<I, O> invokerTransformer(final String methodName, final Class<?>[] paramTypes,
68                                                                final Object[] args) {
69          Objects.requireNonNull(methodName, "methodName");
70          if (paramTypes == null && args != null
71              || paramTypes != null && args == null
72              || paramTypes != null && args != null && paramTypes.length != args.length) {
73              throw new IllegalArgumentException("The parameter types must match the arguments");
74          }
75          if (paramTypes == null || paramTypes.length == 0) {
76              return new InvokerTransformer<>(methodName);
77          }
78          return new InvokerTransformer<>(methodName, paramTypes, args);
79      }
80  
81      /** The method name to call */
82      private final String iMethodName;
83  
84      /** The array of reflection parameter types */
85      private final Class<?>[] iParamTypes;
86  
87      /** The array of reflection arguments */
88      private final Object[] iArgs;
89  
90      /**
91       * Constructor for no arg instance.
92       *
93       * @param methodName  The method to call
94       */
95      private InvokerTransformer(final String methodName) {
96          iMethodName = methodName;
97          iParamTypes = null;
98          iArgs = null;
99      }
100 
101     /**
102      * Constructor that performs no validation.
103      * Use {@code invokerTransformer} if you want that.
104      * <p>
105      * Note: from 4.0, the input parameters will be cloned
106      *
107      * @param methodName  The method to call
108      * @param paramTypes  The constructor parameter types
109      * @param args  The constructor arguments
110      */
111     public InvokerTransformer(final String methodName, final Class<?>[] paramTypes, final Object[] args) {
112         iMethodName = methodName;
113         iParamTypes = paramTypes != null ? paramTypes.clone() : null;
114         iArgs = args != null ? args.clone() : null;
115     }
116 
117     /**
118      * Transforms the input to result by invoking a method on the input.
119      *
120      * @param input  The input object to transform
121      * @return The transformed result, null if null input
122      */
123     @Override
124     @SuppressWarnings("unchecked")
125     public R transform(final Object input) {
126         if (input == null) {
127             return null;
128         }
129         try {
130             final Class<?> cls = input.getClass();
131             final Method method = cls.getMethod(iMethodName, iParamTypes);
132             return (R) method.invoke(input, iArgs);
133         } catch (final NoSuchMethodException ex) {
134             throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
135                                        input.getClass() + "' does not exist");
136         } catch (final IllegalAccessException ex) {
137             throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
138                                        input.getClass() + "' cannot be accessed");
139         } catch (final InvocationTargetException ex) {
140             throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
141                                        input.getClass() + "' threw an exception", ex);
142         }
143     }
144 
145 }