View Javadoc
1   /*
2    *    Copyright 2009-2023 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);
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       // lock down collections
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 }