Skip to content

Commit

Permalink
Fix Bug 'set field of instance failed, when class is super class'
Browse files Browse the repository at this point in the history
  • Loading branch information
zeshaoaaa committed Jul 9, 2019
1 parent cd4c10a commit 7c14aec
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 42 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ uploadArchives {
// 项目名称
pom.artifactId = "OkReflect"
// 版本号
pom.version = "0.0.6"
pom.version = "0.1.3"
}
}
52 changes: 34 additions & 18 deletions src/main/kotlin/okreflect/MethodGetter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ class MethodGetter {
*
* Get the classes of arguments cof constructor or method of the class.
*/
private fun getParametersType(args: Array<out Any>): Array<Class<*>?> {
private fun getParametersType(args: Array<out Any?>): Array<Class<*>?> {
val result = arrayOfNulls<Class<*>>(args.size)
for (i in args.indices) {
result[i] = args[i]::class.java
result[i] = args[i]!!::class.java
}
return result
}
Expand All @@ -42,13 +42,13 @@ class MethodGetter {
*
* Get the classes of arguments cof constructor or method of the class.
*/
private fun getConversedParametersType(args: Array<out Any>): Array<Class<*>?> {
private fun getConversedParametersType(args: Array<out Any?>): Array<Class<*>?> {
val result = arrayOfNulls<Class<*>>(args.size)
for (i in args.indices) {
// When the parameters type is int, Kotlin will take it as Integer,
// so I specify the type as primitive type,
// When the parameter type is primitive, Kotlin will box it,
// so I specify the type as primitive type manually,
// I have not found another solution to solve this problem , if you have
// any idea or suggestion, you can contact me.
// any idea or suggestion, please tell me, thanks.
result[i] = when (args[i]) {
is Byte -> Byte::class.java
is Short -> Short::class.java
Expand All @@ -58,33 +58,39 @@ class MethodGetter {
is Float -> Float::class.java
is Double -> Double::class.java
is Boolean -> Boolean::class.java
else -> args[i]::class.java
else -> args[i]!!::class.java
}
}
return result
}

/**
*
* @param clazz: The class that you want to use.
* @param name: The name of the method you want to call.
* @param args: Parameters that use to call the method.
* @param parameterTypes: The class of parameters in the method.
*
* Get method by the method name you've passed.
* Get method by the name and parameters of the method you've passed.
*/
fun getMethod(clazz: Class<*>?, name: String, args: Array<out Any>): Method? {
fun getMethod(
clazz: Class<*>?,
name: String,
args: Array<out Any?>,
parameterTypes: Array<Class<*>>?
): Method? {

var exception: Exception? = null
var method: Method? = null

try {
method = getDeclaredMethod(clazz, name, args)
method = getDeclaredMethod(clazz, name, args, parameterTypes)
} catch (e: Exception) {
exception = e
}

if (method == null) {
try {
method = getNonDeclaredMethod(clazz, name, args)
method = getNonDeclaredMethod(clazz, name, args, parameterTypes)
} catch (e: Exception) {
exception = e
}
Expand All @@ -104,20 +110,25 @@ class MethodGetter {
/**
* Get method.
*/
private fun getNonDeclaredMethod(clazz: Class<*>?, name: String, args: Array<out Any>): Method? {
private fun getNonDeclaredMethod(
clazz: Class<*>?,
name: String,
args: Array<out Any?>,
parameterTypes: Array<Class<*>>?
): Method? {
var exception: Exception? = null
var method: Method? = null

try {
val types = getParametersType(args)
val types = parameterTypes ?: getParametersType(args)
method = clazz!!.getMethod(name, *types)
} catch (e: Exception) {
exception = e
}

if (method == null) {
try {
val types = getConversedParametersType(args)
val types = parameterTypes ?: getConversedParametersType(args)
method = clazz!!.getMethod(name, *types)
} catch (e: Exception) {
exception = e
Expand All @@ -132,19 +143,24 @@ class MethodGetter {
/**
* Get declared method.
*/
private fun getDeclaredMethod(clazz: Class<*>?, name: String, args: Array<out Any>): Method? {
private fun getDeclaredMethod(
clazz: Class<*>?,
name: String,
args: Array<out Any?>,
parameterTypes: Array<Class<*>>?
): Method? {
var exception: Exception? = null
var declared: Method? = null
try {
val types = getParametersType(args)
val types = parameterTypes ?: getParametersType(args)
declared = clazz!!.getDeclaredMethod(name, *types)
} catch (e: Exception) {
exception = e
}

if (declared == null) {
try {
val types = getConversedParametersType(args)
val types = parameterTypes ?: getConversedParametersType(args)
declared = clazz!!.getDeclaredMethod(name, *types)
} catch (e: Exception) {
exception = e
Expand Down
80 changes: 57 additions & 23 deletions src/main/kotlin/okreflect/OkReflect.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class OkReflect {
*/
private var withOuterInstance = false

/**
* The class of parameters in the method.
*/
private var parameterTypes: Array<Class<*>>? = null

/**
* @param className: The name of the class that you want to create.
*
Expand Down Expand Up @@ -154,13 +159,22 @@ class OkReflect {
}

/**
* @param methodName: the name of the method that you want to call.
* @param methodName: The name of the method that you want to call.
* @param classes: The class of the parameters.
* @param args: The parameters of the method that you wan to call.
*
* Call the method that you want to call.
* The method will be called when [get] method called.
* The method will be call with the instance.
*/
fun call(methodName: String, classes: Array<Class<*>>, vararg args: Any): OkReflect {
parameterTypes = classes
return realCall(methodName, true, *args)
}

/**
* @See [call]
*/
fun call(methodName: String, vararg args: Any): OkReflect {
return realCall(methodName, true, *args)
}
Expand Down Expand Up @@ -202,6 +216,7 @@ class OkReflect {
* you need to pass the instance in this method.
*/
fun with(instance: Any): OkReflect {
withOuterInstance = true
this.instance = instance
return this
}
Expand All @@ -218,6 +233,7 @@ class OkReflect {
return this
}


/**
* @param fieldName: The name of the field.
* @param arg: The value that you want to set to the field.
Expand All @@ -241,7 +257,7 @@ class OkReflect {
*/
private fun setFieldOfInstance(fieldName: String, arg: Any) {
val osName = System.getProperty("os.name")
var field = instance!!.javaClass.getDeclaredField(fieldName)
var field = clazz!!.getDeclaredField(fieldName)
field = accessible(field)
if (osName != "Linux") {
removeFinalModifier(field)
Expand All @@ -263,14 +279,12 @@ class OkReflect {
*/
private fun invoke(methodCall: MethodCall) {
val args = methodCall.args
val methods = instance!!.javaClass.methods
val method = getMethod(clazz, methodCall.methodName, args)
val withInstance = methodCall.callWithInstance
val method = getMethod(clazz, methodCall.methodName, args, parameterTypes)
val returnType = method!!.returnType.toString()
if (returnType == "void") {
method.invoke(instance, *args)
} else {
result = if (withInstance) {
result = if (methodCall.callWithInstance) {
method.invoke(instance, *args)
} else {
verifyResult()
Expand Down Expand Up @@ -376,25 +390,32 @@ class OkReflect {
}
}

/**
* Get the result value from last method if it has a return value,
* or else you will get the instance.
*/
fun <T> get(): T? {
return getByFlag(RETURN_FLAG_RESULT)
}

/**
* @param fieldName: The name of the field that you want to get.
*
* Get the result value from last method if it has a return value,
* or else you will get the instance.
* Get instance when return value from last method is null.
*/
fun <T> get(fieldName: String): T? {
targetFieldName = fieldName
return getByFlag<T>(RETURN_FLAG_FIELD)
}

/**
* @see [get]
*/
fun <T> get(): T? {
return getByFlag(RETURN_FLAG_RESULT_OR_INSTANCE)
}

/**
* OkReflect will return instance when the result is null,
* when you trying to get return value no matter result is null,
* then you can use this method.
*/
fun <T> getResult():T? {
return getByFlag(RETURN_FLAG_RESULT)
}

/**
* Get the class.
*/
Expand Down Expand Up @@ -427,14 +448,11 @@ class OkReflect {
*/
private fun <T> realGet(returnFlag: Int): T? {
if (!withOuterInstance) {
val needInstance = returnFlag == RETURN_FLAG_INSTANCE || returnFlag == RETURN_FLAG_RESULT
if (needInstance) {
if (needInstance(returnFlag)) {
verifyClassInfo()
verifyConstructorArgs()
}
if (clazz == null) {
this.clazz = Class.forName(className!!)
}
initClazz()
if (createCalled) {
initInstance()
invokeMethods()
Expand All @@ -447,6 +465,16 @@ class OkReflect {
return getByResult<T>(returnFlag)
}

private fun initClazz() {
if (clazz == null) {
this.clazz = Class.forName(className!!)
}
}

private fun needInstance(returnFlag: Int): Boolean {
return returnFlag == RETURN_FLAG_INSTANCE || returnFlag == RETURN_FLAG_RESULT_OR_INSTANCE
}

/**
* If there is no constructor parameters for the , there will throw an exception
*/
Expand Down Expand Up @@ -495,7 +523,8 @@ class OkReflect {
RETURN_FLAG_FIELD -> {
targetFieldValue as T
}
RETURN_FLAG_RESULT -> {
RETURN_FLAG_RESULT -> result as T
RETURN_FLAG_RESULT_OR_INSTANCE -> {
if (result != null) {
result as T
} else {
Expand Down Expand Up @@ -526,13 +555,18 @@ class OkReflect {
/**
* Return the return value from the method that you invoked.
*/
private const val RETURN_FLAG_RESULT = 3
private const val RETURN_FLAG_RESULT_OR_INSTANCE = 3

/**
* Return the field.
*/
private const val RETURN_FLAG_FIELD = 4

/**
* Return the return value from the invoked method.
*/
private const val RETURN_FLAG_RESULT = 5

/**
* Set the class name of the instance/
*/
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/TestClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class TestClass extends SuperTestClass {
private static final String staticFinalField = "finalString";
private final String nickname = "666";
private byte b;
private Byte b2;
public char c = 'a';
private static int i = 10;
private Integer i2 = 12;
Expand All @@ -25,6 +26,10 @@ private TestClass(String name, int age) {
this.age = age;
}

private void setName(String name) {
this.name = name;
}

private String getName() {
return name;
}
Expand All @@ -42,4 +47,9 @@ private void setData(String name, byte b) {
this.b = b;
}

private void setData2(String name, Byte b) {
this.name = name;
this.b2 = b;
}

}
23 changes: 23 additions & 0 deletions src/test/java/UseCaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


Expand Down Expand Up @@ -234,7 +235,24 @@ public void testCallMethodWithMultipleParameter() {
assert name.equals("Tom");
}

@Test
public void testCallMethodWithVoidParameter() {
TestClass testClass = new TestClass();
Class classes[] = {String.class, Byte.class};
String name = OkReflect.on(testClass)
.call("setData2", classes, "Tom", null)
.get("name");
assert name.equals("Tom");
}

@Test
public void testGetResult() {
TestClass testClass = new TestClass();
String name = OkReflect.on(testClass)
.call("getName")
.getResult();
assert name.equals("default");
}

@Ignore
@Test
Expand All @@ -245,5 +263,10 @@ public void testSetFinalFieldOfClass() {
assert finalField.equals("changed");
}

// Fix Bug 'set field of instance failed, when class is super class',
// Fix Bug 'get class of parameter failed, when parameter is null'
// Added getResult() method for the purpose of obtain the return value no matter it is null or not.
// Added classes parameter to call() and simpleCall() methods for the purpose of
// passing void parameter into the method.

}

0 comments on commit 7c14aec

Please sign in to comment.