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

mybatis / mybatis-3 / #3518

08 Mar 2024 12:32PM UTC coverage: 87.19% (+0.07%) from 87.116%
#3518

Pull #3108

github

web-flow
Merge be7650ec9 into 3beed1d13
Pull Request #3108: #101: Add support for collection constructor creation

3633 of 4405 branches covered (82.47%)

196 of 214 new or added lines in 6 files covered. (91.59%)

31 existing lines in 3 files now uncovered.

9563 of 10968 relevant lines covered (87.19%)

0.87 hits per line

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

87.84
/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
    this.linkedCollectionMetaInfo = new HashMap<>();
1✔
48
    this.linkedCollectionsByResultMapId = new HashMap<>();
1✔
49
    this.linkedCreationsByResultMapId = new HashMap<>();
1✔
50
    this.resultType = resultType;
1✔
51
    this.constructorArgTypes = types;
1✔
52
    this.constructorArgs = args;
1✔
53
  }
1✔
54

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

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

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

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

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

83
    pendingConstructorCreations.add(pcc);
1✔
84
  }
1✔
85

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

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

98
    linkedCollectionsByResultMapId.get(resultMapId).add(value);
1✔
99
  }
1✔
100

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

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

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

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

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

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

156
    return resolvedItemType;
1✔
157
  }
158

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

164
  /**
165
   * Recursively creates the final result of this creation.
166
   *
167
   * @param objectFactory
168
   *          the object factory
169
   *
170
   * @return the new immutable result
171
   */
172
  Object create(ObjectFactory objectFactory) {
173
    final List<Object> newArguments = new ArrayList<>(constructorArgs.size());
1✔
174
    for (int i = 0; i < constructorArgs.size(); i++) {
1✔
175
      final PendingCreationMetaInfo creationMetaInfo = linkedCollectionMetaInfo.get(i);
1✔
176
      final Object existingArg = constructorArgs.get(i);
1✔
177

178
      if (creationMetaInfo == null) {
1✔
179
        // we are not aware of this argument wrt pending creations
180
        newArguments.add(existingArg);
1✔
181
        continue;
1✔
182
      }
183

184
      // time to finally build this collection
185
      final String resultMapId = creationMetaInfo.getResultMapId();
1✔
186
      if (linkedCreationsByResultMapId.containsKey(resultMapId)) {
1✔
187
        @SuppressWarnings("unchecked")
188
        final Collection<Object> emptyCollection = (Collection<Object>) existingArg;
1✔
189
        final List<PendingConstructorCreation> linkedCreations = linkedCreationsByResultMapId.get(resultMapId);
1✔
190

191
        for (PendingConstructorCreation linkedCreation : linkedCreations) {
1✔
192
          linkedCreation.verifyCanCreate(objectFactory);
1✔
193
          emptyCollection.add(linkedCreation.create(objectFactory));
1✔
194
        }
1✔
195

196
        newArguments.add(emptyCollection);
1✔
197
        continue;
1✔
198
      }
199

200
      // handle the base collection (it was built inline already)
201
      newArguments.add(existingArg);
1✔
202
    }
203

204
    return objectFactory.create(resultType, constructorArgTypes, newArguments);
1✔
205
  }
206
}
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