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

mybatis / mybatis-3 / #3633

24 May 2024 08:58AM CUT coverage: 87.257% (+0.09%) from 87.169%
#3633

Pull #3108

github

web-flow
Merge 7d3668285 into 043270b83
Pull Request #3108: #101: Add support for collection constructor creation

3644 of 4415 branches covered (82.54%)

209 of 227 new or added lines in 7 files covered. (92.07%)

3 existing lines in 1 file now uncovered.

9586 of 10986 relevant lines covered (87.26%)

0.87 hits per line

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

88.16
/src/main/java/org/apache/ibatis/executor/resultset/PendingConstructorCreation.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.executor.resultset;
17

18
import java.lang.reflect.Constructor;
19
import java.lang.reflect.ParameterizedType;
20
import java.lang.reflect.Type;
21
import java.util.ArrayList;
22
import java.util.Collection;
23
import java.util.HashMap;
24
import java.util.List;
25
import java.util.Map;
26

27
import org.apache.ibatis.executor.ExecutorException;
28
import org.apache.ibatis.mapping.ResultMap;
29
import org.apache.ibatis.mapping.ResultMapping;
30
import org.apache.ibatis.reflection.ReflectionException;
31
import org.apache.ibatis.reflection.factory.ObjectFactory;
32

33
/**
34
 * Represents an object that is still to be created once all nested results with collection values have been gathered
35
 *
36
 * @author Willie Scholtz
37
 */
38
final class PendingConstructorCreation {
39
  private final Class<?> resultType;
40
  private final List<Class<?>> constructorArgTypes;
41
  private final List<Object> constructorArgs;
42
  private final Map<Integer, PendingCreationMetaInfo> linkedCollectionMetaInfo;
43
  private final Map<String, Collection<Object>> linkedCollectionsByResultMapId;
44
  private final Map<String, List<PendingConstructorCreation>> linkedCreationsByResultMapId;
45

46
  PendingConstructorCreation(Class<?> resultType, List<Class<?>> types, List<Object> args) {
1✔
47
    // since all our keys are based on result map id, we know we will never go over args size
48
    final int maxSize = types.size();
1✔
49
    this.linkedCollectionMetaInfo = new HashMap<>(maxSize);
1✔
50
    this.linkedCollectionsByResultMapId = new HashMap<>(maxSize);
1✔
51
    this.linkedCreationsByResultMapId = new HashMap<>(maxSize);
1✔
52
    this.resultType = resultType;
1✔
53
    this.constructorArgTypes = types;
1✔
54
    this.constructorArgs = args;
1✔
55
  }
1✔
56

57
  @SuppressWarnings("unchecked")
58
  Collection<Object> initializeCollectionForResultMapping(ObjectFactory objectFactory, ResultMap resultMap,
59
      ResultMapping constructorMapping, Integer index) {
60
    final Class<?> parameterType = constructorMapping.getJavaType();
1✔
61
    if (!objectFactory.isCollection(parameterType)) {
1!
NEW
62
      throw new ExecutorException(
×
63
          "Cannot add a collection result to non-collection based resultMapping: " + constructorMapping);
64
    }
65

66
    final String resultMapId = constructorMapping.getNestedResultMapId();
1✔
67
    return linkedCollectionsByResultMapId.computeIfAbsent(resultMapId, (k) -> {
1✔
68
      // this will allow us to verify the types of the collection before creating the final object
69
      linkedCollectionMetaInfo.put(index, new PendingCreationMetaInfo(resultMap.getType(), resultMapId));
1✔
70

71
      // will be checked before we finally create the object) as we cannot reliably do that here
72
      return (Collection<Object>) objectFactory.create(parameterType);
1✔
73
    });
74
  }
75

76
  void linkCreation(ResultMap nestedResultMap, PendingConstructorCreation pcc) {
77
    final String resultMapId = nestedResultMap.getId();
1✔
78
    final List<PendingConstructorCreation> pendingConstructorCreations = linkedCreationsByResultMapId
1✔
79
        .computeIfAbsent(resultMapId, (k) -> new ArrayList<>());
1✔
80

81
    if (pendingConstructorCreations.contains(pcc)) {
1!
NEW
82
      throw new ExecutorException("Cannot link inner pcc with same value, MyBatis programming error!");
×
83
    }
84

85
    pendingConstructorCreations.add(pcc);
1✔
86
  }
1✔
87

88
  void linkCollectionValue(ResultMapping constructorMapping, Object value) {
89
    // not necessary to add null results to the collection (is this a config flag?)
90
    if (value == null) {
1!
NEW
91
      return;
×
92
    }
93

94
    final String resultMapId = constructorMapping.getNestedResultMapId();
1✔
95
    if (!linkedCollectionsByResultMapId.containsKey(resultMapId)) {
1!
NEW
96
      throw new ExecutorException("Cannot link collection value for resultMapping: " + constructorMapping
×
97
          + ", resultMap has not been seen/initialized yet! Internal error");
98
    }
99

100
    linkedCollectionsByResultMapId.get(resultMapId).add(value);
1✔
101
  }
1✔
102

103
  /**
104
   * Verifies preconditions before we can actually create the result object, this is more of a sanity check to ensure
105
   * all the mappings are as we expect them to be.
106
   *
107
   * @param objectFactory
108
   *          the object factory
109
   */
110
  private void verifyCanCreate(ObjectFactory objectFactory) {
111
    // before we create, we need to get the constructor to be used and verify our types match
112
    // since we added to the collection completely unchecked
113
    final Constructor<?> resolvedConstructor = objectFactory.resolveConstructor(resultType, constructorArgTypes);
1✔
114
    final Type[] genericParameterTypes = resolvedConstructor.getGenericParameterTypes();
1✔
115
    for (int i = 0; i < genericParameterTypes.length; i++) {
1✔
116
      if (!linkedCollectionMetaInfo.containsKey(i)) {
1✔
117
        continue;
1✔
118
      }
119

120
      final PendingCreationMetaInfo creationMetaInfo = linkedCollectionMetaInfo.get(i);
1✔
121
      final Class<?> resolvedItemType = checkResolvedItemType(creationMetaInfo, genericParameterTypes[i]);
1✔
122

123
      // ensure we have an empty collection if there are linked creations for this arg
124
      final String resultMapId = creationMetaInfo.getResultMapId();
1✔
125
      if (linkedCreationsByResultMapId.containsKey(resultMapId)) {
1✔
126
        final Object emptyCollection = constructorArgs.get(i);
1✔
127
        if (emptyCollection == null || !objectFactory.isCollection(emptyCollection.getClass())) {
1!
NEW
128
          throw new ExecutorException(
×
129
              "Expected empty collection for '" + resolvedItemType + "', this is a MyBatis internal error!");
130
        }
131
      } else {
1✔
132
        final Object linkedCollection = constructorArgs.get(i);
1✔
133
        if (!linkedCollectionsByResultMapId.containsKey(resultMapId)) {
1!
NEW
134
          throw new ExecutorException("Expected linked collection for resultMap '" + resultMapId
×
135
              + "', not found! this is a MyBatis internal error!");
136
        }
137

138
        // comparing memory locations here (we rely on that fact)
139
        if (linkedCollection != linkedCollectionsByResultMapId.get(resultMapId)) {
1!
NEW
140
          throw new ExecutorException("Expected linked collection in creation to be the same as arg for resultMap '"
×
141
              + resultMapId + "', not equal! this is a MyBatis internal error!");
142
        }
143
      }
144
    }
145
  }
1✔
146

147
  private static Class<?> checkResolvedItemType(PendingCreationMetaInfo creationMetaInfo, Type genericParameterTypes) {
148
    final ParameterizedType genericParameterType = (ParameterizedType) genericParameterTypes;
1✔
149
    final Class<?> expectedType = (Class<?>) genericParameterType.getActualTypeArguments()[0];
1✔
150
    final Class<?> resolvedItemType = creationMetaInfo.getArgumentType();
1✔
151

152
    if (!expectedType.isAssignableFrom(resolvedItemType)) {
1!
NEW
153
      throw new ReflectionException(
×
154
          "Expected type '" + resolvedItemType + "', while the actual type of the collection was '" + expectedType
155
              + "', ensure your resultMap matches the type of the collection you are trying to inject");
156
    }
157

158
    return resolvedItemType;
1✔
159
  }
160

161
  @Override
162
  public String toString() {
NEW
163
    return "PendingConstructorCreation(" + this.hashCode() + "){" + "resultType=" + resultType + '}';
×
164
  }
165

166
  /**
167
   * Recursively creates the final result of this creation.
168
   *
169
   * @param objectFactory
170
   *          the object factory
171
   * @param verifyCreate
172
   *          should we verify this object can be created, should only be needed once
173
   *
174
   * @return the new immutable result
175
   */
176
  Object create(ObjectFactory objectFactory, boolean verifyCreate) {
177
    if (verifyCreate) {
1✔
178
      verifyCanCreate(objectFactory);
1✔
179
    }
180

181
    final List<Object> newArguments = new ArrayList<>(constructorArgs.size());
1✔
182
    for (int i = 0; i < constructorArgs.size(); i++) {
1✔
183
      final PendingCreationMetaInfo creationMetaInfo = linkedCollectionMetaInfo.get(i);
1✔
184
      final Object existingArg = constructorArgs.get(i);
1✔
185

186
      if (creationMetaInfo == null) {
1✔
187
        // we are not aware of this argument wrt pending creations
188
        newArguments.add(existingArg);
1✔
189
        continue;
1✔
190
      }
191

192
      // time to finally build this collection
193
      final String resultMapId = creationMetaInfo.getResultMapId();
1✔
194
      if (linkedCreationsByResultMapId.containsKey(resultMapId)) {
1✔
195
        @SuppressWarnings("unchecked")
196
        final Collection<Object> emptyCollection = (Collection<Object>) existingArg;
1✔
197
        final List<PendingConstructorCreation> linkedCreations = linkedCreationsByResultMapId.get(resultMapId);
1✔
198

199
        for (PendingConstructorCreation linkedCreation : linkedCreations) {
1✔
200
          emptyCollection.add(linkedCreation.create(objectFactory, verifyCreate));
1✔
201
        }
1✔
202

203
        newArguments.add(emptyCollection);
1✔
204
        continue;
1✔
205
      }
206

207
      // handle the base collection (it was built inline already)
208
      newArguments.add(existingArg);
1✔
209
    }
210

211
    return objectFactory.create(resultType, constructorArgTypes, newArguments);
1✔
212
  }
213
}
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