mirror of
https://github.com/oasisfeng/deagle.git
synced 2025-01-09 04:10:03 +08:00
UPDATE: Add Hack.Invokable.getReturnType().
FIX: Various bugs in Hack.
This commit is contained in:
parent
f40b4b9661
commit
864b7b90dd
@ -13,6 +13,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Java reflection helper optimized for hacking non-public APIs.
|
||||
@ -30,6 +31,9 @@ import java.util.Arrays;
|
||||
@SuppressWarnings({"Convert2Lambda", "WeakerAccess", "unused"})
|
||||
public class Hack {
|
||||
|
||||
public static Class<?> ANY_TYPE = AnyType.class;
|
||||
private static class AnyType {}
|
||||
|
||||
/** This exception is purposely defined as "protected" and not extending Exception to avoid
|
||||
* developers unconsciously catch it outside the centralized hacks declaration, which results
|
||||
* in potentially pre-checked usage of hacks. */
|
||||
@ -152,9 +156,9 @@ public class Hack {
|
||||
|
||||
public static class HackedField<C, T> {
|
||||
|
||||
/** Assert the field type */
|
||||
/** Assert the field type. */
|
||||
public <T2> HackedField<C, T2> ofType(final Class<T2> type) {
|
||||
if (mField != null && ! type.isAssignableFrom(mField.getType()))
|
||||
if (type != ANY_TYPE && mField != null && ! type.isAssignableFrom(mField.getType()))
|
||||
fail(new AssertionException(new ClassCastException(mField + " is not of type " + type)).setHackedField(mField));
|
||||
@SuppressWarnings("unchecked") final HackedField<C, T2> casted = (HackedField<C, T2>) this;
|
||||
return casted;
|
||||
@ -171,7 +175,7 @@ public class Hack {
|
||||
}
|
||||
|
||||
/** Fallback to the given value if this field is unavailable at runtime */
|
||||
public HackedField<C, T> fallbackTo(final T value) {
|
||||
public @NonNull HackedField<C, T> fallbackTo(final T value) {
|
||||
mFallbackValue = value;
|
||||
return this;
|
||||
}
|
||||
@ -189,8 +193,8 @@ public class Hack {
|
||||
|
||||
/** Get current value of this field */
|
||||
public T get(final C instance) {
|
||||
if (mField == null) return mFallbackValue;
|
||||
try {
|
||||
if (mField == null) return mFallbackValue;
|
||||
@SuppressWarnings("unchecked") final T value = (T) mField.get(instance);
|
||||
return value;
|
||||
} catch (final IllegalAccessException e) { return null; } // Should never happen
|
||||
@ -213,11 +217,13 @@ public class Hack {
|
||||
try {
|
||||
if (clazz == null) return;
|
||||
field = clazz.getDeclaredField(name);
|
||||
if (Modifier.isStatic(modifiers) != Modifier.isStatic(field.getModifiers()))
|
||||
if (Modifier.isStatic(modifiers) != Modifier.isStatic(field.getModifiers())) {
|
||||
fail(new AssertionException(field + (Modifier.isStatic(modifiers) ? " is not static" : "is static")).setHackedFieldName(name));
|
||||
if (modifiers > 0 && (field.getModifiers() & modifiers) != modifiers)
|
||||
field = null;
|
||||
} else if (modifiers > 0 && (field.getModifiers() & modifiers) != modifiers) {
|
||||
fail(new AssertionException(field + " does not match modifiers: " + modifiers).setHackedFieldName(name));
|
||||
if (! field.isAccessible()) field.setAccessible(true);
|
||||
field = null;
|
||||
} else if (! field.isAccessible()) field.setAccessible(true);
|
||||
} catch (final NoSuchFieldException e) {
|
||||
final AssertionException hae = new AssertionException(e);
|
||||
hae.setHackedClass(clazz);
|
||||
@ -235,6 +241,7 @@ public class Hack {
|
||||
public static class HackedTargetField<T> {
|
||||
|
||||
public T get() {
|
||||
if (mField == null) return mFallbackValue;
|
||||
try {
|
||||
@SuppressWarnings("unchecked") final T value = (T) mField.get(mInstance);
|
||||
return value;
|
||||
@ -242,7 +249,7 @@ public class Hack {
|
||||
}
|
||||
|
||||
public void set(final T value) {
|
||||
try {
|
||||
if (mField != null) try {
|
||||
mField.set(mInstance, value);
|
||||
} catch (final IllegalAccessException ignored) {} // Should never happen
|
||||
}
|
||||
@ -255,21 +262,30 @@ public class Hack {
|
||||
}
|
||||
|
||||
public HackedTargetField<T> ofType(final String type_name) {
|
||||
if (mField == null) return this;
|
||||
try { @SuppressWarnings("unchecked")
|
||||
final HackedTargetField<T> casted = (HackedTargetField<T>) ofType(Class.forName(type_name, false, mField.getDeclaringClass().getClassLoader()));
|
||||
return casted;
|
||||
} catch (final ClassNotFoundException e) {
|
||||
fail(new AssertionException(e)); return this;
|
||||
fail(new AssertionException(e));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
HackedTargetField(final Field field, final @Nullable Object instance) {
|
||||
/** Fallback to the given value if this field is unavailable at runtime */
|
||||
public @NonNull HackedTargetField<T> fallbackTo(final T value) {
|
||||
mFallbackValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
HackedTargetField(final @Nullable Field field, final @Nullable Object instance) {
|
||||
mField = field;
|
||||
mInstance = instance;
|
||||
}
|
||||
|
||||
private final Field mField;
|
||||
private final @Nullable Field mField;
|
||||
private final Object mInstance; // Instance type is already checked
|
||||
private @Nullable T mFallbackValue;
|
||||
}
|
||||
|
||||
public interface HackedInvokable<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
|
||||
@ -310,26 +326,42 @@ public class Hack {
|
||||
@NonNull HackedMethodN<R, C, T1, T2, T3> withParams(Class<?>... types);
|
||||
}
|
||||
|
||||
public interface HackedMethod0<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
|
||||
@CheckResult HackInvocation<R, C, T1, T2, T3> invoke();
|
||||
public static class CheckedHackedMethod<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
|
||||
|
||||
CheckedHackedMethod(final Invokable invokable) { mInvokable = invokable; }
|
||||
@SuppressWarnings("unchecked") public Class<R> getReturnType() { return (Class<R>) mInvokable.getReturnType(); }
|
||||
protected HackInvocation<R, C, T1, T2, T3> invoke(Object... args) { return new HackInvocation<>(mInvokable, args); }
|
||||
|
||||
private final Invokable mInvokable;
|
||||
}
|
||||
public interface HackedMethod1<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1> {
|
||||
@CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg);
|
||||
|
||||
public static class HackedMethod0<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> extends CheckedHackedMethod<R, C, T1,T2,T3> {
|
||||
HackedMethod0(final Invokable invokable) { super(invokable); }
|
||||
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke() { return super.invoke(); }
|
||||
}
|
||||
public interface HackedMethod2<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2> {
|
||||
@CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2);
|
||||
public static class HackedMethod1<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1> extends CheckedHackedMethod<R, C, T1,T2,T3> {
|
||||
HackedMethod1(final Invokable invokable) { super(invokable); }
|
||||
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg) { return super.invoke(arg); }
|
||||
}
|
||||
public interface HackedMethod3<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3> {
|
||||
@CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2, A3 arg3);
|
||||
public static class HackedMethod2<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2> extends CheckedHackedMethod<R, C, T1,T2,T3> {
|
||||
HackedMethod2(final Invokable invokable) { super(invokable); }
|
||||
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2) { return super.invoke(arg1, arg2); }
|
||||
}
|
||||
public interface HackedMethod4<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3, A4> {
|
||||
@CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2, A3 arg3, A4 arg4);
|
||||
public static class HackedMethod3<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3> extends CheckedHackedMethod<R, C, T1,T2,T3> {
|
||||
HackedMethod3(final Invokable invokable) { super(invokable); }
|
||||
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2, A3 arg3) { return super.invoke(arg1, arg2, arg3); }
|
||||
}
|
||||
public interface HackedMethod5<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3, A4, A5> {
|
||||
@CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5);
|
||||
public static class HackedMethod4<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3, A4> extends CheckedHackedMethod<R, C, T1,T2,T3> {
|
||||
HackedMethod4(final Invokable invokable) { super(invokable); }
|
||||
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2, A3 arg3, A4 arg4) { return super.invoke(arg1, arg2, arg3, arg4); }
|
||||
}
|
||||
public interface HackedMethodN<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
|
||||
@CheckResult HackInvocation<R, C, T1, T2, T3> invoke(Object... args);
|
||||
public static class HackedMethod5<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3, A4, A5> extends CheckedHackedMethod<R, C, T1,T2,T3> {
|
||||
HackedMethod5(final Invokable invokable) { super(invokable); }
|
||||
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) { return super.invoke(arg1, arg2, arg3, arg4, arg5); }
|
||||
}
|
||||
public static class HackedMethodN<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> extends CheckedHackedMethod<R, C, T1,T2,T3> {
|
||||
HackedMethodN(final Invokable invokable) { super(invokable); }
|
||||
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(Object... args) { return super.invoke(args); }
|
||||
}
|
||||
|
||||
public static class HackInvocation<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
|
||||
@ -361,6 +393,7 @@ public class Hack {
|
||||
|
||||
interface Invokable<C> {
|
||||
Object invoke(C target, Object[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException;
|
||||
Class<?> getReturnType();
|
||||
}
|
||||
|
||||
private static class HackedMethodImpl<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> implements NonNullHackedMethod<R, C, T1, T2, T3> {
|
||||
@ -391,7 +424,7 @@ public class Hack {
|
||||
@Override public <TT1 extends Throwable, TT2 extends Throwable> HackedMethod<R, C, TT1, TT2, T3>
|
||||
throwing(final Class<TT1> type1, final Class<TT2> type2) {
|
||||
mThrowTypes = new Class<?>[] { type1, type2 };
|
||||
Arrays.sort(mThrowTypes);
|
||||
Arrays.sort(mThrowTypes, CLASS_COMPARATOR);
|
||||
@SuppressWarnings("unchecked") final HackedMethod<R, C, TT1, TT2, T3> cast = (HackedMethod<R, C, TT1, TT2, T3>) this;
|
||||
return cast;
|
||||
}
|
||||
@ -399,98 +432,70 @@ public class Hack {
|
||||
@Override public <TT1 extends Throwable, TT2 extends Throwable, TT3 extends Throwable> HackedMethod<R, C, TT1, TT2, TT3>
|
||||
throwing(final Class<TT1> type1, final Class<TT2> type2, final Class<TT3> type3) {
|
||||
mThrowTypes = new Class<?>[] { type1, type2, type3 };
|
||||
Arrays.sort(mThrowTypes);
|
||||
Arrays.sort(mThrowTypes, CLASS_COMPARATOR);
|
||||
@SuppressWarnings("unchecked") final HackedMethod<R, C, TT1, TT2, TT3> cast = (HackedMethod<R, C, TT1, TT2, TT3>) this;
|
||||
return cast;
|
||||
}
|
||||
|
||||
@Override public HackedMethod<R, C, Exception, T2, T3> throwing(final Class<?>... types) {
|
||||
mThrowTypes = types;
|
||||
Arrays.sort(mThrowTypes);
|
||||
Arrays.sort(mThrowTypes, CLASS_COMPARATOR);
|
||||
@SuppressWarnings("unchecked") final HackedMethod<R, C, Exception, T2, T3> cast = (HackedMethod<R, C, Exception, T2, T3>) this;
|
||||
return cast;
|
||||
}
|
||||
|
||||
@NonNull @SuppressWarnings("ConstantConditions")
|
||||
@Override public HackedMethod0<R, C, T1, T2, T3> withoutParams() {
|
||||
final Invokable method = findInvokable();
|
||||
return method == null ? null : new HackedMethod0<R, C, T1, T2, T3>() {
|
||||
@Override public HackInvocation<R, C, T1, T2, T3> invoke() {
|
||||
return new HackInvocation<>(method);
|
||||
}
|
||||
};
|
||||
final Invokable<C> invokable = findInvokable();
|
||||
return invokable == null ? null : new HackedMethod0<R, C, T1, T2, T3>(invokable);
|
||||
}
|
||||
|
||||
@NonNull @SuppressWarnings("ConstantConditions")
|
||||
@Override public <A1> HackedMethod1<R, C, T1, T2, T3, A1> withParam(final Class<A1> type) {
|
||||
final Invokable method = findInvokable(type);
|
||||
return method == null ? null : new HackedMethod1<R, C, T1, T2, T3, A1>() {
|
||||
@Override public HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg) {
|
||||
return new HackInvocation<>(method, arg);
|
||||
}
|
||||
};
|
||||
final Invokable invokable = findInvokable(type);
|
||||
return invokable == null ? null : new HackedMethod1<R, C, T1, T2, T3, A1>(invokable);
|
||||
}
|
||||
|
||||
@NonNull @SuppressWarnings("ConstantConditions")
|
||||
@Override public <A1, A2> HackedMethod2<R, C, T1, T2, T3, A1, A2> withParams(final Class<A1> type1, final Class<A2> type2) {
|
||||
final Invokable method = findInvokable(type1, type2);
|
||||
return method == null ? null : new HackedMethod2<R, C, T1, T2, T3, A1, A2>() {
|
||||
@Override public HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2) {
|
||||
return new HackInvocation<>(method, arg1, arg2);
|
||||
}
|
||||
};
|
||||
final Invokable invokable = findInvokable(type1, type2);
|
||||
return invokable == null ? null : new HackedMethod2<R, C, T1, T2, T3, A1, A2>(invokable);
|
||||
}
|
||||
|
||||
@NonNull @SuppressWarnings("ConstantConditions")
|
||||
@Override public <A1, A2, A3> HackedMethod3<R, C, T1, T2, T3, A1, A2, A3> withParams(final Class<A1> type1, final Class<A2> type2, final Class<A3> type3) {
|
||||
final Invokable method = findInvokable(type1, type2, type3);
|
||||
return method == null ? null : new HackedMethod3<R, C, T1, T2, T3, A1, A2, A3>() {
|
||||
@Override public HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2, final A3 arg3) {
|
||||
return new HackInvocation<>(method, arg1, arg2, arg3);
|
||||
}
|
||||
};
|
||||
final Invokable invokable = findInvokable(type1, type2, type3);
|
||||
return invokable == null ? null : new HackedMethod3<R, C, T1, T2, T3, A1, A2, A3>(invokable);
|
||||
}
|
||||
|
||||
@NonNull @SuppressWarnings("ConstantConditions")
|
||||
@Override public <A1, A2, A3, A4> HackedMethod4<R, C, T1, T2, T3, A1, A2, A3, A4> withParams(final Class<A1> type1, final Class<A2> type2, final Class<A3> type3, final Class<A4> type4) {
|
||||
final Invokable method = findInvokable(type1, type2, type3, type4);
|
||||
return method == null ? null : new HackedMethod4<R, C, T1, T2, T3, A1, A2, A3, A4>() {
|
||||
@Override public HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2, final A3 arg3, final A4 arg4) {
|
||||
return new HackInvocation<>(method, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
};
|
||||
final Invokable invokable = findInvokable(type1, type2, type3, type4);
|
||||
return invokable == null ? null : new HackedMethod4<R, C, T1, T2, T3, A1, A2, A3, A4>(invokable);
|
||||
}
|
||||
|
||||
@NonNull @SuppressWarnings("ConstantConditions")
|
||||
@Override public <A1, A2, A3, A4, A5> HackedMethod5<R, C, T1, T2, T3, A1, A2, A3, A4, A5> withParams(final Class<A1> type1, final Class<A2> type2, final Class<A3> type3, final Class<A4> type4, final Class<A5> type5) {
|
||||
final Invokable method = findInvokable(type1, type2, type3, type4, type5);
|
||||
return method == null ? null : new HackedMethod5<R, C, T1, T2, T3, A1, A2, A3, A4, A5>() {
|
||||
@Override public HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2, final A3 arg3, final A4 arg4, final A5 arg5) {
|
||||
return new HackInvocation<>(method, arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
};
|
||||
final Invokable invokable = findInvokable(type1, type2, type3, type4, type5);
|
||||
return invokable == null ? null : new HackedMethod5<R, C, T1, T2, T3, A1, A2, A3, A4, A5>(invokable);
|
||||
}
|
||||
|
||||
@NonNull @SuppressWarnings("ConstantConditions")
|
||||
@Override public HackedMethodN<R, C, T1, T2, T3> withParams(final Class<?>... types) {
|
||||
final Invokable method = findInvokable(types);
|
||||
return method == null ? null : new HackedMethodN<R, C, T1, T2, T3>() {
|
||||
@Override public HackInvocation<R, C, T1, T2, T3> invoke(final Object... args) {
|
||||
return new HackInvocation<>(method, args);
|
||||
}
|
||||
};
|
||||
final Invokable invokable = findInvokable(types);
|
||||
return invokable == null ? null : new HackedMethodN<R, C, T1, T2, T3>(invokable);
|
||||
}
|
||||
|
||||
private @Nullable Invokable findInvokable(final Class<?>... param_types) {
|
||||
final int modifiers; final Invokable invokable; final AccessibleObject accessible; final Class<?>[] ex_types;
|
||||
private @Nullable Invokable<C> findInvokable(final Class<?>... param_types) {
|
||||
final int modifiers; final Invokable<C> invokable; final AccessibleObject accessible; final Class<?>[] ex_types;
|
||||
try {
|
||||
if (mName != null) {
|
||||
final Method method = mClass.getDeclaredMethod(mName, param_types);
|
||||
modifiers = method.getModifiers(); invokable = new InvokableMethod(method); accessible = method;
|
||||
modifiers = method.getModifiers(); invokable = new InvokableMethod<>(method); accessible = method;
|
||||
ex_types = method.getExceptionTypes();
|
||||
if (Modifier.isStatic(mModifiers) != Modifier.isStatic(method.getModifiers()))
|
||||
fail(new AssertionException(method + (Modifier.isStatic(mModifiers) ? " is not static" : "is static")).setHackedMethod(method));
|
||||
if (mReturnType != null && ! method.getReturnType().equals(mReturnType))
|
||||
if (mReturnType != null && mReturnType != ANY_TYPE && ! method.getReturnType().equals(mReturnType))
|
||||
fail(new AssertionException("Return type mismatch: " + method));
|
||||
} else {
|
||||
final Constructor<C> ctor = mClass.getDeclaredConstructor(param_types);
|
||||
@ -500,7 +505,7 @@ public class Hack {
|
||||
} catch (final NoSuchMethodException e) {
|
||||
fail(new AssertionException(e).setHackedClass(mClass).setHackedMethodName(mName).setParamTypes(param_types));
|
||||
if (! mHasFallback) return null;
|
||||
return new FallbackInvokable(mFallbackReturnValue);
|
||||
return new FallbackInvokable<>(mFallbackReturnValue);
|
||||
}
|
||||
|
||||
if (mModifiers > 0 && (modifiers & mModifiers) != mModifiers)
|
||||
@ -509,7 +514,7 @@ public class Hack {
|
||||
if (mThrowTypes == null && ex_types.length > 0 || mThrowTypes != null && ex_types.length == 0)
|
||||
fail(new AssertionException("Checked exception(s) not match: " + invokable));
|
||||
else if (mThrowTypes != null) {
|
||||
Arrays.sort(ex_types);
|
||||
Arrays.sort(ex_types, CLASS_COMPARATOR);
|
||||
if (! Arrays.equals(ex_types, mThrowTypes))
|
||||
fail(new AssertionException("Checked exception(s) not match: " + invokable));
|
||||
}
|
||||
@ -525,6 +530,15 @@ public class Hack {
|
||||
private Class<?>[] mThrowTypes;
|
||||
private R mFallbackReturnValue;
|
||||
private boolean mHasFallback = true; // Default to true for method returning void
|
||||
private static final Comparator<Class> CLASS_COMPARATOR = new Comparator<Class>() {
|
||||
@Override public int compare(final Class lhs, final Class rhs) {
|
||||
return lhs.toString().compareTo(rhs.toString());
|
||||
}
|
||||
|
||||
@Override public boolean equals(final Object object) {
|
||||
return this == object;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class InvokableMethod<C> implements Invokable<C> {
|
||||
@ -536,6 +550,8 @@ public class Hack {
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
|
||||
@Override public Class<?> getReturnType() { return method.getReturnType(); }
|
||||
|
||||
@Override public String toString() { return method.toString(); }
|
||||
|
||||
private final Method method;
|
||||
@ -550,6 +566,10 @@ public class Hack {
|
||||
return constructor.newInstance(args);
|
||||
}
|
||||
|
||||
@Override public Class<?> getReturnType() {
|
||||
return constructor.getDeclaringClass();
|
||||
}
|
||||
|
||||
@Override public String toString() { return constructor.toString(); }
|
||||
|
||||
private final Constructor<C> constructor;
|
||||
@ -557,13 +577,17 @@ public class Hack {
|
||||
|
||||
private static class FallbackInvokable<C> implements Invokable<C> {
|
||||
|
||||
FallbackInvokable(final Object value) { mValue = value; }
|
||||
FallbackInvokable(final @Nullable Object value) { mValue = value; }
|
||||
|
||||
@Override public Object invoke(final C target, final Object[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
private final Object mValue;
|
||||
@Override public Class<?> getReturnType() {
|
||||
return mValue == null ? Object.class : mValue.getClass();
|
||||
}
|
||||
|
||||
private final @Nullable Object mValue;
|
||||
}
|
||||
|
||||
public static class HackedClass<C> {
|
||||
|
Loading…
Reference in New Issue
Block a user