ResultMap.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.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 final 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,
  57.         Boolean autoMapping) {
  58.       resultMap.configuration = configuration;
  59.       resultMap.id = id;
  60.       resultMap.type = type;
  61.       resultMap.resultMappings = resultMappings;
  62.       resultMap.autoMapping = autoMapping;
  63.     }

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

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

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

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

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