본문으로 바로가기

 

package com.company.SuperTypeToken;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.junit.Test;
import org.junit.jupiter.api.DisplayName;
import org.springframework.core.ResolvableType;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class SuperTypeTokenTest {

    @EqualsAndHashCode
    static class TypeSafeMap {
        private final Map<Type, Object> map = new HashMap<>();

        <T> void put(TypeReference<T> tr, T value) {
            map.put(tr.type, value);
        }

        // explicit type casting is perfectly safe.
        @SuppressWarnings("unchecked")
        <T> T get(TypeReference<T> tr) {
            if (tr.type instanceof Class<?>) return ((Class<T>) tr.type).cast(map.get(tr.type));
            else return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr.type));
        }
        
        public int size() { return map.size(); }
        public void clear() { map.clear(); }
        public boolean isEmpty() { return map.isEmpty(); }
        public boolean containsKey(Object key) { return map.containsKey(key); }
        public <T> T remove(T key) { return (T) map.remove(key); }
        public boolean containsValue(Object value) { return map.containsValue(value); }
        public Set<Type> keySet() { return map.keySet(); }

    }

    static class TypeSafeList implements Iterable<TypeSafeList.Types<?>> {

        @Override
        public Iterator<Types<?>> iterator() {
            return list.iterator();
        }

        @ToString @EqualsAndHashCode
        static class Types<T> {
            private Type type;
            private T data;

            public Type getType() { return type; }
            public T getData() { return data; }
            boolean isSameTypeWith(Type rtype) { return rtype.equals(getType()); }
        }

        private final List<Types<?>> list = new ArrayList<>();

        <T> void add(TypeReference<T> tr, T data) {
            Types<T> types = new Types<>();
            types.type = tr.type;
            types.data = data;
            list.add(types);
        }

        /**
         * @param <T> tr.type
         * @param tr 타입안정성을 위해 사용된 슈퍼 타입토큰
         * @return  tr.type 에 해당하는 모든 값을 list 에서 찾아내어 리스트로 모아서 반환
         */
        // explicit type casting is perfectly safe.
        @SuppressWarnings("unchecked")
        <T> List<T> getValues(TypeReference<T> tr) {
            if (tr.type instanceof Class<?>)
                return (List<T>) list.stream().filter(e -> e.type.equals(tr.type)).map(e -> e.data).collect(toList());
            else
                return (List<T>) ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(list.stream().filter(e -> e.type.equals(tr.type)).map(e -> e.data).collect(toList()));
        }

        Type getType(int index) {
            return list.get(index).getType();
        }

        // only one value contains in list. list is immutable.
        List<?> getValue(int index) {
            return List.of(list.get(index).getData());
        }

        public int size() {
            return list.size();
        }

        public List<Types<?>> getList() {
            return list;
        }
    }

    // T does not used in this class directly, but indirectly (getGenericSuperclass()) uses.
    abstract static class TypeReference<T> {
        final Type type;

        public TypeReference() {
            Type sType = this.getClass().getGenericSuperclass();
            if (sType instanceof ParameterizedType) {
                this.type = ((ParameterizedType) sType).getActualTypeArguments()[0];
            } else {
                throw new RuntimeException();
            }
        }

    }

    enum Grade { VIP, VVIP }
    @ToString @Getter @EqualsAndHashCode
    static class User {
        private final String name;
        private final Integer age;
        private Grade grade;

        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
            this.grade = Grade.VIP;
        }
        public User setGrade(Grade grade) { this.grade = grade; return this; }
    }

    @Test
    @DisplayName("타입 안정성을 지닌 다수의 데이터타입을 가지는 해시맵이 구현되었는지 검증 테스트")
    public void typeSafeMapTest() {

        // given
        TypeSafeMap m = new TypeSafeMap();

        // when
        m.put(new TypeReference<String>(){}, "String");
        m.put(new TypeReference<Integer>(){}, 1);
        m.put(new TypeReference<List<Integer>>(){}, List.of(1,2,3));
        m.put(new TypeReference<List<String>>(){}, List.of("a", "b", "c"));

        // then
        assertThat(m.get(new TypeReference<String>(){})).isEqualTo("String");
        assertThat(m.get(new TypeReference<Integer>(){})).isEqualTo(1);
        assertThat(m.get(new TypeReference<List<Integer>>(){})).isEqualTo(List.of(1,2,3));
        assertThat(m.get(new TypeReference<List<String>>(){})).isEqualTo(List.of("a","b","c"));
    }

    @Test
    @DisplayName("스프링이 지원하는 ResolvableType 을 이용한 다양한 타입에 대한 접근이 가능한지에 대하여 검증")
    public void testResolvableType() {
        ResolvableType rt = ResolvableType.forInstance(new TypeReference<List<Map<Set<Integer>, List<String>>>>() {});

        assertThat(rt.getSuperType().getGenerics().length).isEqualTo(1);
        assertThat(rt.getSuperType().getNested(1).toString()).isEqualTo("com.company.SuperTypeToken.SuperTypeTokenTest$TypeReference<java.util.List<java.util.Map<java.util.Set<java.lang.Integer>, " +
                                                                            "java.util.List<java.lang.String>>>>");
        assertThat(rt.getSuperType().getNested(2).toString()).isEqualTo("java.util.List<java.util.Map<java.util.Set<java.lang.Integer>, java.util.List<java.lang.String>>>");
        assertThat(rt.getSuperType().getNested(3).toString()).isEqualTo("java.util.Map<java.util.Set<java.lang.Integer>, java.util.List<java.lang.String>>");
        assertThat(rt.getSuperType().getNested(4).toString()).isEqualTo("java.util.List<java.lang.String>");
        assertThat(rt.getSuperType().getNested(5).toString()).isEqualTo("java.lang.String");
        assertThat(rt.getSuperType().getNested(6).toString()).isEqualTo("?");
        assertThat(rt.getSuperType().getNested(7).toString()).isEqualTo("?");
        assertThat(rt.getSuperType().getNested(8).toString()).isEqualTo("?");
        assertThat(rt.getSuperType().getNested(9).toString()).isEqualTo("?");
    }

    @Test
    @DisplayName("typeSafeMap validate value")
    public void typeSafeMapTest2() {
        // given
        TypeSafeMap typeSafeMap = new TypeSafeMap() {};

        // when
        typeSafeMap.put(new TypeReference<>(){},List.of("One"));
        typeSafeMap.put(new TypeReference<>(){},12.0);
        typeSafeMap.put(new TypeReference<>(){},List.of(Set.of(1, 2, 3)));

        // then
        assertThat(typeSafeMap.get(new TypeReference<List<String>>() {})).isEqualTo(List.of("One"));
        assertThat(typeSafeMap.get(new TypeReference<Double>() {})).isEqualTo(12.0);
        assertThat(typeSafeMap.get(new TypeReference<List<Set<Integer>>>() {})).isEqualTo(List.of(Set.of(1, 2, 3)));

        // after check
        // typeSafeMap.map.forEach((k, v) -> System.out.println(k + " : " + v));
    }


    @Test
    @DisplayName("TypeSafeList 에 들어간 타입 중 특정 타입 값들을 모두 리스트로 꺼내어 타입과 값을 검증함, getValues(TypeReference tr) 사용")
    public void typeSafeListTest() {

        // given
        TypeSafeList typeSafeList = new TypeSafeList();

        // when
        typeSafeList.add(new TypeReference<Integer>() {}, 4934);
        typeSafeList.add(new TypeReference<List<Double>>() {}, List.of(13.0, 50.2));
        typeSafeList.add(new TypeReference<List<Map<Integer, String>>>() {}, List.of(Map.of(42, "the answer to the 'ultimate question of life, the universe, and everything.'")));
        typeSafeList.add(new TypeReference<User>(){}, new User("charlie", 17));
        typeSafeList.add(new TypeReference<User>(){}, new User("mark", 31));

        // then
        assertThat(typeSafeList.getValues(new TypeReference<Integer>(){})).isEqualTo(List.of(4934));
        typeSafeList.add(new TypeReference<Integer>() {}, 2391);
        assertThat(typeSafeList.getValues(new TypeReference<Integer>(){})).isEqualTo(List.of(4934, 2391));
        assertThat(typeSafeList.getValues(new TypeReference<String>(){})).isEqualTo(Collections.emptyList());
        assertThat(typeSafeList.getValues(new TypeReference<List<Double>>(){})).isEqualTo(List.of(List.of(13.0, 50.2)));
        typeSafeList.add(new TypeReference<List<Double>>(){}, List.of(543.23, 705.06));
        assertThat(typeSafeList.getValues(new TypeReference<List<Double>>(){})).isEqualTo(List.of(List.of(13.0, 50.2), List.of(543.23, 705.06)));
        assertThat(typeSafeList.getValues(new TypeReference<User>(){}).get(0).getName()).isEqualTo("charlie");
        assertThat(typeSafeList.getValues(new TypeReference<List<Map<Integer, String>>>(){}).get(0).get(0).get(42)).isEqualTo("the answer to the 'ultimate question of life, the universe, and everything.'");
        assertThat(typeSafeList.getValues(new TypeReference<User>(){}).get(0).setGrade(Grade.VVIP).getGrade()).isEqualTo(Grade.VVIP);
        /**
         *  after check
         *  print all list elements in typeSafeList
         *  typeSafeList.list.stream().forEach((v) -> System.out.println(v + " "));
         */
    }

    @Test
    @DisplayName("TypeSafeList에 담긴 타입들을 검증함, getType(int index) 사용")
    public void typeSafeListTest2() {
        // given
        TypeSafeList typeSafeList = new TypeSafeList();
        // given for compare using typeReference
        ResolvableType listDoubleRT = ResolvableType.forInstance(new TypeReference<List<Double>>() {});
        ResolvableType listMapRT = ResolvableType.forInstance(new TypeReference<List<Map<Integer, String>>>() {});

        // when
        typeSafeList.add(new TypeReference<Integer>() {}, 4934);
        typeSafeList.add(new TypeReference<List<Double>>() {}, List.of(13.0, 50.2));
        typeSafeList.add(new TypeReference<List<Map<Integer, String>>>() {}, List.of(Map.of(42, "the answer to the 'ultimate question of life, the universe, and everything.'")));
        typeSafeList.add(new TypeReference<User>(){}, new User("charlie", 17));
        typeSafeList.add(new TypeReference<User>(){}, new User("mark", 34));

        assertThat(typeSafeList.getType(0)).isEqualTo(Integer.class);
        assertThat(typeSafeList.getType(1)).isEqualTo(listDoubleRT.getSuperType().getNested(2).getType());
        assertThat(typeSafeList.getType(2)).isEqualTo(listMapRT.getSuperType().getNested(2).getType());
        assertThat(typeSafeList.getType(3)).isEqualTo(User.class);
        assertThat(typeSafeList.getType(4)).isEqualTo(User.class);
    }

    @Test
    @DisplayName("TypeSafeList에 담긴 값 하나를 꺼내어 검증함, getValue(int value) 사용")
    public void typeSafeListTest3() {
        // given
        TypeSafeList tslist = new TypeSafeList();
        // given for compare using typeReference
        ResolvableType listDoubleRT = ResolvableType.forInstance(new TypeReference<List<Double>>() {});
        ResolvableType listMapRT = ResolvableType.forInstance(new TypeReference<List<Map<Integer, String>>>() {});

        // when
        tslist.add(new TypeReference<Integer>() {}, 4934);
        tslist.add(new TypeReference<List<Double>>() {}, List.of(13.0, 50.2));
        tslist.add(new TypeReference<List<Map<Integer, String>>>() {}, List.of(Map.of(42, "the answer to the 'ultimate question of life, the universe, and everything.'")));
        tslist.add(new TypeReference<User>(){}, new User("charlie", 17));
        tslist.add(new TypeReference<User>(){}, new User("mark", 34));

        assertThat(tslist.getValue(0).get(0)).isEqualTo(4934);
        assertThat(tslist.getValue(1).get(0)).isEqualTo(List.of(13.0, 50.2));
        assertThat(tslist.getValue(2).get(0)).isEqualTo(List.of(Map.of(42, "the answer to the 'ultimate question of life, the universe, and everything.'")));
        assertThat(tslist.getValue(3).get(0)).isEqualTo(new User("charlie", 17));
        assertThat(tslist.getValue(4).get(0)).isNotEqualTo(new User("charlie", 17));
    }

    @Test
    @DisplayName("TypeSafeList foreach type validation")
    public void typeSafeListTest4() {
        // given
        TypeSafeList typeSafeList = new TypeSafeList();

        // when
        typeSafeList.add(new TypeReference<Integer>() {}, 4934);
        typeSafeList.add(new TypeReference<List<Double>>() {}, List.of(13.0, 50.2));
        typeSafeList.add(new TypeReference<List<Map<Integer, String>>>() {}, List.of(Map.of(42, "the answer to the 'ultimate question of life, the universe, and everything.'")));
        typeSafeList.add(new TypeReference<User>(){}, new User("charlie", 17));
        typeSafeList.add(new TypeReference<User>(){}, new User("mark", 34));

        for(int i = 0; i < typeSafeList.size(); i++) {
            assertTrue(typeSafeList.getList().get(i).isSameTypeWith(typeSafeList.getType(i)));
        }

        for(var i : typeSafeList) {
            System.out.println(i.getData());
        }
    }
}

'Java > Java' 카테고리의 다른 글

은행 계좌 관리 프로그램 ver 1.1  (0) 2020.09.03
간단한 은행 계좌 관리 프로그램  (0) 2020.08.30
Java enum 활용법  (0) 2020.07.29