package com.zjy.mybatis.test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

import com.zjy.jdbc.util.DBUtil;

public class MyBatisReflectUtil {
    private static MyBatisConfig config;
    static {
        try {
            String configXml = "/mybatisConfig.xml";
            config = MyBatisParseUtil.parse(configXml);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Map<String, Object> convert(Object obj) throws Exception {
        List<Class> list = new ArrayList<Class>();
        list.add(Integer.class);
        list.add(Double.class);
        list.add(Float.class);
        list.add(String.class);
        list.add(int.class);
        list.add(float.class);
        list.add(double.class);
        Class clazz = obj.getClass();
        Map<String, Object> result = new HashMap<String, Object>();
        if (list.contains(obj.getClass())) {
            result.put("$1", obj);
        } else {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                String name = field.getName();
                field.setAccessible(true);
                Object value = field.get(obj);
                result.put(name, value);
            }
        }
        return result;
    }

    public static Object getMapper(String type) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        // String type="com.zjy.mybatis.model.Customer";
        String proxyPuffix = "$Proxy_";
        String proxyType = type + proxyPuffix;
        CtClass interfaceClazz = pool.get(type);
        CtClass implClazz = pool.makeClass(proxyType);
        implClazz.addInterface(interfaceClazz);
        Class mapperInterface = Class.forName(type);
        Method methods[] = mapperInterface.getDeclaredMethods();
        String mapperClazzName = mapperInterface.getName();
        for (Method method : methods) {
            String methodName = method.getName();
            if ( methodName.equals("query")) {
                continue;
            }
            CtMethod cm = interfaceClazz.getDeclaredMethod(method.getName());
            CtMethod methodImpl = CtNewMethod.copy(cm, implClazz, null);
            StringBuilder body =new StringBuilder("{");
            Map<String, MapperConfig> mapperConfigs = config.getMapperConfig();
            MapperConfig cfg = mapperConfigs.get(type);
            Map<String, Sql> map = cfg.getSqls();
            Sql sql=map.get(methodName);
            if(sql.getSqlType().equals("select")){
                String resultType=sql.getResultType();
                body.append("return ("+resultType+")");
            }
            body.append(MyBatisReflectUtil.class.getName()+ ".executeById(\"" + mapperClazzName + "\",\""+ methodName + "\",$1);");;
            body.append("}");
            System.out.println(body.toString());
            methodImpl.setBody(body.toString());
            implClazz.addMethod(methodImpl);
        }
        Class clazz = implClazz.toClass();
        return clazz.newInstance();
    }

    public static Object executeById(String nameSpace, String id, int param) {
        return executeById(nameSpace, id, Integer.valueOf(param));
    }

    public static Object executeById(String nameSpace, String id, Object param) {
        try {
            Map<String, MapperConfig> mapperConfigs = config.getMapperConfig();
            MapperConfig cfg = mapperConfigs.get(nameSpace);
            Map<String, Sql> map = cfg.getSqls();
            Sql sql=map.get(id);
            String executeSql = sql.getSql();
            Map<String, Object> properties = MyBatisReflectUtil.convert(param);
            if(sql.getSqlType().equals("select")){
                String resultMapName=sql.getResultMap();
                ResultMap resultMap=cfg.getResultMap().get(resultMapName);
                return executeQuery(sql, resultMap, properties);
            }else{
                return execute(executeSql, properties);
            }
           
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static Object executeQuery(Sql sql, ResultMap map,Map<String, Object> properties)
            throws Exception {
        String reg = "\\#\\{([^\\}]*)\\}";
        Pattern pattern = Pattern.compile(reg);
        String querySql=sql.getSql();
        Matcher matcher = pattern.matcher(querySql);
        int i = 0;
        List<ParameterPosition> list = new ArrayList<ParameterPosition>();
        while (matcher.find()) {
            String text = matcher.group(1);
            ParameterPosition position = new ParameterPosition();
            position.setIndex(i);
            position.setName(text);
            i = i + 1;
            list.add(position);
        }
        querySql = querySql.replaceAll("\\#\\{([^\\}]*)\\}", "?");
        Object paramterValues[] = new Object[list.size()];
        for (int t = 0; t < list.size(); t++) {
            ParameterPosition position = list.get(t);
            int index = position.getIndex();
            String name = position.getName();
            if (properties.containsKey("$1")) {
                paramterValues[index] = properties.get("$1");
            } else {
                paramterValues[index] = properties.get(name);
            }
        }
        List<Map<String,Object>> resultSet=DBUtil.preExecuteQuery(querySql, paramterValues);
        if(resultSet.size()==1){
            String resultType=sql.getResultType();
            Class resultClass=Class.forName(resultType);
            Object result=resultClass.newInstance();
            Map<String,Object> values=resultSet.get(0);
            for(Entry<String,String> entry:map.getResultMap().entrySet()){
                String property=entry.getKey();
                String column=entry.getValue();
                Field field=resultClass.getDeclaredField(property);
                field.setAccessible(true);
                field.set(result, values.get(column));
            }
            return result;
        }
        throw new IllegalArgumentException("返回多个值,暂时不支持!!");
       
    }

    public static boolean execute(String sql, Map<String, Object> properties)
            throws Exception {
        String reg = "\\#\\{([^\\}]*)\\}";
        Pattern pattern = Pattern.compile(reg);
        Matcher matcher = pattern.matcher(sql);
        int i = 0;
        List<ParameterPosition> list = new ArrayList<ParameterPosition>();
        while (matcher.find()) {
            String text = matcher.group(1);
            ParameterPosition position = new ParameterPosition();
            position.setIndex(i);
            position.setName(text);
            i = i + 1;
            list.add(position);
        }
        sql = sql.replaceAll("\\#\\{([^\\}]*)\\}", "?");
        Object paramterValues[] = new Object[list.size()];
        for (int t = 0; t < list.size(); t++) {
            ParameterPosition position = list.get(t);
            int index = position.getIndex();
            String name = position.getName();
            if (properties.containsKey("$1")) {
                paramterValues[index] = properties.get("$1");
            } else {
                paramterValues[index] = properties.get(name);
            }
        }
        return DBUtil.preExecute(sql, paramterValues);
    }
}

package com.zjy.mybatis.test;

import java.util.Map;

public class MapperConfig {
    private String nameSpace;
    private Map<String, Sql> upddateSqls;
    private Map<String,ResultMap> resultMap;
    public String getNameSpace() {
        return nameSpace;
    }
    public void setNameSpace(String nameSpace) {
        this.nameSpace = nameSpace;
    }
    public Map<String, Sql> getSqls() {
        return upddateSqls;
    }
    public void setSqls(Map<String, Sql> updateSqls) {
        this.upddateSqls = updateSqls;
    }
    public Map<String, ResultMap> getResultMap() {
        return resultMap;
    }
    public void setResultMap(Map<String, ResultMap> resultMap) {
        this.resultMap = resultMap;
    }
}

package com.zjy.mybatis.test;

import java.util.Map;

public class MyBatisConfig {
    private Map<String, String> dataSourceConfig;
    private Map<String, MapperConfig> mapperConfig;

    public Map<String, String> getDataSourceConfig() {
        return dataSourceConfig;
    }

    public void setDataSourceConfig(Map<String, String> dataSourceConfig) {
        this.dataSourceConfig = dataSourceConfig;
    }

    public Map<String, MapperConfig> getMapperConfig() {
        return mapperConfig;
    }

    public void setMapperConfig(Map<String, MapperConfig> mapperConfig) {
        this.mapperConfig = mapperConfig;
    }
}

package com.zjy.mybatis.test;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class MyBatisParseUtil {
    public static Map<String, String> getDataSourceConfig(String configXml)
            throws Exception {
        // String configXml = "/mybatisConfig.xml";
        SAXReader reader = new SAXReader();
        if (!configXml.startsWith("/")) {
            configXml = "/" + configXml;
        }
        Document document = reader.read(MyBatisMaker.class
                .getResource(configXml));
        List<Element> list = document
                .selectNodes("/configuration/environments/environment/dataSource/property");
        Map<String, String> config = new HashMap<String, String>();
        for (Element node : list) {
            String name = node.attributeValue("name");
            String value = node.attributeValue("value");
            config.put(name, value);
        }
        return config;
    }

    public static List<String> getMapperResources(String configXml)
            throws Exception {
        SAXReader reader = new SAXReader();
        Document document = reader.read(MyBatisMaker.class
                .getResource(configXml));
        List<Element> list = document
                .selectNodes("/configuration/mappers/mapper");
        List<String> result = new ArrayList<String>();
        for (Element node : list) {
            String resource = node.attributeValue("resource");
            result.add(resource);
        }
        return result;
    }

    public static MapperConfig parseMapper(String configXml) throws Exception {
        SAXReader reader = new SAXReader();
        if (!configXml.startsWith("/")) {
            configXml = "/" + configXml;
        }
        InputStream inputStream = System.class.getResourceAsStream(configXml);
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();
        String mapperType = root.attributeValue("namespace");
        List<String> expresses = new ArrayList<String>();
        expresses.add("/mapper/insert");
        expresses.add("/mapper/update");
        expresses.add("/mapper/delete");
        expresses.add("/mapper/select");
        Map<String, Sql> sqls = new HashMap<String, Sql>();
        for (String str : expresses) {
            List<Element> elements = root.selectNodes(str);
            for (Element ele : elements) {
                String id = ele.attributeValue("id");
                String parameterType = ele.attributeValue("parameterType");
                String resultType = ele.attributeValue("resultType");
                String resultMap = ele.attributeValue("resultMap");
                String sqlText = ele.getText();
                Sql sql = new Sql();
                sql.setId(id);
                sql.setParameterType(parameterType);
                sql.setResultMap(resultMap);
                sql.setResultType(resultType);
                sql.setSql(sqlText);
                String tagName=ele.getName();
                sql.setSqlType(tagName);
                sqls.put(sql.getId(), sql);
            }
        }
        Map<String,ResultMap> resultMapList=parseResultMap(configXml);
        MapperConfig result = new MapperConfig();
        result.setNameSpace(mapperType);
        result.setSqls(sqls);
        result.setResultMap(resultMapList);
        return result;
    }
    public static Map<String,ResultMap> parseResultMap(String configXml)throws Exception{
        InputStream inputStream = System.class.getResourceAsStream(configXml);
        SAXReader reader=new SAXReader();
        Document document = reader.read(inputStream);
        List<String> mapExpress=new ArrayList<String>();
        mapExpress.add("id");
        mapExpress.add("result");
        List<Element> resultMapList=document.selectNodes("mapper/resultMap");
        Map<String,ResultMap>  result=new HashMap<String, ResultMap>();
        for(Element resultEle:resultMapList){
            String id=resultEle.attributeValue("id");
            String type=resultEle.attributeValue("type");
            ResultMap resultMap=new ResultMap();
            resultMap.setId(id);
            resultMap.setType(type);
            Map<String,String>  propertyMap=new HashMap<String, String>();
            for(String express:mapExpress){
                List<Element> elements=resultEle.selectNodes(express);
                for(Element ele:elements){
                    String property=ele.attributeValue("property");
                    String column=ele.attributeValue("column");
                    propertyMap.put(property, column);
                }
                resultMap.setPropertyMap(propertyMap);
            }
            result.put(resultMap.getId(), resultMap);
        }
        return result;
    }

    public static MyBatisConfig parse(String configXml) throws Exception {
        // String configXml = "/mybatisConfig.xml";
        Map<String, String> dataSourceConfig = getDataSourceConfig(configXml);
        List<String> resource = getMapperResources(configXml);
        Map<String, MapperConfig> mappers = new HashMap<String, MapperConfig>();
        for (String str : resource) {
            MapperConfig config = parseMapper(str);
            mappers.put(config.getNameSpace(), config);
        }
        MyBatisConfig myBatis = new MyBatisConfig();
        myBatis.setDataSourceConfig(dataSourceConfig);
        myBatis.setMapperConfig(mappers);
        return myBatis;
    }
}