MapperAnnotationBuilder.java

  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.builder.annotation;

  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.lang.annotation.Annotation;
  20. import java.lang.reflect.Array;
  21. import java.lang.reflect.GenericArrayType;
  22. import java.lang.reflect.Method;
  23. import java.lang.reflect.ParameterizedType;
  24. import java.lang.reflect.Type;
  25. import java.util.ArrayList;
  26. import java.util.Arrays;
  27. import java.util.Collection;
  28. import java.util.HashMap;
  29. import java.util.Iterator;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.Optional;
  33. import java.util.Properties;
  34. import java.util.Set;
  35. import java.util.stream.Collectors;
  36. import java.util.stream.Stream;

  37. import org.apache.ibatis.annotations.Arg;
  38. import org.apache.ibatis.annotations.CacheNamespace;
  39. import org.apache.ibatis.annotations.CacheNamespaceRef;
  40. import org.apache.ibatis.annotations.Case;
  41. import org.apache.ibatis.annotations.Delete;
  42. import org.apache.ibatis.annotations.DeleteProvider;
  43. import org.apache.ibatis.annotations.Insert;
  44. import org.apache.ibatis.annotations.InsertProvider;
  45. import org.apache.ibatis.annotations.Lang;
  46. import org.apache.ibatis.annotations.MapKey;
  47. import org.apache.ibatis.annotations.Options;
  48. import org.apache.ibatis.annotations.Options.FlushCachePolicy;
  49. import org.apache.ibatis.annotations.Property;
  50. import org.apache.ibatis.annotations.Result;
  51. import org.apache.ibatis.annotations.ResultMap;
  52. import org.apache.ibatis.annotations.ResultType;
  53. import org.apache.ibatis.annotations.Results;
  54. import org.apache.ibatis.annotations.Select;
  55. import org.apache.ibatis.annotations.SelectKey;
  56. import org.apache.ibatis.annotations.SelectProvider;
  57. import org.apache.ibatis.annotations.TypeDiscriminator;
  58. import org.apache.ibatis.annotations.Update;
  59. import org.apache.ibatis.annotations.UpdateProvider;
  60. import org.apache.ibatis.binding.MapperMethod.ParamMap;
  61. import org.apache.ibatis.builder.BuilderException;
  62. import org.apache.ibatis.builder.CacheRefResolver;
  63. import org.apache.ibatis.builder.IncompleteElementException;
  64. import org.apache.ibatis.builder.MapperBuilderAssistant;
  65. import org.apache.ibatis.builder.xml.XMLMapperBuilder;
  66. import org.apache.ibatis.cursor.Cursor;
  67. import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
  68. import org.apache.ibatis.executor.keygen.KeyGenerator;
  69. import org.apache.ibatis.executor.keygen.NoKeyGenerator;
  70. import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
  71. import org.apache.ibatis.io.Resources;
  72. import org.apache.ibatis.mapping.Discriminator;
  73. import org.apache.ibatis.mapping.FetchType;
  74. import org.apache.ibatis.mapping.MappedStatement;
  75. import org.apache.ibatis.mapping.ResultFlag;
  76. import org.apache.ibatis.mapping.ResultMapping;
  77. import org.apache.ibatis.mapping.ResultSetType;
  78. import org.apache.ibatis.mapping.SqlCommandType;
  79. import org.apache.ibatis.mapping.SqlSource;
  80. import org.apache.ibatis.mapping.StatementType;
  81. import org.apache.ibatis.parsing.PropertyParser;
  82. import org.apache.ibatis.reflection.TypeParameterResolver;
  83. import org.apache.ibatis.scripting.LanguageDriver;
  84. import org.apache.ibatis.session.Configuration;
  85. import org.apache.ibatis.session.ResultHandler;
  86. import org.apache.ibatis.session.RowBounds;
  87. import org.apache.ibatis.type.JdbcType;
  88. import org.apache.ibatis.type.TypeHandler;
  89. import org.apache.ibatis.type.UnknownTypeHandler;

  90. /**
  91.  * @author Clinton Begin
  92.  * @author Kazuki Shimizu
  93.  */
  94. public class MapperAnnotationBuilder {

  95.   private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
  96.       .of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
  97.           InsertProvider.class, DeleteProvider.class)
  98.       .collect(Collectors.toSet());

  99.   private final Configuration configuration;
  100.   private final MapperBuilderAssistant assistant;
  101.   private final Class<?> type;

  102.   public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
  103.     String resource = type.getName().replace('.', '/') + ".java (best guess)";
  104.     this.assistant = new MapperBuilderAssistant(configuration, resource);
  105.     this.configuration = configuration;
  106.     this.type = type;
  107.   }

  108.   public void parse() {
  109.     String resource = type.toString();
  110.     if (!configuration.isResourceLoaded(resource)) {
  111.       loadXmlResource();
  112.       configuration.addLoadedResource(resource);
  113.       assistant.setCurrentNamespace(type.getName());
  114.       parseCache();
  115.       parseCacheRef();
  116.       for (Method method : type.getMethods()) {
  117.         if (!canHaveStatement(method)) {
  118.           continue;
  119.         }
  120.         if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
  121.             && method.getAnnotation(ResultMap.class) == null) {
  122.           parseResultMap(method);
  123.         }
  124.         try {
  125.           parseStatement(method);
  126.         } catch (IncompleteElementException e) {
  127.           configuration.addIncompleteMethod(new MethodResolver(this, method));
  128.         }
  129.       }
  130.     }
  131.     parsePendingMethods();
  132.   }

  133.   private static boolean canHaveStatement(Method method) {
  134.     // issue #237
  135.     return !method.isBridge() && !method.isDefault();
  136.   }

  137.   private void parsePendingMethods() {
  138.     Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
  139.     synchronized (incompleteMethods) {
  140.       Iterator<MethodResolver> iter = incompleteMethods.iterator();
  141.       while (iter.hasNext()) {
  142.         try {
  143.           iter.next().resolve();
  144.           iter.remove();
  145.         } catch (IncompleteElementException e) {
  146.           // This method is still missing a resource
  147.         }
  148.       }
  149.     }
  150.   }

  151.   private void loadXmlResource() {
  152.     // Spring may not know the real resource name so we check a flag
  153.     // to prevent loading again a resource twice
  154.     // this flag is set at XMLMapperBuilder#bindMapperForNamespace
  155.     if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
  156.       String xmlResource = type.getName().replace('.', '/') + ".xml";
  157.       // #1347
  158.       InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
  159.       if (inputStream == null) {
  160.         // Search XML mapper that is not in the module but in the classpath.
  161.         try {
  162.           inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
  163.         } catch (IOException e2) {
  164.           // ignore, resource is not required
  165.         }
  166.       }
  167.       if (inputStream != null) {
  168.         XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource,
  169.             configuration.getSqlFragments(), type.getName());
  170.         xmlParser.parse();
  171.       }
  172.     }
  173.   }

  174.   private void parseCache() {
  175.     CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
  176.     if (cacheDomain != null) {
  177.       Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
  178.       Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
  179.       Properties props = convertToProperties(cacheDomain.properties());
  180.       assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size,
  181.           cacheDomain.readWrite(), cacheDomain.blocking(), props);
  182.     }
  183.   }

  184.   private Properties convertToProperties(Property[] properties) {
  185.     if (properties.length == 0) {
  186.       return null;
  187.     }
  188.     Properties props = new Properties();
  189.     for (Property property : properties) {
  190.       props.setProperty(property.name(), PropertyParser.parse(property.value(), configuration.getVariables()));
  191.     }
  192.     return props;
  193.   }

  194.   private void parseCacheRef() {
  195.     CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
  196.     if (cacheDomainRef != null) {
  197.       Class<?> refType = cacheDomainRef.value();
  198.       String refName = cacheDomainRef.name();
  199.       if (refType == void.class && refName.isEmpty()) {
  200.         throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
  201.       }
  202.       if (refType != void.class && !refName.isEmpty()) {
  203.         throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
  204.       }
  205.       String namespace = refType != void.class ? refType.getName() : refName;
  206.       try {
  207.         assistant.useCacheRef(namespace);
  208.       } catch (IncompleteElementException e) {
  209.         configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
  210.       }
  211.     }
  212.   }

  213.   private String parseResultMap(Method method) {
  214.     Class<?> returnType = getReturnType(method, type);
  215.     Arg[] args = method.getAnnotationsByType(Arg.class);
  216.     Result[] results = method.getAnnotationsByType(Result.class);
  217.     TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
  218.     String resultMapId = generateResultMapName(method);
  219.     applyResultMap(resultMapId, returnType, args, results, typeDiscriminator);
  220.     return resultMapId;
  221.   }

  222.   private String generateResultMapName(Method method) {
  223.     Results results = method.getAnnotation(Results.class);
  224.     if (results != null && !results.id().isEmpty()) {
  225.       return type.getName() + "." + results.id();
  226.     }
  227.     StringBuilder suffix = new StringBuilder();
  228.     for (Class<?> c : method.getParameterTypes()) {
  229.       suffix.append("-");
  230.       suffix.append(c.getSimpleName());
  231.     }
  232.     if (suffix.length() < 1) {
  233.       suffix.append("-void");
  234.     }
  235.     return type.getName() + "." + method.getName() + suffix;
  236.   }

  237.   private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results,
  238.       TypeDiscriminator discriminator) {
  239.     List<ResultMapping> resultMappings = new ArrayList<>();
  240.     applyConstructorArgs(args, returnType, resultMappings);
  241.     applyResults(results, returnType, resultMappings);
  242.     Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
  243.     // TODO add AutoMappingBehaviour
  244.     assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
  245.     createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
  246.   }

  247.   private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
  248.     if (discriminator != null) {
  249.       for (Case c : discriminator.cases()) {
  250.         String caseResultMapId = resultMapId + "-" + c.value();
  251.         List<ResultMapping> resultMappings = new ArrayList<>();
  252.         // issue #136
  253.         applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
  254.         applyResults(c.results(), resultType, resultMappings);
  255.         // TODO add AutoMappingBehaviour
  256.         assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
  257.       }
  258.     }
  259.   }

  260.   private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
  261.     if (discriminator != null) {
  262.       String column = discriminator.column();
  263.       Class<?> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
  264.       JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
  265.       @SuppressWarnings("unchecked")
  266.       Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) (discriminator
  267.           .typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler());
  268.       Case[] cases = discriminator.cases();
  269.       Map<String, String> discriminatorMap = new HashMap<>();
  270.       for (Case c : cases) {
  271.         String value = c.value();
  272.         String caseResultMapId = resultMapId + "-" + value;
  273.         discriminatorMap.put(value, caseResultMapId);
  274.       }
  275.       return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
  276.     }
  277.     return null;
  278.   }

  279.   void parseStatement(Method method) {
  280.     final Class<?> parameterTypeClass = getParameterType(method);
  281.     final LanguageDriver languageDriver = getLanguageDriver(method);

  282.     getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
  283.       final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass,
  284.           languageDriver, method);
  285.       final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
  286.       final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options) x.getAnnotation())
  287.           .orElse(null);
  288.       final String mappedStatementId = type.getName() + "." + method.getName();

  289.       final KeyGenerator keyGenerator;
  290.       String keyProperty = null;
  291.       String keyColumn = null;
  292.       if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
  293.         // first check for SelectKey annotation - that overrides everything else
  294.         SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class)
  295.             .map(x -> (SelectKey) x.getAnnotation()).orElse(null);
  296.         if (selectKey != null) {
  297.           keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method),
  298.               languageDriver);
  299.           keyProperty = selectKey.keyProperty();
  300.         } else if (options == null) {
  301.           keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  302.         } else {
  303.           keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  304.           keyProperty = options.keyProperty();
  305.           keyColumn = options.keyColumn();
  306.         }
  307.       } else {
  308.         keyGenerator = NoKeyGenerator.INSTANCE;
  309.       }

  310.       Integer fetchSize = null;
  311.       Integer timeout = null;
  312.       StatementType statementType = StatementType.PREPARED;
  313.       ResultSetType resultSetType = configuration.getDefaultResultSetType();
  314.       boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  315.       boolean flushCache = !isSelect;
  316.       boolean useCache = isSelect;
  317.       if (options != null) {
  318.         if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
  319.           flushCache = true;
  320.         } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
  321.           flushCache = false;
  322.         }
  323.         useCache = options.useCache();
  324.         // issue #348
  325.         fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null;
  326.         timeout = options.timeout() > -1 ? options.timeout() : null;
  327.         statementType = options.statementType();
  328.         if (options.resultSetType() != ResultSetType.DEFAULT) {
  329.           resultSetType = options.resultSetType();
  330.         }
  331.       }

  332.       String resultMapId = null;
  333.       if (isSelect) {
  334.         ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
  335.         if (resultMapAnnotation != null) {
  336.           resultMapId = String.join(",", resultMapAnnotation.value());
  337.         } else {
  338.           resultMapId = generateResultMapName(method);
  339.         }
  340.       }

  341.       assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
  342.           // ParameterMapID
  343.           null, parameterTypeClass, resultMapId, getReturnType(method, type), resultSetType, flushCache, useCache,
  344.           // TODO gcode issue #577
  345.           false, keyGenerator, keyProperty, keyColumn, statementAnnotation.getDatabaseId(), languageDriver,
  346.           // ResultSets
  347.           options != null ? nullOrEmpty(options.resultSets()) : null, statementAnnotation.isDirtySelect());
  348.     });
  349.   }

  350.   private LanguageDriver getLanguageDriver(Method method) {
  351.     Lang lang = method.getAnnotation(Lang.class);
  352.     Class<? extends LanguageDriver> langClass = null;
  353.     if (lang != null) {
  354.       langClass = lang.value();
  355.     }
  356.     return configuration.getLanguageDriver(langClass);
  357.   }

  358.   private Class<?> getParameterType(Method method) {
  359.     Class<?> parameterType = null;
  360.     Class<?>[] parameterTypes = method.getParameterTypes();
  361.     for (Class<?> currentParameterType : parameterTypes) {
  362.       if (!RowBounds.class.isAssignableFrom(currentParameterType)
  363.           && !ResultHandler.class.isAssignableFrom(currentParameterType)) {
  364.         if (parameterType == null) {
  365.           parameterType = currentParameterType;
  366.         } else {
  367.           // issue #135
  368.           parameterType = ParamMap.class;
  369.         }
  370.       }
  371.     }
  372.     return parameterType;
  373.   }

  374.   private static Class<?> getReturnType(Method method, Class<?> type) {
  375.     Class<?> returnType = method.getReturnType();
  376.     Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
  377.     if (resolvedReturnType instanceof Class) {
  378.       returnType = (Class<?>) resolvedReturnType;
  379.       if (returnType.isArray()) {
  380.         returnType = returnType.getComponentType();
  381.       }
  382.       // gcode issue #508
  383.       if (void.class.equals(returnType)) {
  384.         ResultType rt = method.getAnnotation(ResultType.class);
  385.         if (rt != null) {
  386.           returnType = rt.value();
  387.         }
  388.       }
  389.     } else if (resolvedReturnType instanceof ParameterizedType) {
  390.       ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
  391.       Class<?> rawType = (Class<?>) parameterizedType.getRawType();
  392.       if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) {
  393.         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
  394.         if (actualTypeArguments != null && actualTypeArguments.length == 1) {
  395.           Type returnTypeParameter = actualTypeArguments[0];
  396.           if (returnTypeParameter instanceof Class<?>) {
  397.             returnType = (Class<?>) returnTypeParameter;
  398.           } else if (returnTypeParameter instanceof ParameterizedType) {
  399.             // (gcode issue #443) actual type can be a also a parameterized type
  400.             returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
  401.           } else if (returnTypeParameter instanceof GenericArrayType) {
  402.             Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
  403.             // (gcode issue #525) support List<byte[]>
  404.             returnType = Array.newInstance(componentType, 0).getClass();
  405.           }
  406.         }
  407.       } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
  408.         // (gcode issue 504) Do not look into Maps if there is not MapKey annotation
  409.         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
  410.         if (actualTypeArguments != null && actualTypeArguments.length == 2) {
  411.           Type returnTypeParameter = actualTypeArguments[1];
  412.           if (returnTypeParameter instanceof Class<?>) {
  413.             returnType = (Class<?>) returnTypeParameter;
  414.           } else if (returnTypeParameter instanceof ParameterizedType) {
  415.             // (gcode issue 443) actual type can be a also a parameterized type
  416.             returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
  417.           }
  418.         }
  419.       } else if (Optional.class.equals(rawType)) {
  420.         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
  421.         Type returnTypeParameter = actualTypeArguments[0];
  422.         if (returnTypeParameter instanceof Class<?>) {
  423.           returnType = (Class<?>) returnTypeParameter;
  424.         }
  425.       }
  426.     }

  427.     return returnType;
  428.   }

  429.   private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
  430.     for (Result result : results) {
  431.       List<ResultFlag> flags = new ArrayList<>();
  432.       if (result.id()) {
  433.         flags.add(ResultFlag.ID);
  434.       }
  435.       @SuppressWarnings("unchecked")
  436.       Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) (result
  437.           .typeHandler() == UnknownTypeHandler.class ? null : result.typeHandler());
  438.       boolean hasNestedResultMap = hasNestedResultMap(result);
  439.       ResultMapping resultMapping = assistant.buildResultMapping(resultType, nullOrEmpty(result.property()),
  440.           nullOrEmpty(result.column()), result.javaType() == void.class ? null : result.javaType(),
  441.           result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
  442.           hasNestedSelect(result) ? nestedSelectId(result) : null,
  443.           hasNestedResultMap ? nestedResultMapId(result) : null, null,
  444.           hasNestedResultMap ? findColumnPrefix(result) : null, typeHandler, flags, null, null, isLazy(result));
  445.       resultMappings.add(resultMapping);
  446.     }
  447.   }

  448.   private String findColumnPrefix(Result result) {
  449.     String columnPrefix = result.one().columnPrefix();
  450.     if (columnPrefix.length() < 1) {
  451.       columnPrefix = result.many().columnPrefix();
  452.     }
  453.     return columnPrefix;
  454.   }

  455.   private String nestedResultMapId(Result result) {
  456.     String resultMapId = result.one().resultMap();
  457.     if (resultMapId.length() < 1) {
  458.       resultMapId = result.many().resultMap();
  459.     }
  460.     if (!resultMapId.contains(".")) {
  461.       resultMapId = type.getName() + "." + resultMapId;
  462.     }
  463.     return resultMapId;
  464.   }

  465.   private boolean hasNestedResultMap(Result result) {
  466.     if (result.one().resultMap().length() > 0 && result.many().resultMap().length() > 0) {
  467.       throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
  468.     }
  469.     return result.one().resultMap().length() > 0 || result.many().resultMap().length() > 0;
  470.   }

  471.   private String nestedSelectId(Result result) {
  472.     String nestedSelect = result.one().select();
  473.     if (nestedSelect.length() < 1) {
  474.       nestedSelect = result.many().select();
  475.     }
  476.     if (!nestedSelect.contains(".")) {
  477.       nestedSelect = type.getName() + "." + nestedSelect;
  478.     }
  479.     return nestedSelect;
  480.   }

  481.   private boolean isLazy(Result result) {
  482.     boolean isLazy = configuration.isLazyLoadingEnabled();
  483.     if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
  484.       isLazy = result.one().fetchType() == FetchType.LAZY;
  485.     } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
  486.       isLazy = result.many().fetchType() == FetchType.LAZY;
  487.     }
  488.     return isLazy;
  489.   }

  490.   private boolean hasNestedSelect(Result result) {
  491.     if (result.one().select().length() > 0 && result.many().select().length() > 0) {
  492.       throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
  493.     }
  494.     return result.one().select().length() > 0 || result.many().select().length() > 0;
  495.   }

  496.   private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
  497.     for (Arg arg : args) {
  498.       List<ResultFlag> flags = new ArrayList<>();
  499.       flags.add(ResultFlag.CONSTRUCTOR);
  500.       if (arg.id()) {
  501.         flags.add(ResultFlag.ID);
  502.       }
  503.       @SuppressWarnings("unchecked")
  504.       Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) (arg
  505.           .typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler());
  506.       ResultMapping resultMapping = assistant.buildResultMapping(resultType, nullOrEmpty(arg.name()),
  507.           nullOrEmpty(arg.column()), arg.javaType() == void.class ? null : arg.javaType(),
  508.           arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(), nullOrEmpty(arg.select()),
  509.           nullOrEmpty(arg.resultMap()), null, nullOrEmpty(arg.columnPrefix()), typeHandler, flags, null, null, false);
  510.       resultMappings.add(resultMapping);
  511.     }
  512.   }

  513.   private String nullOrEmpty(String value) {
  514.     return value == null || value.trim().length() == 0 ? null : value;
  515.   }

  516.   private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId,
  517.       Class<?> parameterTypeClass, LanguageDriver languageDriver) {
  518.     String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  519.     Class<?> resultTypeClass = selectKeyAnnotation.resultType();
  520.     StatementType statementType = selectKeyAnnotation.statementType();
  521.     String keyProperty = selectKeyAnnotation.keyProperty();
  522.     String keyColumn = selectKeyAnnotation.keyColumn();
  523.     boolean executeBefore = selectKeyAnnotation.before();

  524.     // defaults
  525.     boolean useCache = false;
  526.     KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
  527.     Integer fetchSize = null;
  528.     Integer timeout = null;
  529.     boolean flushCache = false;
  530.     String parameterMap = null;
  531.     String resultMap = null;
  532.     ResultSetType resultSetTypeEnum = null;
  533.     String databaseId = selectKeyAnnotation.databaseId().isEmpty() ? null : selectKeyAnnotation.databaseId();

  534.     SqlSource sqlSource = buildSqlSource(selectKeyAnnotation, parameterTypeClass, languageDriver, null);
  535.     SqlCommandType sqlCommandType = SqlCommandType.SELECT;

  536.     assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
  537.         parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, false, keyGenerator,
  538.         keyProperty, keyColumn, databaseId, languageDriver, null, false);

  539.     id = assistant.applyCurrentNamespace(id, false);

  540.     MappedStatement keyStatement = configuration.getMappedStatement(id, false);
  541.     SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
  542.     configuration.addKeyGenerator(id, answer);
  543.     return answer;
  544.   }

  545.   private SqlSource buildSqlSource(Annotation annotation, Class<?> parameterType, LanguageDriver languageDriver,
  546.       Method method) {
  547.     if (annotation instanceof Select) {
  548.       return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver);
  549.     }
  550.     if (annotation instanceof Update) {
  551.       return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver);
  552.     } else if (annotation instanceof Insert) {
  553.       return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver);
  554.     } else if (annotation instanceof Delete) {
  555.       return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver);
  556.     } else if (annotation instanceof SelectKey) {
  557.       return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver);
  558.     }
  559.     return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method);
  560.   }

  561.   private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass,
  562.       LanguageDriver languageDriver) {
  563.     return languageDriver.createSqlSource(configuration, String.join(" ", strings).trim(), parameterTypeClass);
  564.   }

  565.   @SafeVarargs
  566.   private final Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch,
  567.       Class<? extends Annotation>... targetTypes) {
  568.     return getAnnotationWrapper(method, errorIfNoMatch, Arrays.asList(targetTypes));
  569.   }

  570.   private Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch,
  571.       Collection<Class<? extends Annotation>> targetTypes) {
  572.     String databaseId = configuration.getDatabaseId();
  573.     Map<String, AnnotationWrapper> statementAnnotations = targetTypes.stream()
  574.         .flatMap(x -> Arrays.stream(method.getAnnotationsByType(x))).map(AnnotationWrapper::new)
  575.         .collect(Collectors.toMap(AnnotationWrapper::getDatabaseId, x -> x, (existing, duplicate) -> {
  576.           throw new BuilderException(
  577.               String.format("Detected conflicting annotations '%s' and '%s' on '%s'.", existing.getAnnotation(),
  578.                   duplicate.getAnnotation(), method.getDeclaringClass().getName() + "." + method.getName()));
  579.         }));
  580.     AnnotationWrapper annotationWrapper = null;
  581.     if (databaseId != null) {
  582.       annotationWrapper = statementAnnotations.get(databaseId);
  583.     }
  584.     if (annotationWrapper == null) {
  585.       annotationWrapper = statementAnnotations.get("");
  586.     }
  587.     if (errorIfNoMatch && annotationWrapper == null && !statementAnnotations.isEmpty()) {
  588.       // Annotations exist, but there is no matching one for the specified databaseId
  589.       throw new BuilderException(String.format(
  590.           "Could not find a statement annotation that correspond a current database or default statement on method '%s.%s'. Current database id is [%s].",
  591.           method.getDeclaringClass().getName(), method.getName(), databaseId));
  592.     }
  593.     return Optional.ofNullable(annotationWrapper);
  594.   }

  595.   public static Class<?> getMethodReturnType(String mapperFqn, String localStatementId) {
  596.     if (mapperFqn == null || localStatementId == null) {
  597.       return null;
  598.     }
  599.     try {
  600.       Class<?> mapperClass = Resources.classForName(mapperFqn);
  601.       for (Method method : mapperClass.getMethods()) {
  602.         if (method.getName().equals(localStatementId) && canHaveStatement(method)) {
  603.           return getReturnType(method, mapperClass);
  604.         }
  605.       }
  606.     } catch (ClassNotFoundException e) {
  607.       // No corresponding mapper interface which is OK
  608.     }
  609.     return null;
  610.   }

  611.   private static class AnnotationWrapper {
  612.     private final Annotation annotation;
  613.     private final String databaseId;
  614.     private final SqlCommandType sqlCommandType;
  615.     private boolean dirtySelect;

  616.     AnnotationWrapper(Annotation annotation) {
  617.       this.annotation = annotation;
  618.       if (annotation instanceof Select) {
  619.         databaseId = ((Select) annotation).databaseId();
  620.         sqlCommandType = SqlCommandType.SELECT;
  621.         dirtySelect = ((Select) annotation).affectData();
  622.       } else if (annotation instanceof Update) {
  623.         databaseId = ((Update) annotation).databaseId();
  624.         sqlCommandType = SqlCommandType.UPDATE;
  625.       } else if (annotation instanceof Insert) {
  626.         databaseId = ((Insert) annotation).databaseId();
  627.         sqlCommandType = SqlCommandType.INSERT;
  628.       } else if (annotation instanceof Delete) {
  629.         databaseId = ((Delete) annotation).databaseId();
  630.         sqlCommandType = SqlCommandType.DELETE;
  631.       } else if (annotation instanceof SelectProvider) {
  632.         databaseId = ((SelectProvider) annotation).databaseId();
  633.         sqlCommandType = SqlCommandType.SELECT;
  634.         dirtySelect = ((SelectProvider) annotation).affectData();
  635.       } else if (annotation instanceof UpdateProvider) {
  636.         databaseId = ((UpdateProvider) annotation).databaseId();
  637.         sqlCommandType = SqlCommandType.UPDATE;
  638.       } else if (annotation instanceof InsertProvider) {
  639.         databaseId = ((InsertProvider) annotation).databaseId();
  640.         sqlCommandType = SqlCommandType.INSERT;
  641.       } else if (annotation instanceof DeleteProvider) {
  642.         databaseId = ((DeleteProvider) annotation).databaseId();
  643.         sqlCommandType = SqlCommandType.DELETE;
  644.       } else {
  645.         sqlCommandType = SqlCommandType.UNKNOWN;
  646.         if (annotation instanceof Options) {
  647.           databaseId = ((Options) annotation).databaseId();
  648.         } else if (annotation instanceof SelectKey) {
  649.           databaseId = ((SelectKey) annotation).databaseId();
  650.         } else {
  651.           databaseId = "";
  652.         }
  653.       }
  654.     }

  655.     Annotation getAnnotation() {
  656.       return annotation;
  657.     }

  658.     SqlCommandType getSqlCommandType() {
  659.       return sqlCommandType;
  660.     }

  661.     String getDatabaseId() {
  662.       return databaseId;
  663.     }

  664.     boolean isDirtySelect() {
  665.       return dirtySelect;
  666.     }
  667.   }
  668. }