View Javadoc
1   /*
2    *    Copyright 2009-2021 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    *       http://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);
58  
59      private 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, Boolean autoMapping) {
66        resultMap.configuration = configuration;
67        resultMap.id = id;
68        resultMap.type = type;
69        resultMap.resultMappings = resultMappings;
70        resultMap.autoMapping = autoMapping;
71      }
72  
73      public Builder discriminator(Discriminator discriminator) {
74        resultMap.discriminator = discriminator;
75        return this;
76      }
77  
78      public Class<?> type() {
79        return resultMap.type;
80      }
81  
82      public ResultMap build() {
83        if (resultMap.id == null) {
84          throw new IllegalArgumentException("ResultMaps must have an id");
85        }
86        resultMap.mappedColumns = new HashSet<>();
87        resultMap.mappedProperties = new HashSet<>();
88        resultMap.idResultMappings = new ArrayList<>();
89        resultMap.constructorResultMappings = new ArrayList<>();
90        resultMap.propertyResultMappings = new ArrayList<>();
91        final List<String> constructorArgNames = new ArrayList<>();
92        for (ResultMapping resultMapping : resultMap.resultMappings) {
93          resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
94          resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
95          final String column = resultMapping.getColumn();
96          if (column != null) {
97            resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
98          } else if (resultMapping.isCompositeResult()) {
99            for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
100             final String compositeColumn = compositeResultMapping.getColumn();
101             if (compositeColumn != null) {
102               resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
103             }
104           }
105         }
106         final String property = resultMapping.getProperty();
107         if (property != null) {
108           resultMap.mappedProperties.add(property);
109         }
110         if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
111           resultMap.constructorResultMappings.add(resultMapping);
112           if (resultMapping.getProperty() != null) {
113             constructorArgNames.add(resultMapping.getProperty());
114           }
115         } else {
116           resultMap.propertyResultMappings.add(resultMapping);
117         }
118         if (resultMapping.getFlags().contains(ResultFlag.ID)) {
119           resultMap.idResultMappings.add(resultMapping);
120         }
121       }
122       if (resultMap.idResultMappings.isEmpty()) {
123         resultMap.idResultMappings.addAll(resultMap.resultMappings);
124       }
125       if (!constructorArgNames.isEmpty()) {
126         final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
127         if (actualArgNames == null) {
128           throw new BuilderException("Error in result map '" + resultMap.id
129               + "'. Failed to find a constructor in '"
130               + resultMap.getType().getName() + "' by arg names " + constructorArgNames
131               + ". There might be more info in debug log.");
132         }
133         resultMap.constructorResultMappings.sort((o1, o2) -> {
134           int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
135           int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
136           return paramIdx1 - paramIdx2;
137         });
138       }
139       // lock down collections
140       resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
141       resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
142       resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
143       resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
144       resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
145       return resultMap;
146     }
147 
148     private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
149       Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
150       for (Constructor<?> constructor : constructors) {
151         Class<?>[] paramTypes = constructor.getParameterTypes();
152         if (constructorArgNames.size() == paramTypes.length) {
153           List<String> paramNames = getArgNames(constructor);
154           if (constructorArgNames.containsAll(paramNames)
155               && argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
156             return paramNames;
157           }
158         }
159       }
160       return null;
161     }
162 
163     private boolean argTypesMatch(final List<String> constructorArgNames,
164         Class<?>[] paramTypes, List<String> paramNames) {
165       for (int i = 0; i < constructorArgNames.size(); i++) {
166         Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
167         Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
168         if (!actualType.equals(specifiedType)) {
169           if (log.isDebugEnabled()) {
170             log.debug("While building result map '" + resultMap.id
171                 + "', found a constructor with arg names " + constructorArgNames
172                 + ", but the type of '" + constructorArgNames.get(i)
173                 + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
174                 + actualType.getName() + "]");
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 }