mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-25 11:51:15 +08:00
refactor n.e class to type util
This commit is contained in:
parent
f30e8ffd3a
commit
d91d972aa7
@ -20,7 +20,7 @@ import static com.alibaba.testable.constant.Const.SYS_CLASSES;
|
||||
public class TestableClassTransformer implements Opcodes {
|
||||
|
||||
private static final String CONSTRUCTOR = "<init>";
|
||||
private static final String TESTABLE_NE = "testable_internal/n/e";
|
||||
private static final String TESTABLE_NE = "n/e";
|
||||
private final ClassNode cn = new ClassNode();
|
||||
|
||||
public TestableClassTransformer(String className) throws IOException {
|
||||
|
@ -33,8 +33,8 @@ public class PrivateAccessor {
|
||||
|
||||
public static <T> T invoke(Object ref, String method, Object... args) {
|
||||
try {
|
||||
Class[] cls = TypeUtil.gcs(args);
|
||||
Method declaredMethod = TypeUtil.gm(ref.getClass().getDeclaredMethods(), method, cls);
|
||||
Class[] cls = TypeUtil.getClassesFromObjects(args);
|
||||
Method declaredMethod = TypeUtil.getMethodByNameAndParameterTypes(ref.getClass().getDeclaredMethods(), method, cls);
|
||||
declaredMethod.setAccessible(true);
|
||||
return (T)declaredMethod.invoke(ref, args);
|
||||
} catch (Exception e) {
|
||||
|
@ -50,9 +50,9 @@ public class TestSetupMethodGenerator extends BaseGenerator {
|
||||
ListBuffer<JCStatement> statements = new ListBuffer<>();
|
||||
for (Pair<Name, Pair<JCExpression, List<JCExpression>>> m : injectMethods.toList()) {
|
||||
if (isMemberMethod(m)) {
|
||||
statements.append(addToPoolStatement(m, ConstPool.NE_ADD_F));
|
||||
statements.append(addToPoolStatement(m, ConstPool.METHOD_ADD_TO_MEM_POOL));
|
||||
} else {
|
||||
statements.append(addToPoolStatement(m, ConstPool.NE_ADD_W));
|
||||
statements.append(addToPoolStatement(m, ConstPool.METHOD_ADD_TO_CON_POLL));
|
||||
}
|
||||
}
|
||||
if (!testSetupMethodName.isEmpty()) {
|
||||
@ -86,7 +86,7 @@ public class TestSetupMethodGenerator extends BaseGenerator {
|
||||
}
|
||||
|
||||
private JCStatement addToPoolStatement(Pair<Name, Pair<JCExpression, List<JCExpression>>> m, String addPoolMethod) {
|
||||
JCExpression pool = nameToExpression(ConstPool.NE_POOL);
|
||||
JCExpression pool = nameToExpression(ConstPool.CLASS_SUBSTITUTION);
|
||||
JCExpression classType = m.snd.fst;
|
||||
JCExpression methodName = cx.treeMaker.Literal(m.fst.toString());
|
||||
JCExpression parameterTypes = cx.treeMaker.NewArray(cx.treeMaker.Ident(cx.names.fromString(TYPE_CLASS)),
|
||||
|
@ -5,14 +5,14 @@ package com.alibaba.testable.util;
|
||||
*/
|
||||
public final class ConstPool {
|
||||
|
||||
public static final String NE_PKG = "testable_internal.n";
|
||||
public static final String NE_PKG = "n";
|
||||
public static final String NE_CLS = "e";
|
||||
public static final String NE_NEW = "w";
|
||||
public static final String NE_FUN = "f";
|
||||
public static final String NE_PKG_CLS = NE_PKG + "." + NE_CLS;
|
||||
public static final String NE_POOL = NE_PKG_CLS + ".p";
|
||||
public static final String NE_ADD_W = NE_PKG_CLS + ".aw";
|
||||
public static final String NE_ADD_F = NE_PKG_CLS + ".af";
|
||||
public static final String TYPE_UTIL = "com.alibaba.testable.util.TypeUtil";
|
||||
public static final String CLASS_SUBSTITUTION = TYPE_UTIL + ".TestableSubstitution";
|
||||
public static final String METHOD_ADD_TO_CON_POLL = TYPE_UTIL + ".addToConstructorPool";
|
||||
public static final String METHOD_ADD_TO_MEM_POOL = TYPE_UTIL + ".addToMemberMethodPool";
|
||||
public static final String TYPE_TO_CLASS = "class";
|
||||
public static final String REF_THIS = "this";
|
||||
public static final String VOID = "void";
|
||||
|
@ -2,16 +2,136 @@ package com.alibaba.testable.util;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class TypeUtil {
|
||||
|
||||
/**
|
||||
* Information of substitution method
|
||||
*/
|
||||
public static class TestableSubstitution {
|
||||
public Class type; // target instance type to new / method return type
|
||||
public Class[] parameterTypes; // constructor parameter types / member method parameter types
|
||||
public Object targetObject; // object which provides substitution / object which provides substitution
|
||||
public String methodName; // substitution method name / original member method name
|
||||
|
||||
public TestableSubstitution(Class type, String methodName, Class[] parameterTypes, Object targetObject) {
|
||||
this.type = type;
|
||||
this.methodName = methodName;
|
||||
this.parameterTypes = parameterTypes;
|
||||
this.targetObject = targetObject;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TestableSubstitution> mockNewPool = new ArrayList<>();
|
||||
private static List<TestableSubstitution> mockMemberPool = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* add item to constructor pool
|
||||
*/
|
||||
public static void addToConstructorPool(TestableSubstitution np) {
|
||||
mockNewPool.add(np);
|
||||
}
|
||||
|
||||
/**
|
||||
* add item to method pool
|
||||
*/
|
||||
public static void addToMemberMethodPool(TestableSubstitution np) {
|
||||
mockMemberPool.add(np);
|
||||
}
|
||||
|
||||
/**
|
||||
* substitution entry for new
|
||||
*/
|
||||
public static <T> T wrapNew(Class<T> ct, Object... as) {
|
||||
Class[] cs = TypeUtil.getClassesFromObjects(as);
|
||||
if (!mockNewPool.isEmpty()) {
|
||||
try {
|
||||
TestableSubstitution pi = getFromConstructorPool(ct, cs);
|
||||
if (pi != null) {
|
||||
Method m = pi.targetObject.getClass().getDeclaredMethod(pi.methodName, pi.parameterTypes);
|
||||
m.setAccessible(true);
|
||||
return (T)m.invoke(pi.targetObject, as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Constructor c = TypeUtil.getConstructorByParameterTypes(ct.getConstructors(), cs);
|
||||
if (c != null) {
|
||||
return (T)c.newInstance(as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* substitution entry for member call
|
||||
*/
|
||||
public static <T> T wrapCall(Object obj, String mn, Object... as) {
|
||||
Class[] cs = TypeUtil.getClassesFromObjects(as);
|
||||
if (!mockMemberPool.isEmpty()) {
|
||||
try {
|
||||
TestableSubstitution pi = getFromMemberMethodPool(mn, cs);
|
||||
if (pi != null) {
|
||||
Method m = pi.targetObject.getClass().getDeclaredMethod(pi.methodName, pi.parameterTypes);
|
||||
m.setAccessible(true);
|
||||
return (T)m.invoke(pi.targetObject, as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Method m = TypeUtil.getMethodByNameAndParameterTypes(obj.getClass().getDeclaredMethods(), mn, cs);
|
||||
if (m != null) {
|
||||
m.setAccessible(true);
|
||||
return (T)m.invoke(obj, as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get from method pool by key
|
||||
*/
|
||||
private static TestableSubstitution getFromMemberMethodPool(String methodName, Class[] parameterTypes) {
|
||||
for (TestableSubstitution f : mockMemberPool) {
|
||||
if (f.methodName.equals(methodName) && TypeUtil.typeEquals(f.parameterTypes, parameterTypes)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get from constructor pool by key
|
||||
*/
|
||||
private static TestableSubstitution getFromConstructorPool(Class type, Class[] parameterTypes) {
|
||||
for (TestableSubstitution w : mockNewPool) {
|
||||
if (w.type.equals(type) && TypeUtil.typeEquals(w.parameterTypes, parameterTypes)) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get classes of parameter objects
|
||||
*/
|
||||
public static Class[] gcs(Object[] as) {
|
||||
Class[] cs = new Class[as.length];
|
||||
public static Class[] getClassesFromObjects(Object[] parameterObjects) {
|
||||
Class[] cs = new Class[parameterObjects.length];
|
||||
for (int i = 0; i < cs.length; i++) {
|
||||
cs[i] = as[i].getClass();
|
||||
cs[i] = parameterObjects[i].getClass();
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
@ -19,9 +139,12 @@ public class TypeUtil {
|
||||
/**
|
||||
* get method by name and parameter matching
|
||||
*/
|
||||
public static Method gm(Method[] mds, String mn, Class[] cs) {
|
||||
for (Method m : mds) {
|
||||
if (m.getName().equals(mn) && te(m.getParameterTypes(), cs)) {
|
||||
public static Method getMethodByNameAndParameterTypes(Method[] availableMethods,
|
||||
String methodName,
|
||||
Class[] parameterTypes) {
|
||||
for (Method m : availableMethods) {
|
||||
if (m.getName().equals(methodName) &&
|
||||
typeEquals(m.getParameterTypes(), parameterTypes)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
@ -31,9 +154,10 @@ public class TypeUtil {
|
||||
/**
|
||||
* get constructor by parameter matching
|
||||
*/
|
||||
public static Constructor gc(Constructor<?>[] cons, Class[] cs) {
|
||||
for (Constructor c : cons) {
|
||||
if (te(c.getParameterTypes(), cs)) {
|
||||
public static Constructor getConstructorByParameterTypes(Constructor<?>[] constructors,
|
||||
Class[] parameterTypes) {
|
||||
for (Constructor c : constructors) {
|
||||
if (typeEquals(c.getParameterTypes(), parameterTypes)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@ -43,12 +167,13 @@ public class TypeUtil {
|
||||
/**
|
||||
* type equeals
|
||||
*/
|
||||
public static boolean te(Class[] c1, Class[] c2) {
|
||||
if (c1.length != c2.length) {
|
||||
public static boolean typeEquals(Class[] classesLeft, Class[] classesRight) {
|
||||
if (classesLeft.length != classesRight.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < c1.length; i++) {
|
||||
if (!c1[i].equals(c2[i]) && !fe(c1[i], c2[i])) {
|
||||
for (int i = 0; i < classesLeft.length; i++) {
|
||||
if (!classesLeft[i].equals(classesRight[i]) &&
|
||||
!fuzzyEqual(classesLeft[i], classesRight[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -57,18 +182,18 @@ public class TypeUtil {
|
||||
|
||||
/**
|
||||
* fuzzy equal
|
||||
* @param c1 fact types (can be primary type)
|
||||
* @param c2 user types
|
||||
* @param factTypes fact types (can be primary type)
|
||||
* @param userTypes user types
|
||||
*/
|
||||
private static boolean fe(Class c1, Class c2) {
|
||||
return (c1.equals(int.class) && c2.equals(Integer.class)) ||
|
||||
(c1.equals(long.class) && c2.equals(Long.class)) ||
|
||||
(c1.equals(short.class) && c2.equals(Short.class)) ||
|
||||
(c1.equals(boolean.class) && c2.equals(Boolean.class)) ||
|
||||
(c1.equals(char.class) && c2.equals(Character.class)) ||
|
||||
(c1.equals(byte.class) && c2.equals(Byte.class)) ||
|
||||
(c1.equals(float.class) && c2.equals(Float.class)) ||
|
||||
(c1.equals(double.class) && c2.equals(Double.class));
|
||||
private static boolean fuzzyEqual(Class factTypes, Class userTypes) {
|
||||
return (factTypes.equals(int.class) && userTypes.equals(Integer.class)) ||
|
||||
(factTypes.equals(long.class) && userTypes.equals(Long.class)) ||
|
||||
(factTypes.equals(short.class) && userTypes.equals(Short.class)) ||
|
||||
(factTypes.equals(boolean.class) && userTypes.equals(Boolean.class)) ||
|
||||
(factTypes.equals(char.class) && userTypes.equals(Character.class)) ||
|
||||
(factTypes.equals(byte.class) && userTypes.equals(Byte.class)) ||
|
||||
(factTypes.equals(float.class) && userTypes.equals(Float.class)) ||
|
||||
(factTypes.equals(double.class) && userTypes.equals(Double.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
64
core/src/main/java/n/e.java
Normal file
64
core/src/main/java/n/e.java
Normal file
@ -0,0 +1,64 @@
|
||||
package n;
|
||||
|
||||
import static com.alibaba.testable.util.TypeUtil.wrapNew;
|
||||
|
||||
public final class e {
|
||||
|
||||
public static <T> T w(Class<T> ct) {
|
||||
return wrapNew(ct);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1) {
|
||||
return wrapNew(ct, a1);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2) {
|
||||
return wrapNew(ct, a1, a2);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3) {
|
||||
return wrapNew(ct, a1, a2, a3);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4) {
|
||||
return wrapNew(ct, a1, a2, a3, a4);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5, a6);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5, a6, a7);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5, a6, a7, a8);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9, Object a10) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9, Object a10, Object a11) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9, Object a10, Object a11, Object a12) {
|
||||
return wrapNew(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
|
||||
}
|
||||
|
||||
}
|
@ -1,182 +0,0 @@
|
||||
package testable_internal.n;
|
||||
|
||||
import com.alibaba.testable.util.TypeUtil;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.Object;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
public final class e {
|
||||
|
||||
public static class p {
|
||||
public Class c; // target instance type to new / method return type
|
||||
public Class[] a; // constructor parameter types / member method parameter types
|
||||
public Object o; // object which provides substitution / object which provides substitution
|
||||
public String m; // substitutional method name / original member method name
|
||||
|
||||
public p(Class c, String m, Class[] a, Object o) {
|
||||
this.c = c;
|
||||
this.m = m;
|
||||
this.a = a;
|
||||
this.o = o;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<p> pw = new ArrayList<>();
|
||||
private static List<p> pf = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* add item to constructor pool
|
||||
*/
|
||||
public static void aw(p np) {
|
||||
pw.add(np);
|
||||
}
|
||||
|
||||
/**
|
||||
* add item to method pool
|
||||
*/
|
||||
public static void af(p np) {
|
||||
pf.add(np);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct) {
|
||||
return wi(ct);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1) {
|
||||
return wi(ct, a1);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2) {
|
||||
return wi(ct, a1, a2);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3) {
|
||||
return wi(ct, a1, a2, a3);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4) {
|
||||
return wi(ct, a1, a2, a3, a4);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5) {
|
||||
return wi(ct, a1, a2, a3, a4, a5);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6) {
|
||||
return wi(ct, a1, a2, a3, a4, a5, a6);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) {
|
||||
return wi(ct, a1, a2, a3, a4, a5, a6, a7);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8) {
|
||||
return wi(ct, a1, a2, a3, a4, a5, a6, a7, a8);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9) {
|
||||
return wi(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9, Object a10) {
|
||||
return wi(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9, Object a10, Object a11) {
|
||||
return wi(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
|
||||
}
|
||||
|
||||
public static <T> T w(Class<T> ct, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
|
||||
Object a8, Object a9, Object a10, Object a11, Object a12) {
|
||||
return wi(ct, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
|
||||
}
|
||||
|
||||
/**
|
||||
* substitution entry for new
|
||||
*/
|
||||
public static <T> T wi(Class<T> ct, Object... as) {
|
||||
Class[] cs = TypeUtil.gcs(as);
|
||||
if (!pw.isEmpty()) {
|
||||
try {
|
||||
p pi = gpw(ct, cs);
|
||||
if (pi != null) {
|
||||
Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a);
|
||||
m.setAccessible(true);
|
||||
return (T)m.invoke(pi.o, as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Constructor c = TypeUtil.gc(ct.getConstructors(), cs);
|
||||
if (c != null) {
|
||||
return (T)c.newInstance(as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* substitution entry for member call
|
||||
*/
|
||||
public static <T> T f(Object obj, String mn, Object... as) {
|
||||
Class[] cs = TypeUtil.gcs(as);
|
||||
if (!pf.isEmpty()) {
|
||||
try {
|
||||
p pi = gpf(mn, cs);
|
||||
if (pi != null) {
|
||||
Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a);
|
||||
m.setAccessible(true);
|
||||
return (T)m.invoke(pi.o, as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Method m = TypeUtil.gm(obj.getClass().getDeclaredMethods(), mn, cs);
|
||||
if (m != null) {
|
||||
m.setAccessible(true);
|
||||
return (T)m.invoke(obj, as);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get from method pool by key
|
||||
*/
|
||||
private static p gpf(String mn, Class[] cs) {
|
||||
for (p f : pf) {
|
||||
if (f.m.equals(mn) && TypeUtil.te(f.a, cs)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get from constructor pool by key
|
||||
*/
|
||||
private static p gpw(Class ct, Class[] cs) {
|
||||
for (p w : pw) {
|
||||
if (w.c.equals(ct) && TypeUtil.te(w.a, cs)) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user