1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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);
58
59 private final ResultMap resultMap = new ResultMap();
60
61 public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
62 this(configuration, id, type, resultMappings, null);
63 }
64
65 public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings,
66 Boolean autoMapping) {
67 resultMap.configuration = configuration;
68 resultMap.id = id;
69 resultMap.type = type;
70 resultMap.resultMappings = resultMappings;
71 resultMap.autoMapping = autoMapping;
72 }
73
74 public Builder discriminator(Discriminator discriminator) {
75 resultMap.discriminator = discriminator;
76 return this;
77 }
78
79 public Class<?> type() {
80 return resultMap.type;
81 }
82
83 public ResultMap build() {
84 if (resultMap.id == null) {
85 throw new IllegalArgumentException("ResultMaps must have an id");
86 }
87 resultMap.mappedColumns = new HashSet<>();
88 resultMap.mappedProperties = new HashSet<>();
89 resultMap.idResultMappings = new ArrayList<>();
90 resultMap.constructorResultMappings = new ArrayList<>();
91 resultMap.propertyResultMappings = new ArrayList<>();
92 final List<String> constructorArgNames = new ArrayList<>();
93 for (ResultMapping resultMapping : resultMap.resultMappings) {
94 resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
95 resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps
96 || resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null;
97 final String column = resultMapping.getColumn();
98 if (column != null) {
99 resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
100 } else if (resultMapping.isCompositeResult()) {
101 for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
102 final String compositeColumn = compositeResultMapping.getColumn();
103 if (compositeColumn != null) {
104 resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
105 }
106 }
107 }
108 final String property = resultMapping.getProperty();
109 if (property != null) {
110 resultMap.mappedProperties.add(property);
111 }
112 if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
113 resultMap.constructorResultMappings.add(resultMapping);
114 if (resultMapping.getProperty() != null) {
115 constructorArgNames.add(resultMapping.getProperty());
116 }
117 } else {
118 resultMap.propertyResultMappings.add(resultMapping);
119 }
120 if (resultMapping.getFlags().contains(ResultFlag.ID)) {
121 resultMap.idResultMappings.add(resultMapping);
122 }
123 }
124 if (resultMap.idResultMappings.isEmpty()) {
125 resultMap.idResultMappings.addAll(resultMap.resultMappings);
126 }
127 if (!constructorArgNames.isEmpty()) {
128 final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
129 if (actualArgNames == null) {
130 throw new BuilderException("Error in result map '" + resultMap.id + "'. Failed to find a constructor in '"
131 + resultMap.getType().getName() + "' with arg names " + constructorArgNames
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) -> {
135 int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
136 int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
137 return paramIdx1 - paramIdx2;
138 });
139 }
140
141 resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
142 resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
143 resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
144 resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
145 resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
146 return resultMap;
147 }
148
149 private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
150 Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
151 for (Constructor<?> constructor : constructors) {
152 Class<?>[] paramTypes = constructor.getParameterTypes();
153 if (constructorArgNames.size() == paramTypes.length) {
154 List<String> paramNames = getArgNames(constructor);
155 if (constructorArgNames.containsAll(paramNames)
156 && argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
157 return paramNames;
158 }
159 }
160 }
161 return null;
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++) {
167 Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
168 Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
169 if (!actualType.equals(specifiedType)) {
170 if (log.isDebugEnabled()) {
171 log.debug("While building result map '" + resultMap.id + "', found a constructor with arg names "
172 + constructorArgNames + ", but the type of '" + constructorArgNames.get(i)
173 + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: [" + actualType.getName()
174 + "]");
175 }
176 return false;
177 }
178 }
179 return true;
180 }
181
182 private List<String> getArgNames(Constructor<?> constructor) {
183 List<String> paramNames = new ArrayList<>();
184 List<String> actualParamNames = null;
185 final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
186 int paramCount = paramAnnotations.length;
187 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
188 String name = null;
189 for (Annotation annotation : paramAnnotations[paramIndex]) {
190 if (annotation instanceof Param) {
191 name = ((Param) annotation).value();
192 break;
193 }
194 }
195 if (name == null && resultMap.configuration.isUseActualParamName()) {
196 if (actualParamNames == null) {
197 actualParamNames = ParamNameUtil.getParamNames(constructor);
198 }
199 if (actualParamNames.size() > paramIndex) {
200 name = actualParamNames.get(paramIndex);
201 }
202 }
203 paramNames.add(name != null ? name : "arg" + paramIndex);
204 }
205 return paramNames;
206 }
207 }
208
209 public String getId() {
210 return id;
211 }
212
213 public boolean hasNestedResultMaps() {
214 return hasNestedResultMaps;
215 }
216
217 public boolean hasNestedQueries() {
218 return hasNestedQueries;
219 }
220
221 public Class<?> getType() {
222 return type;
223 }
224
225 public List<ResultMapping> getResultMappings() {
226 return resultMappings;
227 }
228
229 public List<ResultMapping> getConstructorResultMappings() {
230 return constructorResultMappings;
231 }
232
233 public List<ResultMapping> getPropertyResultMappings() {
234 return propertyResultMappings;
235 }
236
237 public List<ResultMapping> getIdResultMappings() {
238 return idResultMappings;
239 }
240
241 public Set<String> getMappedColumns() {
242 return mappedColumns;
243 }
244
245 public Set<String> getMappedProperties() {
246 return mappedProperties;
247 }
248
249 public Discriminator getDiscriminator() {
250 return discriminator;
251 }
252
253 public void forceNestedResultMaps() {
254 hasNestedResultMaps = true;
255 }
256
257 public Boolean getAutoMapping() {
258 return autoMapping;
259 }
260
261 }