/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * 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 com.alibaba.nacos.common.utils;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * ReflectUtils unit test.
 *
 * @author karsonto
 * @date 2022/08/19
 */
class ReflectUtilsTest {
    
    List<String> listStr;
    
    @BeforeEach
    void before() {
        listStr = new ArrayList<>(2);
    }
    
    /*
     * Pay attention to JDK 17 changes. Need to add vm options below:
     * --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED
     * --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED
     */
    @Test
    void testGetFieldValue() {
        Object elementData = ReflectUtils.getFieldValue(listStr, "elementData");
        assertTrue(elementData instanceof Object[]);
        assertEquals(2, ((Object[]) elementData).length);
    }
    
    @Test
    void testGetFieldValueWithoutField() {
        assertThrows(RuntimeException.class, () -> {
            ReflectUtils.getFieldValue(listStr, "elementDataxx");
        });
    }
    
    @Test
    void testGetFieldValueWithDefault() {
        Object elementData = ReflectUtils.getFieldValue(listStr, "elementDataxx", 3);
        assertEquals(3, elementData);
        elementData = ReflectUtils.getFieldValue(listStr, "elementData", 3);
        assertTrue(elementData instanceof Object[]);
        assertEquals(2, ((Object[]) elementData).length);
    }
    
    @Test
    void testGetField() throws NoSuchFieldException {
        Field field = listStr.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        Object elementData = ReflectUtils.getField(field, listStr);
        assertTrue(elementData instanceof Object[]);
    }
    
    @Test
    void testGetFieldWithoutAccess() throws NoSuchFieldException {
        assertThrows(IllegalStateException.class, () -> {
            Field field = listStr.getClass().getDeclaredField("elementData");
            ReflectUtils.getField(field, listStr);
        });
    }
    
    @Test
    void testInvokeMethod() throws Exception {
        Method method = listStr.getClass().getDeclaredMethod("grow", int.class);
        method.setAccessible(true);
        ReflectUtils.invokeMethod(method, listStr, 4);
        Object elementData = ReflectUtils.getFieldValue(listStr, "elementData");
        assertEquals(4, ((Object[]) elementData).length);
    }
    
    @Test
    void testInvokeMethodWithoutAccess() throws Exception {
        assertThrows(IllegalStateException.class, () -> {
            Method method = listStr.getClass().getDeclaredMethod("grow", int.class);
            ReflectUtils.invokeMethod(method, listStr, 4);
        });
    }
    
    @Test
    void testHandleReflectionException() {
        assertThrows(UndeclaredThrowableException.class, () -> {
            try {
                NoSuchMethodException exception = new NoSuchMethodException("test");
                ReflectUtils.handleReflectionException(exception);
            } catch (Exception e) {
                assertEquals("Method not found: test", e.getMessage());
            }
            try {
                IllegalAccessException exception = new IllegalAccessException("test");
                ReflectUtils.handleReflectionException(exception);
            } catch (Exception e) {
                assertEquals("Could not access method or field: test", e.getMessage());
            }
            RuntimeException exception = new RuntimeException("test");
            try {
                ReflectUtils.handleReflectionException(exception);
            } catch (Exception e) {
                assertEquals(exception, e);
            }
            try {
                InvocationTargetException invocationTargetException = new InvocationTargetException(exception);
                ReflectUtils.handleReflectionException(invocationTargetException);
            } catch (Exception e) {
                assertEquals(exception, e);
            }
            ReflectUtils.handleReflectionException(new IOException());
        });
    }
    
    @Test
    void testRethrowRuntimeException() {
        assertThrows(UndeclaredThrowableException.class, () -> {
            ClassFormatError error = new ClassFormatError("test");
            try {
                ReflectUtils.rethrowRuntimeException(error);
            } catch (Error e) {
                assertEquals(error, e);
            }
            ReflectUtils.rethrowRuntimeException(new IOException());
        });
    }
}
