001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.test;
017
018import com.jfinal.kit.LogKit;
019import io.jboot.aop.InterceptorCache;
020import io.jboot.aop.cglib.JbootCglibCallback;
021import io.jboot.aop.javassist.JbootJavassistHandler;
022import io.jboot.service.JbootServiceBase;
023import io.jboot.utils.ClassUtil;
024import javassist.util.proxy.MethodHandler;
025import net.sf.cglib.proxy.MethodProxy;
026
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033
034class MockMethodInterceptor extends JbootCglibCallback implements MethodHandler {
035
036    private static final Map<InterceptorCache.MethodKey, MockMethodInfo> METHOD_INFO_CACHE = new HashMap<>();
037
038    public static void addMethodInfo(MockMethodInfo value) {
039        InterceptorCache.MethodKey methodKey = InterceptorCache.getMethodKey(value.getTargetClass(), value.getTargetMethod());
040        METHOD_INFO_CACHE.put(methodKey, value);
041    }
042
043    public static void addMockClass(Class<?> mockClass) {
044        Class<?>[] interfaces = mockClass.getInterfaces();
045        List<MockMethodInfo> mockMethodInfos = new ArrayList<>();
046        for (Class<?> inter : interfaces) {
047            for (MockMethodInfo mockMethodInfo : METHOD_INFO_CACHE.values()) {
048                //相同的代理对象
049                if (mockMethodInfo.getTargetClass() == inter) {
050                    mockMethodInfos.add(new MockMethodInfo(mockMethodInfo, mockClass));
051                }
052            }
053        }
054        mockMethodInfos.forEach(MockMethodInterceptor::addMethodInfo);
055    }
056
057
058    private boolean autoMockInterface;
059
060    public MockMethodInterceptor(boolean autoMockInterface) {
061        this.autoMockInterface = autoMockInterface;
062    }
063
064    @Override
065    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
066
067        Class<?> targetClass = ClassUtil.getUsefulClass(target.getClass());
068
069        //对于接口而且没有实现类的情况,target 是一个 Object 类
070        if (targetClass == Object.class && method.getDeclaringClass() != Object.class) {
071            targetClass = method.getDeclaringClass();
072        }
073
074        InterceptorCache.MethodKey methodKey = InterceptorCache.getMethodKey(targetClass, method);
075
076        if (METHOD_INFO_CACHE.containsKey(methodKey)) {
077            MockMethodInfo methodInfo = METHOD_INFO_CACHE.get(methodKey);
078            return methodInfo.invokeMock(target, args);
079        }
080
081        if (autoMockInterface && Modifier.isInterface(targetClass.getModifiers())) {
082            if (!("toString".equals(method.getName()) && args.length == 0)) {
083                LogKit.warn("Return null for Mock Method: \"" + ClassUtil.buildMethodString(method) + "\", " +
084                        "Because the class \"" + targetClass.getName() + "\" is an interface and has no any implementation classes.");
085            }
086            return null;
087        }
088
089        try {
090            return super.intercept(target, method, args, methodProxy);
091        } catch (Exception ex) {
092            if ("initDao".equals(method.getName()) && JbootServiceBase.class == method.getDeclaringClass()) {
093                return null;
094            } else {
095                throw ex;
096            }
097        }
098
099    }
100
101
102    private static final JbootJavassistHandler orginalHandler = new JbootJavassistHandler();
103
104    @Override
105    public Object invoke(Object target, Method thisMethod, Method method, Object[] args) throws Throwable {
106        Class<?> targetClass = ClassUtil.getUsefulClass(target.getClass());
107
108        //对于接口而且没有实现类的情况,target 是一个 Object 类
109        if (targetClass == Object.class && method.getDeclaringClass() != Object.class) {
110            targetClass = method.getDeclaringClass();
111        }
112
113        InterceptorCache.MethodKey methodKey = InterceptorCache.getMethodKey(targetClass, method);
114
115        if (METHOD_INFO_CACHE.containsKey(methodKey)) {
116            MockMethodInfo methodInfo = METHOD_INFO_CACHE.get(methodKey);
117            return methodInfo.invokeMock(target, args);
118        }
119
120        if (autoMockInterface && Modifier.isInterface(targetClass.getModifiers())) {
121            if (!("toString".equals(method.getName()) && args.length == 0)) {
122                LogKit.warn("Return null for Mock Method: \"" + ClassUtil.buildMethodString(method) + "\", " +
123                        "Because the class \"" + targetClass.getName() + "\" is an interface and has no any implementation classes.");
124            }
125            return null;
126        }
127
128        try {
129            return orginalHandler.invoke(target, thisMethod, method, args);
130        } catch (Exception ex) {
131            if ("initDao".equals(method.getName()) && JbootServiceBase.class == method.getDeclaringClass()) {
132                return null;
133            } else {
134                throw ex;
135            }
136        }
137    }
138}