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

mybatis / mybatis-3 / #3532

18 Mar 2024 01:14PM UTC coverage: 87.201% (+0.09%) from 87.116%
#3532

Pull #3108

github

web-flow
Merge 0c52fa490 into b044200be
Pull Request #3108: #101: Add support for collection constructor creation

3635 of 4407 branches covered (82.48%)

206 of 224 new or added lines in 7 files covered. (91.96%)

5 existing lines in 1 file now uncovered.

9572 of 10977 relevant lines covered (87.2%)

0.87 hits per line

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

94.83
/src/main/java/org/apache/ibatis/mapping/ResultMap.java
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.mapping;
17

18
import java.lang.annotation.Annotation;
19
import java.lang.reflect.Constructor;
20
import java.util.ArrayList;
21
import java.util.Collections;
22
import java.util.HashSet;
23
import java.util.List;
24
import java.util.Locale;
25
import java.util.Set;
26

27
import org.apache.ibatis.annotations.Param;
28
import org.apache.ibatis.builder.BuilderException;
29
import org.apache.ibatis.logging.Log;
30
import org.apache.ibatis.logging.LogFactory;
31
import org.apache.ibatis.reflection.ParamNameUtil;
32
import org.apache.ibatis.session.Configuration;
33

34
/**
35
 * @author Clinton Begin
36
 */
37
public class ResultMap {
38
  private Configuration configuration;
39

40
  private String id;
41
  private Class<?> type;
42
  private List<ResultMapping> resultMappings;
43
  private List<ResultMapping> idResultMappings;
44
  private List<ResultMapping> constructorResultMappings;
45
  private List<ResultMapping> propertyResultMappings;
46
  private Set<String> mappedColumns;
47
  private Set<String> mappedProperties;
48
  private Discriminator discriminator;
49
  private boolean hasNestedResultMaps;
50
  private boolean hasNestedQueries;
51
  private Boolean autoMapping;
52

53
  private ResultMap() {
54
  }
55

56
  public static class Builder {
57
    private static final Log log = LogFactory.getLog(Builder.class);
1✔
58

59
    private final ResultMap resultMap = new ResultMap();
1✔
60

61
    public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
62
      this(configuration, id, type, resultMappings, null);
1✔
63
    }
1✔
64

65
    public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings,
66
        Boolean autoMapping) {
1✔
67
      resultMap.configuration = configuration;
1✔
68
      resultMap.id = id;
1✔
69
      resultMap.type = type;
1✔
70
      resultMap.resultMappings = resultMappings;
1✔
71
      resultMap.autoMapping = autoMapping;
1✔
72
    }
1✔
73

74
    public Builder discriminator(Discriminator discriminator) {
75
      resultMap.discriminator = discriminator;
1✔
76
      return this;
1✔
77
    }
78

79
    public Class<?> type() {
UNCOV
80
      return resultMap.type;
×
81
    }
82

83
    public ResultMap build() {
84
      if (resultMap.id == null) {
1!
UNCOV
85
        throw new IllegalArgumentException("ResultMaps must have an id");
×
86
      }
87
      resultMap.mappedColumns = new HashSet<>();
1✔
88
      resultMap.mappedProperties = new HashSet<>();
1✔
89
      resultMap.idResultMappings = new ArrayList<>();
1✔
90
      resultMap.constructorResultMappings = new ArrayList<>();
1✔
91
      resultMap.propertyResultMappings = new ArrayList<>();
1✔
92
      final List<String> constructorArgNames = new ArrayList<>();
1✔
93
      for (ResultMapping resultMapping : resultMap.resultMappings) {
1✔
94
        resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
1✔
95
        resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps
1✔
96
            || resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null;
1✔
97
        final String column = resultMapping.getColumn();
1✔
98
        if (column != null) {
1✔
99
          resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
1✔
100
        } else if (resultMapping.isCompositeResult()) {
1✔
101
          for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
1✔
102
            final String compositeColumn = compositeResultMapping.getColumn();
1✔
103
            if (compositeColumn != null) {
1!
104
              resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
1✔
105
            }
106
          }
1✔
107
        }
108
        final String property = resultMapping.getProperty();
1✔
109
        if (property != null) {
1✔
110
          resultMap.mappedProperties.add(property);
1✔
111
        }
112
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
1✔
113
          resultMap.constructorResultMappings.add(resultMapping);
1✔
114
          if (resultMapping.getProperty() != null) {
1✔
115
            constructorArgNames.add(resultMapping.getProperty());
1✔
116
          }
117
        } else {
118
          resultMap.propertyResultMappings.add(resultMapping);
1✔
119
        }
120
        if (resultMapping.getFlags().contains(ResultFlag.ID)) {
1✔
121
          resultMap.idResultMappings.add(resultMapping);
1✔
122
        }
123
      }
1✔
124
      if (resultMap.idResultMappings.isEmpty()) {
1✔
125
        resultMap.idResultMappings.addAll(resultMap.resultMappings);
1✔
126
      }
127
      if (!constructorArgNames.isEmpty()) {
1✔
128
        final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
1✔
129
        if (actualArgNames == null) {
1✔
130
          throw new BuilderException("Error in result map '" + resultMap.id + "'. Failed to find a constructor in '"
1✔
131
              + resultMap.getType().getName() + "' with arg names " + constructorArgNames
1✔
132
              + ". Note that 'javaType' is required when there is no writable property with the same name ('name' is optional, BTW). There might be more info in debug log.");
133
        }
134
        resultMap.constructorResultMappings.sort((o1, o2) -> {
1✔
135
          int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
1✔
136
          int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
1✔
137
          return paramIdx1 - paramIdx2;
1✔
138
        });
139
      }
140
      // lock down collections
141
      resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
1✔
142
      resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
1✔
143
      resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
1✔
144
      resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
1✔
145
      resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
1✔
146
      return resultMap;
1✔
147
    }
148

149
    private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
150
      Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
1✔
151
      for (Constructor<?> constructor : constructors) {
1✔
152
        Class<?>[] paramTypes = constructor.getParameterTypes();
1✔
153
        if (constructorArgNames.size() == paramTypes.length) {
1✔
154
          List<String> paramNames = getArgNames(constructor);
1✔
155
          if (constructorArgNames.containsAll(paramNames)
1✔
156
              && argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
1✔
157
            return paramNames;
1✔
158
          }
159
        }
160
      }
161
      return null;
1✔
162
    }
163

164
    private boolean argTypesMatch(final List<String> constructorArgNames, Class<?>[] paramTypes,
165
        List<String> paramNames) {
166
      for (int i = 0; i < constructorArgNames.size(); i++) {
1✔
167
        Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
1✔
168
        Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
1✔
169
        if (!actualType.equals(specifiedType)) {
1✔
170
          if (log.isDebugEnabled()) {
1!
UNCOV
171
            log.debug("While building result map '" + resultMap.id + "', found a constructor with arg names "
×
UNCOV
172
                + constructorArgNames + ", but the type of '" + constructorArgNames.get(i)
×
UNCOV
173
                + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: [" + actualType.getName()
×
174
                + "]");
175
          }
176
          return false;
1✔
177
        }
178
      }
179
      return true;
1✔
180
    }
181

182
    private List<String> getArgNames(Constructor<?> constructor) {
183
      List<String> paramNames = new ArrayList<>();
1✔
184
      List<String> actualParamNames = null;
1✔
185
      final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
1✔
186
      int paramCount = paramAnnotations.length;
1✔
187
      for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
1✔
188
        String name = null;
1✔
189
        for (Annotation annotation : paramAnnotations[paramIndex]) {
1✔
190
          if (annotation instanceof Param) {
1!
191
            name = ((Param) annotation).value();
1✔
192
            break;
1✔
193
          }
194
        }
195
        if (name == null && resultMap.configuration.isUseActualParamName()) {
1!
196
          if (actualParamNames == null) {
1✔
197
            actualParamNames = ParamNameUtil.getParamNames(constructor);
1✔
198
          }
199
          if (actualParamNames.size() > paramIndex) {
1!
200
            name = actualParamNames.get(paramIndex);
1✔
201
          }
202
        }
203
        paramNames.add(name != null ? name : "arg" + paramIndex);
1!
204
      }
205
      return paramNames;
1✔
206
    }
207
  }
208

209
  public String getId() {
210
    return id;
1✔
211
  }
212

213
  public boolean hasResultMapsUsingConstructorCollection() {
214
    return configuration.isExperimentalConstructorCollectionMappingEnabled()
1✔
215
        && this.constructorResultMappings.stream().filter(crm -> crm.getNestedQueryId() == null)
1!
216
            .map(ResultMapping::getJavaType).anyMatch(configuration.getObjectFactory()::isCollection);
1✔
217
  }
218

219
  public boolean hasNestedResultMaps() {
220
    return hasNestedResultMaps;
1✔
221
  }
222

223
  public boolean hasNestedQueries() {
224
    return hasNestedQueries;
×
225
  }
226

227
  public Class<?> getType() {
228
    return type;
1✔
229
  }
230

231
  public List<ResultMapping> getResultMappings() {
232
    return resultMappings;
1✔
233
  }
234

235
  public List<ResultMapping> getConstructorResultMappings() {
236
    return constructorResultMappings;
1✔
237
  }
238

239
  public List<ResultMapping> getPropertyResultMappings() {
240
    return propertyResultMappings;
1✔
241
  }
242

243
  public List<ResultMapping> getIdResultMappings() {
244
    return idResultMappings;
1✔
245
  }
246

247
  public Set<String> getMappedColumns() {
248
    return mappedColumns;
1✔
249
  }
250

251
  public Set<String> getMappedProperties() {
252
    return mappedProperties;
1✔
253
  }
254

255
  public Discriminator getDiscriminator() {
256
    return discriminator;
1✔
257
  }
258

259
  public void forceNestedResultMaps() {
260
    hasNestedResultMaps = true;
1✔
261
  }
1✔
262

263
  public Boolean getAutoMapping() {
264
    return autoMapping;
1✔
265
  }
266

267
}
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