ResultMap.java

  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. import java.lang.annotation.Annotation;
  18. import java.lang.reflect.Constructor;
  19. import java.util.ArrayList;
  20. import java.util.Collections;
  21. import java.util.HashSet;
  22. import java.util.List;
  23. import java.util.Locale;
  24. import java.util.Set;

  25. import org.apache.ibatis.annotations.Param;
  26. import org.apache.ibatis.builder.BuilderException;
  27. import org.apache.ibatis.logging.Log;
  28. import org.apache.ibatis.logging.LogFactory;
  29. import org.apache.ibatis.reflection.ParamNameUtil;
  30. import org.apache.ibatis.session.Configuration;

  31. /**
  32.  * @author Clinton Begin
  33.  */
  34. public class ResultMap {
  35.   private Configuration configuration;

  36.   private String id;
  37.   private Class<?> type;
  38.   private List<ResultMapping> resultMappings;
  39.   private List<ResultMapping> idResultMappings;
  40.   private List<ResultMapping> constructorResultMappings;
  41.   private List<ResultMapping> propertyResultMappings;
  42.   private Set<String> mappedColumns;
  43.   private Set<String> mappedProperties;
  44.   private Discriminator discriminator;
  45.   private boolean hasNestedResultMaps;
  46.   private boolean hasNestedQueries;
  47.   private Boolean autoMapping;

  48.   private ResultMap() {
  49.   }

  50.   public static class Builder {
  51.     private static final Log log = LogFactory.getLog(Builder.class);

  52.     private ResultMap resultMap = new ResultMap();

  53.     public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
  54.       this(configuration, id, type, resultMappings, null);
  55.     }

  56.     public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings, Boolean autoMapping) {
  57.       resultMap.configuration = configuration;
  58.       resultMap.id = id;
  59.       resultMap.type = type;
  60.       resultMap.resultMappings = resultMappings;
  61.       resultMap.autoMapping = autoMapping;
  62.     }

  63.     public Builder discriminator(Discriminator discriminator) {
  64.       resultMap.discriminator = discriminator;
  65.       return this;
  66.     }

  67.     public Class<?> type() {
  68.       return resultMap.type;
  69.     }

  70.     public ResultMap build() {
  71.       if (resultMap.id == null) {
  72.         throw new IllegalArgumentException("ResultMaps must have an id");
  73.       }
  74.       resultMap.mappedColumns = new HashSet<>();
  75.       resultMap.mappedProperties = new HashSet<>();
  76.       resultMap.idResultMappings = new ArrayList<>();
  77.       resultMap.constructorResultMappings = new ArrayList<>();
  78.       resultMap.propertyResultMappings = new ArrayList<>();
  79.       final List<String> constructorArgNames = new ArrayList<>();
  80.       for (ResultMapping resultMapping : resultMap.resultMappings) {
  81.         resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
  82.         resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
  83.         final String column = resultMapping.getColumn();
  84.         if (column != null) {
  85.           resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
  86.         } else if (resultMapping.isCompositeResult()) {
  87.           for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
  88.             final String compositeColumn = compositeResultMapping.getColumn();
  89.             if (compositeColumn != null) {
  90.               resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
  91.             }
  92.           }
  93.         }
  94.         final String property = resultMapping.getProperty();
  95.         if (property != null) {
  96.           resultMap.mappedProperties.add(property);
  97.         }
  98.         if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
  99.           resultMap.constructorResultMappings.add(resultMapping);
  100.           if (resultMapping.getProperty() != null) {
  101.             constructorArgNames.add(resultMapping.getProperty());
  102.           }
  103.         } else {
  104.           resultMap.propertyResultMappings.add(resultMapping);
  105.         }
  106.         if (resultMapping.getFlags().contains(ResultFlag.ID)) {
  107.           resultMap.idResultMappings.add(resultMapping);
  108.         }
  109.       }
  110.       if (resultMap.idResultMappings.isEmpty()) {
  111.         resultMap.idResultMappings.addAll(resultMap.resultMappings);
  112.       }
  113.       if (!constructorArgNames.isEmpty()) {
  114.         final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
  115.         if (actualArgNames == null) {
  116.           throw new BuilderException("Error in result map '" + resultMap.id
  117.               + "'. Failed to find a constructor in '"
  118.               + resultMap.getType().getName() + "' by arg names " + constructorArgNames
  119.               + ". There might be more info in debug log.");
  120.         }
  121.         resultMap.constructorResultMappings.sort((o1, o2) -> {
  122.           int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
  123.           int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
  124.           return paramIdx1 - paramIdx2;
  125.         });
  126.       }
  127.       // lock down collections
  128.       resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
  129.       resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
  130.       resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
  131.       resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
  132.       resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
  133.       return resultMap;
  134.     }

  135.     private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
  136.       Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
  137.       for (Constructor<?> constructor : constructors) {
  138.         Class<?>[] paramTypes = constructor.getParameterTypes();
  139.         if (constructorArgNames.size() == paramTypes.length) {
  140.           List<String> paramNames = getArgNames(constructor);
  141.           if (constructorArgNames.containsAll(paramNames)
  142.               && argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
  143.             return paramNames;
  144.           }
  145.         }
  146.       }
  147.       return null;
  148.     }

  149.     private boolean argTypesMatch(final List<String> constructorArgNames,
  150.         Class<?>[] paramTypes, List<String> paramNames) {
  151.       for (int i = 0; i < constructorArgNames.size(); i++) {
  152.         Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
  153.         Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
  154.         if (!actualType.equals(specifiedType)) {
  155.           if (log.isDebugEnabled()) {
  156.             log.debug("While building result map '" + resultMap.id
  157.                 + "', found a constructor with arg names " + constructorArgNames
  158.                 + ", but the type of '" + constructorArgNames.get(i)
  159.                 + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
  160.                 + actualType.getName() + "]");
  161.           }
  162.           return false;
  163.         }
  164.       }
  165.       return true;
  166.     }

  167.     private List<String> getArgNames(Constructor<?> constructor) {
  168.       List<String> paramNames = new ArrayList<>();
  169.       List<String> actualParamNames = null;
  170.       final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
  171.       int paramCount = paramAnnotations.length;
  172.       for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
  173.         String name = null;
  174.         for (Annotation annotation : paramAnnotations[paramIndex]) {
  175.           if (annotation instanceof Param) {
  176.             name = ((Param) annotation).value();
  177.             break;
  178.           }
  179.         }
  180.         if (name == null && resultMap.configuration.isUseActualParamName()) {
  181.           if (actualParamNames == null) {
  182.             actualParamNames = ParamNameUtil.getParamNames(constructor);
  183.           }
  184.           if (actualParamNames.size() > paramIndex) {
  185.             name = actualParamNames.get(paramIndex);
  186.           }
  187.         }
  188.         paramNames.add(name != null ? name : "arg" + paramIndex);
  189.       }
  190.       return paramNames;
  191.     }
  192.   }

  193.   public String getId() {
  194.     return id;
  195.   }

  196.   public boolean hasNestedResultMaps() {
  197.     return hasNestedResultMaps;
  198.   }

  199.   public boolean hasNestedQueries() {
  200.     return hasNestedQueries;
  201.   }

  202.   public Class<?> getType() {
  203.     return type;
  204.   }

  205.   public List<ResultMapping> getResultMappings() {
  206.     return resultMappings;
  207.   }

  208.   public List<ResultMapping> getConstructorResultMappings() {
  209.     return constructorResultMappings;
  210.   }

  211.   public List<ResultMapping> getPropertyResultMappings() {
  212.     return propertyResultMappings;
  213.   }

  214.   public List<ResultMapping> getIdResultMappings() {
  215.     return idResultMappings;
  216.   }

  217.   public Set<String> getMappedColumns() {
  218.     return mappedColumns;
  219.   }

  220.   public Set<String> getMappedProperties() {
  221.     return mappedProperties;
  222.   }

  223.   public Discriminator getDiscriminator() {
  224.     return discriminator;
  225.   }

  226.   public void forceNestedResultMaps() {
  227.     hasNestedResultMaps = true;
  228.   }

  229.   public Boolean getAutoMapping() {
  230.     return autoMapping;
  231.   }

  232. }