diff --git a/app/src/main/java/com/airsaid/okmock/data/provider/ArrayMockDataProvider.java b/app/src/main/java/com/airsaid/okmock/data/provider/ArrayMockDataProvider.java index 445c490..073e414 100644 --- a/app/src/main/java/com/airsaid/okmock/data/provider/ArrayMockDataProvider.java +++ b/app/src/main/java/com/airsaid/okmock/data/provider/ArrayMockDataProvider.java @@ -15,120 +15,120 @@ public class ArrayMockDataProvider { @Mock public boolean[] booleanArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public boolean[][] booleanArrayDimension; @Mock public Boolean[] booleanWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Boolean[][] booleanWrapArrayDimension; @Mock public char[] charArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public char[][] charArrayDimension; @Mock public Character[] charWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Character[][] charWrapArrayDimension; @Mock public byte[] byteArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public byte[][] byteArrayDimension; @Mock public Byte[] byteWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Byte[][] byteWrapArrayDimension; @Mock public short[] shortArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public short[][] shortArrayDimension; @Mock public Short[] shortWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Short[][] shortWrapArrayDimension; @Mock public int[] intArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public int[][] intArrayDimension; @Mock public Integer[] intWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Integer[][] intWrapArrayDimension; @Mock public float[] floatArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public float[][] floatArrayDimension; @Mock public Float[] floatWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Float[][] floatWrapArrayDimension; @Mock public long[] longArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public long[][] longArrayDimension; @Mock public Long[] longWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Long[][] longWrapArrayDimension; @Mock public double[] doubleArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public double[][] doubleArrayDimension; @Mock public Double[] doubleWrapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Double[][] doubleWrapArrayDimension; @Mock public Person[] personArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Person[][] personArrayDimension; @Mock public Group[] groupArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Group[][] groupArrayDimension; - @Mock + @Mock(randomSizeRange = {1, 10}) public List[] listArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public List[][] listArrayDimension; - @Mock + @Mock(randomSizeRange = {1, 10}) public Map[] mapArray; - @Mock + @Mock(randomSizeRange = {1, 10}) public Map[][] mapArrayDimension; } diff --git a/app/src/main/java/com/airsaid/okmock/data/provider/EnumDataProvider.kt b/app/src/main/java/com/airsaid/okmock/data/provider/EnumDataProvider.kt index 9906bb9..9f74a57 100644 --- a/app/src/main/java/com/airsaid/okmock/data/provider/EnumDataProvider.kt +++ b/app/src/main/java/com/airsaid/okmock/data/provider/EnumDataProvider.kt @@ -8,10 +8,10 @@ import com.airsaid.okmock.data.Color */ class EnumDataProvider { - @Mock + @Mock(randomSizeRange = [2, 2]) lateinit var color: Color - @Mock + @Mock(randomSizeRange = [10, 20]) lateinit var colorArray: Array @Mock diff --git a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/constant/Constants.kt b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/constant/Constants.kt index ccdbcc9..bd97781 100644 --- a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/constant/Constants.kt +++ b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/constant/Constants.kt @@ -22,7 +22,11 @@ package com.airsaid.okmock.plugin.constant object Constants { const val OK_MOCK_CLASS_NAME = "com/airsaid/okmock/OKMock" + const val OK_MOCK_CONFIG_NAME = "com/airsaid/okmock/OKMockConfig" + const val GET_MOCK_DATA_METHOD_NAME = "getMockData" - const val GET_MOCK_DATA_METHOD_DESCRIPTOR = "(Ljava/lang/String;)Ljava/lang/Object;" + const val GET_MOCK_DATA_METHOD_DESCRIPTOR = "(Ljava/lang/String;Lcom/airsaid/okmock/OKMockConfig;)Ljava/lang/Object;" + + const val RANDOM_SIZE_RANGE_NAME = "randomSizeRange" } \ No newline at end of file diff --git a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/model/FieldInfo.kt b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/model/FieldInfo.kt index 94555cc..4227035 100644 --- a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/model/FieldInfo.kt +++ b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/model/FieldInfo.kt @@ -28,10 +28,41 @@ package com.airsaid.okmock.plugin.model * generic types. * @param value the field's value. This parameter, which may be null if the * field does not have an value. + * @param randomSizeRange the parameter of the [com.airsaid.okmock.OKMock] annotation on the field, + * used to specify random size range for mock data. default size range is 1 until 50. * * @author airsaid */ data class FieldInfo( val owner: String, val access: Int, val name: String, - val descriptor: String, val signature: String?, val value: Any? -) \ No newline at end of file + val descriptor: String, val signature: String?, val value: Any?, + val randomSizeRange: IntArray = intArrayOf(1, 50) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FieldInfo + + if (owner != other.owner) return false + if (access != other.access) return false + if (name != other.name) return false + if (descriptor != other.descriptor) return false + if (signature != other.signature) return false + if (value != other.value) return false + if (!randomSizeRange.contentEquals(other.randomSizeRange)) return false + + return true + } + + override fun hashCode(): Int { + var result = owner.hashCode() + result = 31 * result + access + result = 31 * result + name.hashCode() + result = 31 * result + descriptor.hashCode() + result = 31 * result + (signature?.hashCode() ?: 0) + result = 31 * result + (value?.hashCode() ?: 0) + result = 31 * result + randomSizeRange.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/transform/OKMockTransform.kt b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/transform/OKMockTransform.kt index d8a256d..b45a6d7 100644 --- a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/transform/OKMockTransform.kt +++ b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/transform/OKMockTransform.kt @@ -21,10 +21,13 @@ import com.airsaid.okmock.plugin.OKMockPlugin import com.airsaid.okmock.plugin.constant.Constants.GET_MOCK_DATA_METHOD_DESCRIPTOR import com.airsaid.okmock.plugin.constant.Constants.GET_MOCK_DATA_METHOD_NAME import com.airsaid.okmock.plugin.constant.Constants.OK_MOCK_CLASS_NAME +import com.airsaid.okmock.plugin.constant.Constants.OK_MOCK_CONFIG_NAME +import com.airsaid.okmock.plugin.constant.Constants.RANDOM_SIZE_RANGE_NAME import com.airsaid.okmock.plugin.extension.OKMockExtension import com.airsaid.okmock.plugin.model.FieldInfo import com.airsaid.okmock.plugin.util.isPrimitiveType import com.airsaid.okmock.plugin.util.toPrimitiveWrapType +import com.airsaid.okmock.plugin.util.visitIntValue import com.android.build.api.transform.TransformInvocation import org.gradle.api.Project import org.objectweb.asm.* @@ -106,12 +109,33 @@ class OKMockTransform(project: Project, extension: OKMockExtension) : AbstractTr fv: FieldVisitor ) : FieldVisitor(ASM9, fv) { override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor { - annotations.forEach { annotation -> - if (Type.getDescriptor(annotation) == descriptor) { - results.add(field) + val av = super.visitAnnotation(descriptor, visible) + if (av != null) { + annotations.forEach { annotation -> + if (Type.getDescriptor(annotation) == descriptor) { + results.add(field) + return AnnotationParamsAdapter(field, av) + } } } - return super.visitAnnotation(descriptor, visible) + return av + } + + private class AnnotationParamsAdapter( + private val field: FieldInfo, + annotationVisitor: AnnotationVisitor? + ) : AnnotationVisitor(ASM9, annotationVisitor) { + override fun visitAnnotation(name: String?, descriptor: String?): AnnotationVisitor { + return super.visitAnnotation(name, descriptor) + } + + override fun visit(name: String?, value: Any?) { + if (name == RANDOM_SIZE_RANGE_NAME && value is IntArray) { + field.randomSizeRange[0] = value[0] + field.randomSizeRange[1] = value[1] + } + super.visit(name, value) + } } } @@ -139,6 +163,19 @@ class OKMockTransform(project: Project, extension: OKMockExtension) : AbstractTr mv.visitVarInsn(ALOAD, 0) } mv.visitLdcInsn(getMethodParams(fieldInfo)) + mv.visitTypeInsn(NEW, OK_MOCK_CONFIG_NAME) + mv.visitInsn(DUP) + mv.visitInsn(ICONST_2) + mv.visitIntInsn(NEWARRAY, T_INT) + mv.visitInsn(DUP) + mv.visitInsn(ICONST_0) + mv.visitIntValue(fieldInfo.randomSizeRange[0]) + mv.visitInsn(IASTORE) + mv.visitInsn(DUP) + mv.visitInsn(ICONST_1) + mv.visitIntValue(fieldInfo.randomSizeRange[1]) + mv.visitInsn(IASTORE) + mv.visitMethodInsn(INVOKESPECIAL, OK_MOCK_CONFIG_NAME, "", "([I)V", false) mv.visitMethodInsn( INVOKESTATIC, OK_MOCK_CLASS_NAME, GET_MOCK_DATA_METHOD_NAME, GET_MOCK_DATA_METHOD_DESCRIPTOR, false diff --git a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/util/Extensions.kt b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/util/Extensions.kt index fb5a0f6..c6f5e33 100644 --- a/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/util/Extensions.kt +++ b/okmock-plugin/src/main/java/com/airsaid/okmock/plugin/util/Extensions.kt @@ -21,6 +21,8 @@ import com.android.build.gradle.BaseExtension import com.android.build.gradle.LibraryExtension import org.gradle.api.GradleException import org.gradle.api.Project +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes import org.objectweb.asm.Type fun Project.getExtension(): BaseExtension { @@ -106,3 +108,15 @@ fun Type.toPrimitiveWrapType(): Type { } throw IllegalArgumentException("$this is not a primitive type.") } + +fun MethodVisitor.visitIntValue(value: Int) { + if (value >= -1 && value <= 5) { + visitInsn(value + 3) + } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + visitIntInsn(Opcodes.BIPUSH, value) + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + visitIntInsn(Opcodes.SIPUSH, value) + } else { + visitLdcInsn(value) + } +} diff --git a/okmock/src/main/java/com/airsaid/okmock/ArrayUtils.java b/okmock/src/main/java/com/airsaid/okmock/ArrayUtils.java index 3bd686f..40d4f2a 100644 --- a/okmock/src/main/java/com/airsaid/okmock/ArrayUtils.java +++ b/okmock/src/main/java/com/airsaid/okmock/ArrayUtils.java @@ -26,18 +26,17 @@ class ArrayUtils { /** - * Creates a new array with the specified component type and random length range. + * Creates a new array with the specified component type and size. * - * @param componentType the {@code Class} object representing the component type of the new array. - * @param dimension dimension of the array. - * @param startInclusive the start size of the random length of the array. - * @param endExclusive the end size of the random length of the array. + * @param componentType the {@code Class} object representing the component type of the new array. + * @param dimension dimension of the array. + * @param size default size of the array. * @return the array object. */ - public static Object getArray(Class componentType, int dimension, int startInclusive, int endExclusive) { + public static Object getArray(Class componentType, int dimension, int size) { int[] arrayDimensions = new int[dimension]; for (int i = 0; i < dimension; i++) { - arrayDimensions[i] = RandomDataProvider.nextInt(startInclusive, endExclusive); + arrayDimensions[i] = size; } return Array.newInstance(componentType, arrayDimensions); } diff --git a/okmock/src/main/java/com/airsaid/okmock/OKMock.java b/okmock/src/main/java/com/airsaid/okmock/OKMock.java index 56c0037..d9ff985 100644 --- a/okmock/src/main/java/com/airsaid/okmock/OKMock.java +++ b/okmock/src/main/java/com/airsaid/okmock/OKMock.java @@ -19,10 +19,8 @@ import com.airsaid.okmock.api.MockValue; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; @@ -43,6 +41,32 @@ */ public class OKMock { + private static final OKMockConfig DEFAULT_CONFIG = new OKMockConfig(new int[]{1, 50}); + + private static OKMockConfig sConfig = DEFAULT_CONFIG; + + /** + * Returns the corresponding mock object through the specified type signature and config object. + *

+ * For example, if {@code signature} is {@code Z} descriptor, then return {@link Boolean} mock object. + * if {@code signature} is an {@code [I} descriptor, then return {@link Integer} array mock object. + * if {@code signature} is an {@code Ljava/lang/String;} descriptor, then return {@link String} mock object. + * if {@code signature} is an {@code [Ljava/lang/Boolean;} descriptor, then return {@link Boolean} array mock object. + * if {@code signature} is an {@code Ljava/util/List;} signature, + * then return {@link List} mock object, and contains random {@link String} data. + *

+ * The data is completely random by default, If you need custom data, you can view the {@link MockValue} annotation. + * + * @param signature the type descriptor or signature. + * @param config the configuration object for generate mock data. + * @return the mock object corresponding to the given type descriptor or signature. + * @see MockValue + */ + public static Object getMockData(String signature, OKMockConfig config) { + sConfig = config; + return getMockData(signature); + } + /** * Returns the corresponding mock object through the specified type signature. *

@@ -110,7 +134,7 @@ private static Object getInstanceRecursively(List formatSignatures, int Class sourceClass = TypeUtils.getClass(descriptor); boolean isArray = Objects.requireNonNull(sourceClass).isArray(); Class componentType = TypeUtils.getOriginalComponentType(sourceClass); - Object array = isArray ? ArrayUtils.getArray(componentType, ArrayUtils.getArrayDimension(sourceClass), 1, 50) : null; + Object array = isArray ? ArrayUtils.getArray(componentType, ArrayUtils.getArrayDimension(sourceClass), sConfig.getSize()) : null; if (Boolean.class.isAssignableFrom(componentType) || Boolean.TYPE == componentType) { return isArray ? RandomDataProvider.getRandomBooleanArray(array) : RandomDataProvider.getRandomBoolean(); @@ -220,7 +244,7 @@ private static Map getMapInstance(Class clazz) { } private static void randomForEach(IntConsumer consumer) { - for (int i = 0; i <= RandomDataProvider.nextInt(1, 50); i++) { + for (int i = 0; i <= sConfig.getSize(); i++) { consumer.accept(i); } } diff --git a/okmock/src/main/java/com/airsaid/okmock/OKMockConfig.java b/okmock/src/main/java/com/airsaid/okmock/OKMockConfig.java new file mode 100644 index 0000000..fb00e75 --- /dev/null +++ b/okmock/src/main/java/com/airsaid/okmock/OKMockConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Airsaid. https://github.com/airsaid + * + * 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.airsaid.okmock; + +import java.util.Arrays; + +/** + * Configuration object for {@link OKMock}. + * + * @author airsaid + */ +public class OKMockConfig { + + private final int[] randomSizeRange; + + public OKMockConfig(int[] randomSizeRange) { + this.randomSizeRange = randomSizeRange; + checkConfig(); + } + + public int getSize() { + int startInclusive = randomSizeRange[0]; + int endExclusive = randomSizeRange[1]; + if (startInclusive == endExclusive) { + return startInclusive; + } + return RandomDataProvider.nextInt(startInclusive, endExclusive); + } + + private void checkConfig() { + if (randomSizeRange.length != 2) { + throw new IllegalArgumentException("RandomSizeRange must have two values. current length: " + randomSizeRange.length); + } + if (randomSizeRange[0] > randomSizeRange[1]) { + throw new IllegalArgumentException("The randomSizeRange range is incorrect: " + Arrays.toString(randomSizeRange)); + } + } + + @Override + public String toString() { + return "OKMockConfig{" + + "randomSizeRange=" + Arrays.toString(randomSizeRange) + + '}'; + } +} diff --git a/okmock/src/main/java/com/airsaid/okmock/TypeUtils.java b/okmock/src/main/java/com/airsaid/okmock/TypeUtils.java index 7762608..3e7cbcf 100644 --- a/okmock/src/main/java/com/airsaid/okmock/TypeUtils.java +++ b/okmock/src/main/java/com/airsaid/okmock/TypeUtils.java @@ -74,7 +74,7 @@ public static Class getClass(String descriptor) { try { Class clazz = isPrimitiveType(className) ? getClass(className) : Class.forName(className); if (isArray) { - return ArrayUtils.getArray(clazz, arrayDimension, 0, 0).getClass(); + return ArrayUtils.getArray(clazz, arrayDimension, 0).getClass(); } return clazz; } catch (ClassNotFoundException e) { diff --git a/okmock/src/main/java/com/airsaid/okmock/api/Mock.java b/okmock/src/main/java/com/airsaid/okmock/api/Mock.java index a40d870..e018bc5 100644 --- a/okmock/src/main/java/com/airsaid/okmock/api/Mock.java +++ b/okmock/src/main/java/com/airsaid/okmock/api/Mock.java @@ -22,10 +22,10 @@ import java.lang.annotation.Target; /** - * This annotation is an identification annotation. that is used to decorate field. - * The field to be decorated must not have an assignment operation. + * This annotation is used to specify the field to be mocked. *

- * The decorated fields are automatically populated with random data. + * The decorated fields cannot have assignment operations and will be + * automatically populated with random data. * * @author airsaid */ @@ -33,4 +33,11 @@ @Target(ElementType.FIELD) public @interface Mock { + /** + * Returns specify a random size range for the mock data. + * + * @return random size range for the mock data. + */ + int[] randomSizeRange() default {1, 50}; + } \ No newline at end of file diff --git a/okmock/src/test/java/com/airsaid/okmock/ArrayUtilsTest.java b/okmock/src/test/java/com/airsaid/okmock/ArrayUtilsTest.java index e9229c8..9ac8e7d 100644 --- a/okmock/src/test/java/com/airsaid/okmock/ArrayUtilsTest.java +++ b/okmock/src/test/java/com/airsaid/okmock/ArrayUtilsTest.java @@ -1,12 +1,12 @@ package com.airsaid.okmock; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import org.junit.Test; import java.lang.reflect.Array; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - /** * @author airsaid */ @@ -14,7 +14,7 @@ public class ArrayUtilsTest { @Test public void getArray() { - Object array = ArrayUtils.getArray(String.class, 1, 3, 3); + Object array = ArrayUtils.getArray(String.class, 1, 3); int length = Array.getLength(array); assertEquals(3, length); @@ -25,8 +25,8 @@ public void getArray() { } @Test - public void gArray2By2Dimension() { - Object array = ArrayUtils.getArray(String.class, 2, 1, 1); + public void getArray2By2Dimension() { + Object array = ArrayUtils.getArray(String.class, 2, 1); assertEquals(1, Array.getLength(array)); assertEquals(String[].class, Array.get(array, 0).getClass()); assertEquals(1, Array.getLength(Array.get(array, 0)));