View Javadoc
1   /*
2    *    Copyright 2009-2024 the original author or authors.
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    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.reflection;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.Method;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Optional;
25  import java.util.SortedMap;
26  import java.util.TreeMap;
27  
28  import org.apache.ibatis.annotations.Param;
29  import org.apache.ibatis.binding.MapperMethod.ParamMap;
30  import org.apache.ibatis.session.Configuration;
31  import org.apache.ibatis.session.ResultHandler;
32  import org.apache.ibatis.session.RowBounds;
33  
34  public class ParamNameResolver {
35  
36    public static final String GENERIC_NAME_PREFIX = "param";
37  
38    public static final String[] GENERIC_NAME_CACHE = new String[10];
39  
40    static {
41      for (int i = 0; i < 10; i++) {
42        GENERIC_NAME_CACHE[i] = GENERIC_NAME_PREFIX + (i + 1);
43      }
44    }
45  
46    private final boolean useActualParamName;
47  
48    /**
49     * <p>
50     * The key is the index and the value is the name of the parameter.<br />
51     * The name is obtained from {@link Param} if specified. When {@link Param} is not specified, the parameter index is
52     * used. Note that this index could be different from the actual index when the method has special parameters (i.e.
53     * {@link RowBounds} or {@link ResultHandler}).
54     * </p>
55     * <ul>
56     * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
57     * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
58     * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
59     * </ul>
60     */
61    private final SortedMap<Integer, String> names;
62  
63    private boolean hasParamAnnotation;
64  
65    public ParamNameResolver(Configuration config, Method method) {
66      this.useActualParamName = config.isUseActualParamName();
67      final Class<?>[] paramTypes = method.getParameterTypes();
68      final Annotation[][] paramAnnotations = method.getParameterAnnotations();
69      final SortedMap<Integer, String> map = new TreeMap<>();
70      int paramCount = paramAnnotations.length;
71      // get names from @Param annotations
72      for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
73        if (isSpecialParameter(paramTypes[paramIndex])) {
74          // skip special parameters
75          continue;
76        }
77        String name = null;
78        for (Annotation annotation : paramAnnotations[paramIndex]) {
79          if (annotation instanceof Param) {
80            hasParamAnnotation = true;
81            name = ((Param) annotation).value();
82            break;
83          }
84        }
85        if (name == null) {
86          // @Param was not specified.
87          if (useActualParamName) {
88            name = getActualParamName(method, paramIndex);
89          }
90          if (name == null) {
91            // use the parameter index as the name ("0", "1", ...)
92            // gcode issue #71
93            name = String.valueOf(map.size());
94          }
95        }
96        map.put(paramIndex, name);
97      }
98      names = Collections.unmodifiableSortedMap(map);
99    }
100 
101   private String getActualParamName(Method method, int paramIndex) {
102     return ParamNameUtil.getParamNames(method).get(paramIndex);
103   }
104 
105   private static boolean isSpecialParameter(Class<?> clazz) {
106     return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
107   }
108 
109   /**
110    * Returns parameter names referenced by SQL providers.
111    *
112    * @return the names
113    */
114   public String[] getNames() {
115     return names.values().toArray(new String[0]);
116   }
117 
118   /**
119    * <p>
120    * A single non-special parameter is returned without a name. Multiple parameters are named using the naming rule. In
121    * addition to the default names, this method also adds the generic names (param1, param2, ...).
122    * </p>
123    *
124    * @param args
125    *          the args
126    *
127    * @return the named params
128    */
129   public Object getNamedParams(Object[] args) {
130     final int paramCount = names.size();
131     if (args == null || paramCount == 0) {
132       return null;
133     }
134     if (!hasParamAnnotation && paramCount == 1) {
135       Object value = args[names.firstKey()];
136       return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
137     } else {
138       final Map<String, Object> param = new ParamMap<>();
139       int i = 0;
140       for (Map.Entry<Integer, String> entry : names.entrySet()) {
141         param.put(entry.getValue(), args[entry.getKey()]);
142         // add generic param names (param1, param2, ...)
143         final String genericParamName = i < 10 ? GENERIC_NAME_CACHE[i] : GENERIC_NAME_PREFIX + (i + 1);
144         // ensure not to overwrite parameter named with @Param
145         if (!names.containsValue(genericParamName)) {
146           param.put(genericParamName, args[entry.getKey()]);
147         }
148         i++;
149       }
150       return param;
151     }
152   }
153 
154   /**
155    * Wrap to a {@link ParamMap} if object is {@link Collection} or array.
156    *
157    * @param object
158    *          a parameter object
159    * @param actualParamName
160    *          an actual parameter name (If specify a name, set an object to {@link ParamMap} with specified name)
161    *
162    * @return a {@link ParamMap}
163    *
164    * @since 3.5.5
165    */
166   public static Object wrapToMapIfCollection(Object object, String actualParamName) {
167     if (object instanceof Collection) {
168       ParamMap<Object> map = new ParamMap<>();
169       map.put("collection", object);
170       if (object instanceof List) {
171         map.put("list", object);
172       }
173       Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
174       return map;
175     }
176     if (object != null && object.getClass().isArray()) {
177       ParamMap<Object> map = new ParamMap<>();
178       map.put("array", object);
179       Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
180       return map;
181     }
182     return object;
183   }
184 
185 }