• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / mybatis-3 / 2686

01 Feb 2025 09:55PM UTC coverage: 87.093% (-0.1%) from 87.217%
2686

Pull #3379

github

web-flow
Merge c97c5c598 into 3d71c862a
Pull Request #3379: Resolve type handler based on `java.lang.reflect.Type` instead of `Class` and respect runtime JDBC type

3825 of 4663 branches covered (82.03%)

515 of 579 new or added lines in 36 files covered. (88.95%)

28 existing lines in 6 files now uncovered.

9912 of 11381 relevant lines covered (87.09%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.92
/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java
1
/*
2
 *    Copyright 2009-2025 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.GenericArrayType;
20
import java.lang.reflect.Method;
21
import java.lang.reflect.ParameterizedType;
22
import java.lang.reflect.Type;
23
import java.util.Collection;
24
import java.util.Collections;
25
import java.util.HashMap;
26
import java.util.List;
27
import java.util.Map;
28
import java.util.Optional;
29
import java.util.SortedMap;
30
import java.util.TreeMap;
31

32
import org.apache.ibatis.annotations.Param;
33
import org.apache.ibatis.binding.MapperMethod.ParamMap;
34
import org.apache.ibatis.reflection.property.PropertyTokenizer;
35
import org.apache.ibatis.session.Configuration;
36
import org.apache.ibatis.session.ResultHandler;
37
import org.apache.ibatis.session.RowBounds;
38

39
public class ParamNameResolver {
40

41
  public static final String GENERIC_NAME_PREFIX = "param";
42

43
  public static final String[] GENERIC_NAME_CACHE = new String[10];
1✔
44

45
  static {
46
    for (int i = 0; i < 10; i++) {
1✔
47
      GENERIC_NAME_CACHE[i] = GENERIC_NAME_PREFIX + (i + 1);
1✔
48
    }
49
  }
1✔
50

51
  private final boolean useActualParamName;
52

53
  /**
54
   * The key is the index and the value is the name of the parameter.<br />
55
   * The name is obtained from {@link Param} if specified. When {@link Param} is not specified, the parameter index is
56
   * used. Note that this index could be different from the actual index when the method has special parameters (i.e.
57
   * {@link RowBounds} or {@link ResultHandler}).
58
   * <ul>
59
   * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
60
   * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
61
   * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
62
   * </ul>
63
   */
64
  private final SortedMap<Integer, String> names;
65
  private final Map<String, Type> typeMap = new HashMap<>();
1✔
66

67
  private boolean hasParamAnnotation;
68
  private boolean useParamMap;
69

70
  public ParamNameResolver(Configuration config, Method method, Class<?> mapperClass) {
1✔
71
    this.useActualParamName = config.isUseActualParamName();
1✔
72
    final Class<?>[] paramTypes = method.getParameterTypes();
1✔
73
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
1✔
74
    final SortedMap<Integer, String> map = new TreeMap<>();
1✔
75
    Type[] actualParamTypes = TypeParameterResolver.resolveParamTypes(method, mapperClass);
1✔
76
    int paramCount = paramAnnotations.length;
1✔
77
    // get names from @Param annotations
78
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
1✔
79
      if (isSpecialParameter(paramTypes[paramIndex])) {
1✔
80
        // skip special parameters
81
        continue;
1✔
82
      }
83
      String name = null;
1✔
84
      for (Annotation annotation : paramAnnotations[paramIndex]) {
1✔
85
        if (annotation instanceof Param) {
1!
86
          hasParamAnnotation = true;
1✔
87
          useParamMap = true;
1✔
88
          name = ((Param) annotation).value();
1✔
89
          break;
1✔
90
        }
91
      }
92
      if (name == null) {
1✔
93
        // @Param was not specified.
94
        if (useActualParamName) {
1✔
95
          name = getActualParamName(method, paramIndex);
1✔
96
        }
97
        if (name == null) {
1✔
98
          // use the parameter index as the name ("0", "1", ...)
99
          // gcode issue #71
100
          name = String.valueOf(map.size());
1✔
101
        }
102
      }
103
      map.put(paramIndex, name);
1✔
104
      typeMap.put(name, actualParamTypes[paramIndex]);
1✔
105
    }
106
    names = Collections.unmodifiableSortedMap(map);
1✔
107
    if (names.size() > 1) {
1✔
108
      useParamMap = true;
1✔
109
    }
110
    if (names.size() == 1) {
1✔
111
      Type soleParamType = actualParamTypes[0];
1✔
112
      if (soleParamType instanceof GenericArrayType) {
1!
NEW
113
        typeMap.put("array", soleParamType);
×
114
      } else {
115
        Class<?> soleParamClass = null;
1✔
116
        if (soleParamType instanceof ParameterizedType) {
1✔
117
          soleParamClass = (Class<?>) ((ParameterizedType) soleParamType).getRawType();
1✔
118
        } else if (soleParamType instanceof Class) {
1!
119
          soleParamClass = (Class<?>) soleParamType;
1✔
120
        }
121
        if (Collection.class.isAssignableFrom(soleParamClass)) {
1✔
122
          typeMap.put("collection", soleParamType);
1✔
123
          if (List.class.isAssignableFrom(soleParamClass)) {
1✔
124
            typeMap.put("list", soleParamType);
1✔
125
          }
126
        }
127
      }
128
    }
129
  }
1✔
130

131
  private String getActualParamName(Method method, int paramIndex) {
132
    return ParamNameUtil.getParamNames(method).get(paramIndex);
1✔
133
  }
134

135
  private static boolean isSpecialParameter(Class<?> clazz) {
136
    return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
1✔
137
  }
138

139
  /**
140
   * Returns parameter names referenced by SQL providers.
141
   *
142
   * @return the names
143
   */
144
  public String[] getNames() {
145
    return names.values().toArray(new String[0]);
1✔
146
  }
147

148
  /**
149
   * A single non-special parameter is returned without a name. Multiple parameters are named using the naming rule. In
150
   * addition to the default names, this method also adds the generic names (param1, param2, ...).
151
   *
152
   * @param args
153
   *          the args
154
   *
155
   * @return the named params
156
   */
157
  public Object getNamedParams(Object[] args) {
158
    final int paramCount = names.size();
1✔
159
    if (args == null || paramCount == 0) {
1✔
160
      return null;
1✔
161
    }
162
    if (!hasParamAnnotation && paramCount == 1) {
1✔
163
      Object value = args[names.firstKey()];
1✔
164
      return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
1✔
165
    } else {
166
      final Map<String, Object> param = new ParamMap<>();
1✔
167
      int i = 0;
1✔
168
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
1✔
169
        param.put(entry.getValue(), args[entry.getKey()]);
1✔
170
        // add generic param names (param1, param2, ...)
171
        final String genericParamName = i < 10 ? GENERIC_NAME_CACHE[i] : GENERIC_NAME_PREFIX + (i + 1);
1!
172
        // ensure not to overwrite parameter named with @Param
173
        if (!names.containsValue(genericParamName)) {
1!
174
          param.put(genericParamName, args[entry.getKey()]);
1✔
175
        }
176
        i++;
1✔
177
      }
1✔
178
      return param;
1✔
179
    }
180
  }
181

182
  public Type getType(String name) {
183
    PropertyTokenizer propertyTokenizer = new PropertyTokenizer(name);
1✔
184
    String unindexed = propertyTokenizer.getName();
1✔
185
    Type type = typeMap.get(unindexed);
1✔
186

187
    if (type == null && unindexed.startsWith(GENERIC_NAME_PREFIX)) {
1✔
188
      try {
189
        Integer paramIndex = Integer.valueOf(unindexed.substring(GENERIC_NAME_PREFIX.length())) - 1;
1✔
190
        unindexed = names.get(paramIndex);
1✔
191
        if (unindexed != null) {
1!
192
          type = typeMap.get(unindexed);
1✔
193
        }
NEW
194
      } catch (NumberFormatException e) {
×
195
        // user mistake
196
      }
1✔
197
    }
198

199
    if (propertyTokenizer.getIndex() != null) {
1✔
200
      if (type instanceof ParameterizedType) {
1✔
201
        Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
1✔
202
        return typeArgs[0];
1✔
203
      } else if (type instanceof Class && ((Class<?>) type).isArray()) {
1!
204
        return ((Class<?>) type).getComponentType();
1✔
205
      }
206
    }
207
    return type;
1✔
208
  }
209

210
  /**
211
   * Wrap to a {@link ParamMap} if object is {@link Collection} or array.
212
   *
213
   * @param object
214
   *          a parameter object
215
   * @param actualParamName
216
   *          an actual parameter name (If specify a name, set an object to {@link ParamMap} with specified name)
217
   *
218
   * @return a {@link ParamMap}
219
   *
220
   * @since 3.5.5
221
   */
222
  public static Object wrapToMapIfCollection(Object object, String actualParamName) {
223
    if (object instanceof Collection) {
1✔
224
      ParamMap<Object> map = new ParamMap<>();
1✔
225
      map.put("collection", object);
1✔
226
      if (object instanceof List) {
1✔
227
        map.put("list", object);
1✔
228
      }
229
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
1✔
230
      return map;
1✔
231
    }
232
    if (object != null && object.getClass().isArray()) {
1✔
233
      ParamMap<Object> map = new ParamMap<>();
1✔
234
      map.put("array", object);
1✔
235
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
1✔
236
      return map;
1✔
237
    }
238
    return object;
1✔
239
  }
240

241
  public boolean isUseParamMap() {
242
    return useParamMap;
1✔
243
  }
244
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc