diff --git a/README.md b/README.md index de4c8eab6..b2550a17f 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,11 @@ public class GenericData { private A a; private B b; private C c; + private A[] aArray; + private List bList; + private Map map; + private List[] cArray; + private Map, List>[] d; //getter setter省略... } diff --git a/pom.xml b/pom.xml index bdc5e208a..ab648f916 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,10 @@ TaoYu 332309254@qq.com + + KanYuXia + kanyuxia@outlook.com + diff --git a/src/main/java/com/github/jsonzou/jmockdata/JMockData.java b/src/main/java/com/github/jsonzou/jmockdata/JMockData.java index 6286f0339..b6e31453c 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/JMockData.java +++ b/src/main/java/com/github/jsonzou/jmockdata/JMockData.java @@ -1,7 +1,6 @@ package com.github.jsonzou.jmockdata; -import com.github.jsonzou.jmockdata.mocker.ClassMocker; -import com.github.jsonzou.jmockdata.mocker.GenericMocker; +import com.github.jsonzou.jmockdata.mocker.BaseMocker; /** * 模拟对象门面类 @@ -30,7 +29,7 @@ public static T mock(Class clazz) { * @return 模拟数据对象 */ public static T mock(Class clazz, MockConfig mockConfig) { - return new ClassMocker<>(clazz).mock(mockConfig); + return new BaseMocker(clazz).mock(mockConfig); } /** @@ -57,7 +56,7 @@ public static T mock(TypeReference typeReference) { * @return 模拟数据对象 */ public static T mock(TypeReference typeReference, MockConfig mockConfig) { - return (T) new GenericMocker(typeReference.getType()).mock(mockConfig); + return new BaseMocker(typeReference.getType()).mock(mockConfig.init(typeReference.getType())); } } diff --git a/src/main/java/com/github/jsonzou/jmockdata/MockConfig.java b/src/main/java/com/github/jsonzou/jmockdata/MockConfig.java index 24dac3351..c1ece0549 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/MockConfig.java +++ b/src/main/java/com/github/jsonzou/jmockdata/MockConfig.java @@ -1,8 +1,11 @@ package com.github.jsonzou.jmockdata; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * 模拟数据配置类 @@ -12,7 +15,11 @@ public class MockConfig { /** * Bean缓存 */ - private Map beanCache = new ConcurrentHashMap<>(16); + private Map beanCache = new HashMap<>(); + /** + * TypeVariable缓存 + */ + private Map typeVariableCache = new HashMap<>(); private byte[] byteRange = {0, 127}; private short[] shortRange = {0, 1000}; private int[] intRange = {0, 10000}; @@ -30,14 +37,35 @@ public class MockConfig { "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; - public void addCache(String name, Object object) { + public MockConfig() { + } + + public void addBeanCache(String name, Object object) { beanCache.put(name, object); } - public Object getCacheObject(String name) { + public Object getBeanCacheObject(String name) { return beanCache.get(name); } + public MockConfig init(Type type) { + if (type instanceof ParameterizedType) { + Class clazz = (Class) ((ParameterizedType) type).getRawType(); + Type[] types = ((ParameterizedType) type).getActualTypeArguments(); + TypeVariable[] typeVariables = clazz.getTypeParameters(); + if (typeVariables != null && typeVariables.length > 0) { + for (int index = 0; index < typeVariables.length; index++) { + typeVariableCache.put(typeVariables[index].getName(), types[index]); + } + } + } + return this; + } + + public Type getVariableType(String name) { + return typeVariableCache.get(name); + } + public MockConfig byteRange(byte min, byte max) { this.byteRange[0] = min; this.byteRange[1] = max; diff --git a/src/main/java/com/github/jsonzou/jmockdata/Mocker.java b/src/main/java/com/github/jsonzou/jmockdata/Mocker.java index 4ff9d70a2..a588df970 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/Mocker.java +++ b/src/main/java/com/github/jsonzou/jmockdata/Mocker.java @@ -5,6 +5,12 @@ */ public interface Mocker { + /** + * 模拟数据 + * + * @param mockConfig 模拟数据配置 + * @return 模拟数据对象 + */ T mock(MockConfig mockConfig); } diff --git a/src/main/java/com/github/jsonzou/jmockdata/mocker/ArrayMocker.java b/src/main/java/com/github/jsonzou/jmockdata/mocker/ArrayMocker.java index c7403a68e..2d9ac7d11 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/mocker/ArrayMocker.java +++ b/src/main/java/com/github/jsonzou/jmockdata/mocker/ArrayMocker.java @@ -4,31 +4,103 @@ import com.github.jsonzou.jmockdata.Mocker; import com.github.jsonzou.jmockdata.util.RandomUtils; import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; /** * 数组模拟器 */ @SuppressWarnings("unchecked") +// TODO 2018/1/20 代码还需要整理 public class ArrayMocker implements Mocker { - private Class clazz; + private Type type; - private Type componentType; - - public ArrayMocker(Class clazz, Type componentType) { - this.clazz = clazz; - this.componentType = componentType; + ArrayMocker(Type type) { + this.type = type; } @Override public Object mock(MockConfig mockConfig) { + // 创建有参数化的数组 + if (type instanceof GenericArrayType) { + return createGenericArray(mockConfig); + } + return array(mockConfig); + } + + private Object array(MockConfig mockConfig) { int size = RandomUtils.nextSize(mockConfig.getSizeRange()[0], mockConfig.getSizeRange()[1]); - Object result = Array.newInstance(clazz, size); + Class componentClass = ((Class) type).getComponentType(); + Object result = Array.newInstance(componentClass, size); for (int index = 0; index < size; index++) { - Array.set(result, index, new GenericMocker(componentType).mock(mockConfig)); + Object value = new BaseMocker(componentClass).mock(mockConfig); + Array.set(result, index, value); } return result; } + // 由于GenericArrayType无法获得Class,所以递归创建多维数组 + private Object createGenericArray(MockConfig mockConfig) { + GenericArrayType genericArrayType = (GenericArrayType) this.type; + // 递归获取该数组的维数,以及最后的Class类型 + Map> map = map(mockConfig, genericArrayType, 0); + Entry> entry = map.entrySet().iterator().next(); + Entry baseEntry = entry.getValue().entrySet().iterator().next(); + int[] dimensions = new int[entry.getKey()]; + for (int index = 0; index < dimensions.length; index++) { + dimensions[index] = RandomUtils.nextSize(mockConfig.getSizeRange()[0], mockConfig.getSizeRange()[1]); + } + // 创建多维数组每种维度的对象 + List list = new ArrayList<>(dimensions.length); + Class clazz = baseEntry.getKey(); + for (int index = dimensions.length - 1; index >= 0; index--) { + Object array = Array.newInstance(clazz, dimensions[index]); + list.add(array); + clazz = array.getClass(); + } + // 实例化多维数组 + Object baseResult = new BaseMocker(baseEntry.getKey(), baseEntry.getValue()).mock(mockConfig); + for (int i = 0; i < list.size(); i++) { + Object array = list.get(i); + for (int j = 0; j < dimensions[dimensions.length - i - 1]; j++) { + Array.set(array, j, baseResult); + } + baseResult = array; + } + return baseResult; + } + + private Map> map(MockConfig mockConfig, GenericArrayType genericArrayType, int dimension) { + Map> result = new HashMap<>(); + Type componentType = genericArrayType.getGenericComponentType(); + dimension++; + if (componentType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) componentType; + Map map = new HashMap<>(); + map.put((Class) parameterizedType.getRawType(), parameterizedType.getActualTypeArguments()); + result.put(dimension, map); + return result; + } + if (componentType instanceof GenericArrayType) { + return map(mockConfig, (GenericArrayType) componentType, dimension); + } + if (componentType instanceof TypeVariable) { + Map map = new HashMap<>(); + map.put((Class) mockConfig.getVariableType(((TypeVariable) componentType).getName()), null); + result.put(dimension, map); + return result; + } + Map map = new HashMap<>(); + map.put((Class) componentType, null); + result.put(dimension, map); + return result; + } } diff --git a/src/main/java/com/github/jsonzou/jmockdata/mocker/BaseMocker.java b/src/main/java/com/github/jsonzou/jmockdata/mocker/BaseMocker.java new file mode 100644 index 000000000..fcbdedde1 --- /dev/null +++ b/src/main/java/com/github/jsonzou/jmockdata/mocker/BaseMocker.java @@ -0,0 +1,38 @@ +package com.github.jsonzou.jmockdata.mocker; + +import com.github.jsonzou.jmockdata.MockConfig; +import com.github.jsonzou.jmockdata.Mocker; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +@SuppressWarnings("unchecked") +public class BaseMocker implements Mocker { + + private Type type; + + private Type[] genericTypes; + + public BaseMocker(Type type, Type... genericTypes) { + this.type = type; + this.genericTypes = genericTypes; + } + + @Override + public T mock(MockConfig mockConfig) { + Mocker mocker; + if (type instanceof ParameterizedType) { + mocker = new GenericMocker((ParameterizedType) type); + } else if (type instanceof GenericArrayType) { + mocker = new ArrayMocker(type); + } else if (type instanceof TypeVariable) { + Type actualType = mockConfig.getVariableType(((TypeVariable) type).getName()); + mocker = new BaseMocker(actualType); + } else { + mocker = new ClassMocker((Class) type, genericTypes); + } + return (T) mocker.mock(mockConfig); + } + +} diff --git a/src/main/java/com/github/jsonzou/jmockdata/mocker/BeanMocker.java b/src/main/java/com/github/jsonzou/jmockdata/mocker/BeanMocker.java index 056f67b96..c19dd518f 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/mocker/BeanMocker.java +++ b/src/main/java/com/github/jsonzou/jmockdata/mocker/BeanMocker.java @@ -5,89 +5,36 @@ import com.github.jsonzou.jmockdata.Mocker; import com.github.jsonzou.jmockdata.util.ReflectionUtils; import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; import java.util.Map.Entry; -/** - * Bean模拟器 - */ -@SuppressWarnings("unchecked") -public class BeanMocker implements Mocker { +public class BeanMocker implements Mocker { - private Class clazz; + private final Class clazz; - private Type[] genericTypes; - - private Map typeVariableMap; - - public BeanMocker(Class clazz, Type... genericTypes) { + BeanMocker(Class clazz) { this.clazz = clazz; - this.genericTypes = genericTypes; - TypeVariable[] typeVariables = clazz.getTypeParameters(); - if (typeVariables != null && typeVariables.length > 0) { - typeVariableMap = new HashMap<>(); - for (int i = 0; i < typeVariables.length; i++) { - typeVariableMap.put(typeVariables[i].getName(), genericTypes[i]); - } - } } @Override - public T mock(MockConfig mockConfig) { - if (clazz.isArray()) { - Type componentType = genericTypes.length == 0 ? clazz.getComponentType() : genericTypes[0]; - return (T) new ArrayMocker(clazz.getComponentType(), componentType).mock(mockConfig); - } else if (Map.class.isAssignableFrom(clazz)) { - return (T) new MapMocker(genericTypes).mock(mockConfig); - } else if (Collection.class.isAssignableFrom(clazz)) { - return (T) new CollectionMocker(clazz, genericTypes[0]).mock(mockConfig); - } - // 从缓存中取已经构造的Bean - Object cacheBean = mockConfig.getCacheObject(clazz.getName()); - return cacheBean != null ? (T) cacheBean : mockBean(mockConfig); - } - - private T mockBean(MockConfig mockConfig) { + public Object mock(MockConfig mockConfig) { try { - T result = (T) clazz.newInstance(); - mockConfig.addCache(clazz.getName(), result); + // 从缓存中取已经构造的Bean,解决循环依赖问题 + // TODO 2018/1/20 这里解决了循环依赖问题,但是又会出现新的问题, 如List,其中List中的A对象全是相同的。 + Object cacheBean = mockConfig.getBeanCacheObject(clazz.getName()); + if (cacheBean != null) { + return cacheBean; + } + Object result = clazz.newInstance(); + mockConfig.addBeanCache(clazz.getName(), result); for (Class currentClass = clazz; currentClass != Object.class; currentClass = currentClass.getSuperclass()) { // 模拟有setter方法的字段 for (Entry entry : ReflectionUtils.fieldAndSetterMethod(currentClass).entrySet()) { Field field = entry.getKey(); - Type genericType = field.getGenericType(); Method method = entry.getValue(); - Class fieldClass = field.getType(); - Object value = null; - if (genericType instanceof TypeVariable) { - value = new GenericMocker(typeVariableMap.get(((TypeVariable) genericType).getName())).mock(mockConfig); - } else { - // 判断字段是否是Map or Collection - if (Map.class.isAssignableFrom(fieldClass) || Collection.class.isAssignableFrom(fieldClass)) { - value = new BeanMocker(fieldClass, ((ParameterizedType) field.getGenericType()).getActualTypeArguments()).mock(mockConfig); - // 判断字段是否是数组 - } else if (fieldClass.isArray()) { - Type componentType; - Type type = field.getGenericType(); - // 字段是数组类型(一维数组) - if (type instanceof GenericArrayType) { - componentType = ((GenericArrayType) type).getGenericComponentType(); - } else { - // 字段是多维数组 - componentType = ((Class) type).getComponentType(); - } - value = new BeanMocker(fieldClass, componentType).mock(mockConfig); - } else { - value = new ClassMocker(fieldClass).mock(mockConfig); - } - } + Type genericType = field.getGenericType(); + Object value = new BaseMocker(genericType).mock(mockConfig); ReflectionUtils.setRefValue(result, method, value); } } diff --git a/src/main/java/com/github/jsonzou/jmockdata/mocker/ClassMocker.java b/src/main/java/com/github/jsonzou/jmockdata/mocker/ClassMocker.java index a579c1059..8a8be5823 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/mocker/ClassMocker.java +++ b/src/main/java/com/github/jsonzou/jmockdata/mocker/ClassMocker.java @@ -3,21 +3,36 @@ import com.github.jsonzou.jmockdata.MockConfig; import com.github.jsonzou.jmockdata.Mocker; import com.github.jsonzou.jmockdata.MockerManager; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; -public class ClassMocker implements Mocker { +public class ClassMocker implements Mocker { - private Class clazz; + private Class clazz; - public ClassMocker(Class clazz) { + private Type[] genericTypes; + + ClassMocker(Class clazz, Type[] genericTypes) { this.clazz = clazz; + this.genericTypes = genericTypes; } @Override - public T mock(MockConfig mockConfig) { - Mocker mocker = MockerManager.getMocker(clazz); - if (mocker == null) { - mocker = new BeanMocker(clazz); + public Object mock(MockConfig mockConfig) { + Mocker mocker; + if (clazz.isArray()) { + mocker = new ArrayMocker(clazz); + } else if (Map.class.isAssignableFrom(clazz)) { + mocker = new MapMocker(genericTypes); + } else if (Collection.class.isAssignableFrom(clazz)) { + mocker = new CollectionMocker(clazz, genericTypes[0]); + } else { + mocker = MockerManager.getMocker(clazz); + if (mocker == null) { + mocker = new BeanMocker(clazz); + } } - return (T) mocker.mock(mockConfig); + return mocker.mock(mockConfig); } } diff --git a/src/main/java/com/github/jsonzou/jmockdata/mocker/CollectionMocker.java b/src/main/java/com/github/jsonzou/jmockdata/mocker/CollectionMocker.java index 92bcc14b0..884af3094 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/mocker/CollectionMocker.java +++ b/src/main/java/com/github/jsonzou/jmockdata/mocker/CollectionMocker.java @@ -1,7 +1,6 @@ package com.github.jsonzou.jmockdata.mocker; import com.github.jsonzou.jmockdata.MockConfig; -import com.github.jsonzou.jmockdata.MockException; import com.github.jsonzou.jmockdata.Mocker; import com.github.jsonzou.jmockdata.util.RandomUtils; import java.lang.reflect.Type; @@ -9,45 +8,33 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Set; /** - * @author TaoYu + * 模拟Collection */ -@SuppressWarnings("unchecked") -public class CollectionMocker implements Mocker { +public class CollectionMocker implements Mocker { - private Class clazz; + private Class clazz; - private Type type; + private Type genericType; - public CollectionMocker(Class clazz, Type type) { + CollectionMocker(Class clazz, Type genericType) { this.clazz = clazz; - this.type = type; + this.genericType = genericType; } @Override - public Collection mock(MockConfig mockConfig) { + public Object mock(MockConfig mockConfig) { int size = RandomUtils.nextSize(mockConfig.getSizeRange()[0], mockConfig.getSizeRange()[1]); - Collection result = initCollection(size); - while (size-- > 0) { - result.add(new GenericMocker(type).mock(mockConfig)); - } - return result; - } - - private Collection initCollection(int size) { - Collection result; + Collection result; if (List.class.isAssignableFrom(clazz)) { - result = new ArrayList(size); - } else if (Set.class.isAssignableFrom(clazz)) { - result = new HashSet(size); + result = new ArrayList<>(size); } else { - try { - result = (Collection) clazz.newInstance(); - } catch (Exception e) { - throw new MockException("暂时不支持的collection类型", e); - } + result = new HashSet<>(size); + } + for (int index = 0; index < size; index++) { + Object value = new BaseMocker(genericType).mock(mockConfig); + result.add(value); } return result; } diff --git a/src/main/java/com/github/jsonzou/jmockdata/mocker/GenericMocker.java b/src/main/java/com/github/jsonzou/jmockdata/mocker/GenericMocker.java index d2ea0b7d4..a3bd2a7c6 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/mocker/GenericMocker.java +++ b/src/main/java/com/github/jsonzou/jmockdata/mocker/GenericMocker.java @@ -2,33 +2,24 @@ import com.github.jsonzou.jmockdata.MockConfig; import com.github.jsonzou.jmockdata.Mocker; -import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -@SuppressWarnings("unchecked") +/** + * 模拟泛型 + */ public class GenericMocker implements Mocker { - private Type type; + private ParameterizedType type; - public GenericMocker(Type type) { + GenericMocker(ParameterizedType type) { this.type = type; } - @Override public Object mock(MockConfig mockConfig) { - Mocker mocker; - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - mocker = new BeanMocker((Class) parameterizedType.getRawType(), parameterizedType.getActualTypeArguments()); - } else if (type instanceof GenericArrayType) { - ParameterizedType parameterizedType = (ParameterizedType) ((GenericArrayType) type).getGenericComponentType(); - mocker = new ArrayMocker((Class) parameterizedType.getRawType(), parameterizedType); - } else { - mocker = new ClassMocker((Class) type); - } - return mocker.mock(mockConfig); + Type rawType = type.getRawType(); + Type[] argumentTypes = type.getActualTypeArguments(); + return new BaseMocker(rawType, argumentTypes).mock(mockConfig); } - } diff --git a/src/main/java/com/github/jsonzou/jmockdata/mocker/MapMocker.java b/src/main/java/com/github/jsonzou/jmockdata/mocker/MapMocker.java index d9493a30d..449ae9e00 100644 --- a/src/main/java/com/github/jsonzou/jmockdata/mocker/MapMocker.java +++ b/src/main/java/com/github/jsonzou/jmockdata/mocker/MapMocker.java @@ -10,27 +10,23 @@ /** * 模拟Map */ -@SuppressWarnings("unchecked") -public class MapMocker implements Mocker { +public class MapMocker implements Mocker { - private Type[] genericTypes; + private Type[] types; - public MapMocker(Type[] genericTypes) { - this.genericTypes = genericTypes; + MapMocker(Type[] types) { + this.types = types; } @Override - public Map mock(MockConfig mockConfig) { + public Object mock(MockConfig mockConfig) { int size = RandomUtils.nextSize(mockConfig.getSizeRange()[0], mockConfig.getSizeRange()[1]); - Map result = new HashMap(size); - while (size-- > 0) { - result.put(mockType(genericTypes[0], mockConfig), mockType(genericTypes[1], mockConfig)); + Map result = new HashMap<>(size); + for (int index = 0; index < size; index++) { + Object key = new BaseMocker(types[0]).mock(mockConfig); + Object value = new BaseMocker(types[1]).mock(mockConfig); + result.put(key, value); } return result; } - - private Object mockType(Type type, MockConfig mockConfig) { - return new GenericMocker(type).mock(mockConfig); - } - } diff --git a/src/test/java/com/github/jsonzou/jmockdata/JMockDataTest.java b/src/test/java/com/github/jsonzou/jmockdata/JMockDataTest.java index 0dcf0d555..216a2fe24 100644 --- a/src/test/java/com/github/jsonzou/jmockdata/JMockDataTest.java +++ b/src/test/java/com/github/jsonzou/jmockdata/JMockDataTest.java @@ -105,6 +105,13 @@ public void testGenericData() { assertNotNull(genericData); } + @Test + public void testGenericDatas() { + GenericData genericData = JMockData.mock(new TypeReference>() { + }); + assertNotNull(genericData); + } + @Test public void testMockConfig() { MockConfig mockConfig = new MockConfig() diff --git a/src/test/java/com/github/jsonzou/jmockdata/bean/GenericData.java b/src/test/java/com/github/jsonzou/jmockdata/bean/GenericData.java index 7a8f0a26d..248ea5ff0 100644 --- a/src/test/java/com/github/jsonzou/jmockdata/bean/GenericData.java +++ b/src/test/java/com/github/jsonzou/jmockdata/bean/GenericData.java @@ -1,16 +1,85 @@ package com.github.jsonzou.jmockdata.bean; -import lombok.Data; +import java.util.List; +import java.util.Map; /** * Created by jsonzou on 2018/1/17. */ -@Data public class GenericData { private A a; private B b; private C c; + private A[] aArray; + private List bList; + private Map map; + private List[] cArray; + private Map, List>[] d; + + public A getA() { + return a; + } + + public void setA(A a) { + this.a = a; + } + + public B getB() { + return b; + } + + public void setB(B b) { + this.b = b; + } + + public C getC() { + return c; + } + + public void setC(C c) { + this.c = c; + } + + public A[] getaArray() { + return aArray; + } + + public void setaArray(A[] aArray) { + this.aArray = aArray; + } + + public List getbList() { + return bList; + } + + public void setbList(List bList) { + this.bList = bList; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + public List[] getcArray() { + return cArray; + } + + public void setcArray(List[] cArray) { + this.cArray = cArray; + } + + public Map, List>[] getD() { + return d; + } + + public void setD(Map, List>[] d) { + this.d = d; + } }