/*
 * Copyright 1999-2015 dangdang.com.
 * <p>
 * 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.
 * </p>
 */

package io.shardingjdbc.spring.boot.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;

import io.shardingjdbc.core.exception.ShardingJdbcException;

public class PropertyUtil {

    private static final int SPRING_BOOT_VERSION;

    static {
        int springBootVersion = 1;
        try {
            Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");
        } catch (ClassNotFoundException e) {
            springBootVersion = 2;
        }
        SPRING_BOOT_VERSION = springBootVersion;
    }

    /**
     * Spring Boot 1.x is compatible with Spring Boot 2.x by Using Java Reflect.
     * @param environment not null
     * @param prefix not null
     * @param targetClass not null
     * @param <T> not null
     * @return not null
     */
    @SuppressWarnings("unchecked")
    public static <T> T handle(final Environment environment, final String prefix, final Class<T> targetClass) {
        switch (SPRING_BOOT_VERSION) {
            case 1:
                return (T) v1(environment, prefix, targetClass);
            default:
                return (T) v2(environment, prefix, targetClass);
        }
    }

    /**
     * Spring Boot 1.x.
     * @code {
     *     RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment);
     *     return resolver.getSubProperties(prefix);
     *     }
     */
    private static Object v1(final Environment environment, final String prefix, final Class<?> targetClass) {
        try {
            Class<?> resolverClass = Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");
            Constructor<?> resolverConstructor = resolverClass.getDeclaredConstructor(PropertyResolver.class);
            Method getSubPropertiesMethod = resolverClass.getDeclaredMethod("getSubProperties", String.class);
            Object resolverObject = resolverConstructor.newInstance(environment);
            String prefixParam = prefix.endsWith(".") ? prefix : prefix + ".";
            return getSubPropertiesMethod.invoke(resolverObject, prefixParam);
        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new ShardingJdbcException(e.getMessage(), e);
        }
    }

    /**
     * Spring Boot 2.x.
     * @code {
     *     Binder binder = Binder.get(environment);
     *     return binder.bind(prefix, targetClass).get();
     *     }
     */
    private static Object v2(final Environment environment, final String prefix, final Class<?> targetClass) {
        try {
            Class<?> binderClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");
            Method getMethod = binderClass.getDeclaredMethod("get", Environment.class);
            Method bindMethod = binderClass.getDeclaredMethod("bind", String.class, Class.class);
            Object binderObject = getMethod.invoke(null, environment);
            String prefixParam = prefix.endsWith(".") ? prefix.substring(0, prefix.length() - 1) : prefix;
            Object bindResultObject = bindMethod.invoke(binderObject, prefixParam, targetClass);
            Method resultGetMethod = bindResultObject.getClass().getDeclaredMethod("get");
            return resultGetMethod.invoke(bindResultObject);
        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new ShardingJdbcException(e.getMessage(), e);
        }
    }
}

@@ -1023,64 +1023,8 @@
 tic 
-final int SPRING_BOOT_VERSION;%0D%0A%0D%0A    static %7B%0D%0A        
 int 
@@ -1046,16 +1046,32 @@
 on = 1;%0D
+%0A%0D%0A    static %7B%0D
 %0A       
@@ -1260,58 +1260,8 @@
  %7D%0D%0A
-        SPRING_BOOT_VERSION = springBootVersion;%0D%0A
     
@@ -1386,83 +1386,150 @@
 ent 
-not null%0D%0A     * @param prefix not null%0D%0A     * @param targetClass not null
+: the environment context%0D%0A     * @param prefix : the prefix part of property key%0D%0A     * @param targetClass : the target class type of result
 %0D%0A  
@@ -1544,24 +1544,45 @@
 ram %3CT%3E 
-not null
+: refer to @param targetClass
 %0D%0A     *
@@ -1594,16 +1594,9 @@
 urn 
-not null
+T
 %0D%0A  
@@ -1772,27 +1772,25 @@
 ch (
-SPRING_BOOT_VERSION
+springBootVersion
 ) %7B%0D
@@ -1989,224 +1989,8 @@
 %0D%0A%0D%0A
-    /**%0D%0A     * Spring Boot 1.x.%0D%0A     * @code %7B%0D%0A     *     RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment);%0D%0A     *     return resolver.getSubProperties(prefix);%0D%0A     *     %7D%0D%0A     */%0D%0A
     
@@ -2698,32 +2698,38 @@
        %7D catch (
+final 
 ClassNotFoundExc
@@ -2877,32 +2877,33 @@
 argetException e
+x
 ) %7B%0D%0A           
@@ -2928,32 +2928,33 @@
 gJdbcException(e
+x
 .getMessage(), e
@@ -2957,131 +2957,9 @@
 ), e
-);%0D%0A        %7D%0D%0A    %7D%0D%0A%0D%0A    /**%0D%0A     * Spring Boot 2.x.%0D%0A     * @code %7B%0D%0A     *     Binder binder = Binder.get(environment
+x
 );%0D%0A
@@ -2967,83 +2967,21 @@
     
-*
    
-  return binder.bind(prefix, targetClass).get();%0D%0A     *     %7D%0D%0A     */
+%7D%0D%0A    %7D%0D%0A
 %0D%0A  
@@ -3844,32 +3844,38 @@
        %7D catch (
+final 
 ClassNotFoundExc
@@ -3998,32 +3998,33 @@
 argetException e
+x
 ) %7B%0D%0A           
@@ -4057,16 +4057,17 @@
 eption(e
+x
 .getMess
@@ -4074,16 +4074,17 @@
 age(), e
+x
 );%0D%0A    

fix the checkstyle errors