/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor.keygen;

import java.sql.Statement;
import java.util.List;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.RowBounds;

/**
 * @author Clinton Begin
 * @author Jeff Butler
 */
public class SelectKeyGenerator implements KeyGenerator {

  public static final String SELECT_KEY_SUFFIX = "!selectKey";
  private final boolean executeBefore;
  private final MappedStatement keyStatement;

  public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
    this.executeBefore = executeBefore;
    this.keyStatement = keyStatement;
  }

  @Override
  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    if (executeBefore) {
      processGeneratedKeys(executor, ms, parameter);
    }
  }

  @Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    if (!executeBefore) {
      processGeneratedKeys(executor, ms, parameter);
    }
  }

  private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
    try {
      if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
        String[] keyProperties = keyStatement.getKeyProperties();
        final Configuration configuration = ms.getConfiguration();
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        // Do not close keyExecutor.
        // The transaction will be closed by parent executor.
        Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
        List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
        if (values.size() == 0) {
            throw new ExecutorException("SelectKey returned no data.");
        } else if (values.size() > 1) {
            throw new ExecutorException("SelectKey returned more than one value.");
        } else {
            MetaObject metaResult = configuration.newMetaObject(values.get(0));
            if (keyProperties.length == 1) {
                if (metaResult.hasGetter(keyProperties[0])) {
                    setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
                } else {
                    // no getter for the property - maybe just a single value object
                    // so try that
                    setValue(metaParam, keyProperties[0], values.get(0));
                }
            } else {
                handleMultipleProperties(keyProperties, metaParam, metaResult);
            }
        }
      }
    } catch (ExecutorException e) {
      throw e;
    } catch (Exception e) {
      throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
    }
  }

  private void handleMultipleProperties(String[] keyProperties,
      MetaObject metaParam, MetaObject metaResult) {
    String[] keyColumns = keyStatement.getKeyColumns();

    if (keyColumns == null || keyColumns.length == 0) {
      // no key columns specified, just use the property names
      for (String keyProperty : keyProperties) {
        setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
      }
    } else {
      if (keyColumns.length != keyProperties.length) {
        throw new ExecutorException("If SelectKey has key columns, the number must match the number of key properties.");
      }
      for (int i = 0; i < keyProperties.length; i++) {
        setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
      }
    }
  }

  private void setValue(MetaObject metaParam, String property, Object value) {
    if (metaParam.hasSetter(property)) {
      metaParam.setValue(property, value);
    } else {
      throw new ExecutorException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
    }
  }
}

@@ -2686,34 +2686,32 @@
  0) %7B%0A          
-  
 throw new Execut
@@ -2790,26 +2790,24 @@
 ze() %3E 1) %7B%0A
-  
           th
@@ -2899,26 +2899,24 @@
 %7B%0A          
-  
 MetaObject m
@@ -2977,26 +2977,24 @@
 ;%0A          
- 
- 
 if (keyPrope
@@ -3010,28 +3010,24 @@
 gth == 1) %7B%0A
-    
             
@@ -3078,38 +3078,32 @@
 %7B%0A              
- 
-     
 setValue(metaPar
@@ -3168,36 +3168,32 @@
 ));%0A            
-    
 %7D else %7B%0A       
@@ -3199,22 +3199,16 @@
         
-    
-  
 // no ge
@@ -3278,22 +3278,16 @@
         
-      
 // so tr
@@ -3299,38 +3299,32 @@
 t%0A              
-     
- 
 setValue(metaPar
@@ -3369,32 +3369,26 @@
             
-    %7D%0A  
+%7D%0A
           %7D 
@@ -3406,20 +3406,16 @@
         
-    
 handleMu
@@ -3470,18 +3470,16 @@
 esult);%0A
-  
         

indent size to 2 from 4.