JavaReflection.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. package org.ssssssss.magicapi.expression.interpreter;
  2. import java.lang.reflect.Field;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Modifier;
  5. import java.util.*;
  6. import java.util.concurrent.ConcurrentHashMap;
  7. public class JavaReflection extends AbstractReflection {
  8. private final Map<Class<?>, Map<String, Field>> fieldCache = new ConcurrentHashMap<Class<?>, Map<String, Field>>();
  9. private final Map<Class<?>, Map<MethodSignature, Method>> methodCache = new ConcurrentHashMap<Class<?>, Map<MethodSignature, Method>>();
  10. private final Map<Class<?>, Map<String,List<Method>>> extensionmethodCache = new ConcurrentHashMap<>();
  11. @SuppressWarnings("rawtypes")
  12. @Override
  13. public Object getField (Object obj, String name) {
  14. Class cls = obj instanceof Class ? (Class)obj : obj.getClass();
  15. Map<String, Field> fields = fieldCache.get(cls);
  16. if (fields == null) {
  17. fields = new ConcurrentHashMap<String, Field>();
  18. fieldCache.put(cls, fields);
  19. }
  20. Field field = fields.get(name);
  21. if (field == null) {
  22. try {
  23. field = cls.getDeclaredField(name);
  24. field.setAccessible(true);
  25. fields.put(name, field);
  26. } catch (Throwable t) {
  27. // fall through, try super classes
  28. }
  29. if (field == null) {
  30. Class parentClass = cls.getSuperclass();
  31. while (parentClass != Object.class && parentClass != null) {
  32. try {
  33. field = parentClass.getDeclaredField(name);
  34. field.setAccessible(true);
  35. fields.put(name, field);
  36. } catch (NoSuchFieldException e) {
  37. // fall through
  38. }
  39. parentClass = parentClass.getSuperclass();
  40. }
  41. }
  42. }
  43. return field;
  44. }
  45. @Override
  46. public Object getFieldValue (Object obj, Object field) {
  47. Field javaField = (Field)field;
  48. try {
  49. return javaField.get(obj);
  50. } catch (Throwable e) {
  51. throw new RuntimeException("Couldn't get value of field '" + javaField.getName() + "' from object of type '" + obj.getClass().getSimpleName() + "'");
  52. }
  53. }
  54. @Override
  55. public void registerExtensionClass(Class<?> target,Class<?> clazz){
  56. Method[] methods = clazz.getDeclaredMethods();
  57. if(methods != null){
  58. Map<String, List<Method>> cachedMethodMap = extensionmethodCache.get(target);
  59. if(cachedMethodMap == null){
  60. cachedMethodMap = new HashMap<>();
  61. extensionmethodCache.put(target,cachedMethodMap);
  62. }
  63. for (Method method : methods) {
  64. if(Modifier.isStatic(method.getModifiers()) && method.getParameterCount() > 0){
  65. List<Method> cachedList = cachedMethodMap.get(method.getName());
  66. if(cachedList == null){
  67. cachedList = new ArrayList<>();
  68. cachedMethodMap.put(method.getName(), cachedList);
  69. }
  70. cachedList.add(method);
  71. }
  72. }
  73. }
  74. }
  75. @Override
  76. public Object getExtensionMethod(Object obj, String name, Object... arguments) {
  77. Class<?> cls = obj instanceof Class ? (Class<?>)obj : obj.getClass();
  78. if(cls.isArray()){
  79. cls = Object[].class;
  80. }
  81. return getExtensionMethod(cls,name,arguments);
  82. }
  83. private Object getExtensionMethod(Class<?> cls, String name, Object... arguments) {
  84. if(cls == null){
  85. cls = Object.class;
  86. }
  87. Map<String, List<Method>> methodMap = extensionmethodCache.get(cls);
  88. if(methodMap != null){
  89. List<Method> methodList = methodMap.get(name);
  90. if(methodList != null){
  91. Class<?>[] parameterTypes = new Class[arguments.length + 1];
  92. parameterTypes[0] = cls;
  93. for (int i = 0; i < arguments.length; i++) {
  94. parameterTypes[i + 1] = arguments[i] == null ? null : arguments[i].getClass();
  95. }
  96. return findMethod(methodList, parameterTypes);
  97. }
  98. }
  99. if(cls != Object.class){
  100. Class<?>[] interfaces = cls.getInterfaces();
  101. if(interfaces != null){
  102. for (Class<?> clazz : interfaces) {
  103. Object method = getExtensionMethod(clazz,name,arguments);
  104. if(method != null){
  105. return method;
  106. }
  107. }
  108. }
  109. return getExtensionMethod(cls.getSuperclass(),name,arguments);
  110. }
  111. return null;
  112. }
  113. @Override
  114. public Object getMethod (Object obj, String name, Object... arguments) {
  115. Class<?> cls = obj instanceof Class ? (Class<?>)obj : obj.getClass();
  116. Map<MethodSignature, Method> methods = methodCache.get(cls);
  117. if (methods == null) {
  118. methods = new ConcurrentHashMap<MethodSignature, Method>();
  119. methodCache.put(cls, methods);
  120. }
  121. Class<?>[] parameterTypes = new Class[arguments.length];
  122. for (int i = 0; i < arguments.length; i++) {
  123. parameterTypes[i] = arguments[i] == null ? null : arguments[i].getClass();
  124. }
  125. JavaReflection.MethodSignature signature = new MethodSignature(name, parameterTypes);
  126. Method method = methods.get(signature);
  127. if (method == null) {
  128. try {
  129. if (name == null) {
  130. method = findApply(cls);
  131. } else {
  132. method = findMethod(cls, name, parameterTypes);
  133. if(method == null && parameterTypes != null){
  134. method = findMethod(cls, name, new Class<?>[]{Object[].class});
  135. }
  136. }
  137. method.setAccessible(true);
  138. methods.put(signature, method);
  139. } catch (Throwable e) {
  140. // fall through
  141. }
  142. if (method == null) {
  143. Class<?> parentClass = cls.getSuperclass();
  144. while (parentClass != Object.class && parentClass != null) {
  145. try {
  146. if (name == null) {
  147. method = findApply(parentClass);
  148. } else {
  149. method = findMethod(parentClass, name, parameterTypes);
  150. }
  151. method.setAccessible(true);
  152. methods.put(signature, method);
  153. } catch (Throwable e) {
  154. // fall through
  155. }
  156. parentClass = parentClass.getSuperclass();
  157. }
  158. }
  159. }
  160. return method;
  161. }
  162. /** Returns the <code>apply()</code> method of a functional interface. **/
  163. private static Method findApply (Class<?> cls) {
  164. for (Method method : cls.getDeclaredMethods()) {
  165. if ("apply".equals(method.getName())) {
  166. return method;
  167. }
  168. }
  169. return null;
  170. }
  171. private static Method findMethod (List<Method> methods, Class<?>[] parameterTypes) {
  172. Method foundMethod = null;
  173. int foundScore = 0;
  174. for (Method method : methods) {
  175. // Check if the types match.
  176. Class<?>[] otherTypes = method.getParameterTypes();
  177. if(parameterTypes.length != otherTypes.length){
  178. continue;
  179. }
  180. boolean match = true;
  181. int score = 0;
  182. for (int ii = 0, nn = parameterTypes.length; ii < nn; ii++) {
  183. Class<?> type = parameterTypes[ii];
  184. Class<?> otherType = otherTypes[ii];
  185. if (!otherType.isAssignableFrom(type)) {
  186. score++;
  187. if (!isPrimitiveAssignableFrom(type, otherType)) {
  188. score++;
  189. if (!isCoercible(type, otherType)) {
  190. match = false;
  191. break;
  192. } else {
  193. score++;
  194. }
  195. }
  196. }else if(type == null && otherType.isPrimitive()){
  197. match = false;
  198. break;
  199. }
  200. }
  201. if (match) {
  202. if (foundMethod == null) {
  203. foundMethod = method;
  204. foundScore = score;
  205. } else {
  206. if (score < foundScore) {
  207. foundScore = score;
  208. foundMethod = method;
  209. }
  210. }
  211. }
  212. }
  213. return foundMethod;
  214. }
  215. /** Returns the method best matching the given signature, including type coercion, or null. **/
  216. private static Method findMethod (Class<?> cls, String name, Class<?>[] parameterTypes) {
  217. Method[] methods = cls.getDeclaredMethods();
  218. List<Method> methodList = new ArrayList<>();
  219. for (int i = 0, n = methods.length; i < n; i++) {
  220. Method method = methods[i];
  221. // if neither name or parameter list size match, bail on this method
  222. if (!method.getName().equals(name)) {
  223. continue;
  224. }
  225. if (method.getParameterTypes().length != parameterTypes.length) {
  226. continue;
  227. }
  228. methodList.add(method);
  229. }
  230. return findMethod(methodList,parameterTypes);
  231. }
  232. /** Returns whether the from type can be assigned to the to type, assuming either type is a (boxed) primitive type. We can
  233. * relax the type constraint a little, as we'll invoke a method via reflection. That means the from type will always be boxed,
  234. * as the {@link Method#invoke(Object, Object...)} method takes objects. **/
  235. private static boolean isPrimitiveAssignableFrom (Class<?> from, Class<?> to) {
  236. if ((from == Boolean.class || from == boolean.class) && (to == boolean.class || to == Boolean.class)) {
  237. return true;
  238. }
  239. if ((from == Integer.class || from == int.class) && (to == int.class || to == Integer.class)) {
  240. return true;
  241. }
  242. if ((from == Float.class || from == float.class) && (to == float.class || to == Float.class)) {
  243. return true;
  244. }
  245. if ((from == Double.class || from == double.class) && (to == double.class || to == Double.class)) {
  246. return true;
  247. }
  248. if ((from == Byte.class || from == byte.class) && (to == byte.class || to == Byte.class)) {
  249. return true;
  250. }
  251. if ((from == Short.class || from == short.class) && (to == short.class || to == Short.class)) {
  252. return true;
  253. }
  254. if ((from == Long.class || from == long.class) && (to == long.class || to == Long.class)) {
  255. return true;
  256. }
  257. if ((from == Character.class || from == char.class) && (to == char.class || to == Character.class)) {
  258. return true;
  259. }
  260. return false;
  261. }
  262. public static String[] getStringTypes(Object[] objects){
  263. String[] parameterTypes = new String[objects == null ? 0: objects.length];
  264. if(objects != null){
  265. for(int i=0,len = objects.length;i<len;i++){
  266. Object value = objects[i];
  267. parameterTypes[i] = value == null ? "null" : value.getClass().getSimpleName();
  268. }
  269. }
  270. return parameterTypes;
  271. }
  272. /** Returns whether the from type can be coerced to the to type. The coercion rules follow those of Java. See JLS 5.1.2
  273. * https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html **/
  274. private static boolean isCoercible (Class<?> from, Class<?> to) {
  275. if (from == Integer.class || from == int.class) {
  276. return to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class || to == Long.class;
  277. }
  278. if (from == Float.class || from == float.class) {
  279. return to == double.class || to == Double.class;
  280. }
  281. if (from == Double.class || from == double.class) {
  282. return false;
  283. }
  284. if (from == Character.class || from == char.class) {
  285. return to == int.class || to == Integer.class || to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class
  286. || to == Long.class;
  287. }
  288. if (from == Byte.class || from == byte.class) {
  289. return to == int.class || to == Integer.class || to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class
  290. || to == Long.class || to == short.class || to == Short.class;
  291. }
  292. if (from == Short.class || from == short.class) {
  293. return to == int.class || to == Integer.class || to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class
  294. || to == Long.class;
  295. }
  296. if (from == Long.class || from == long.class) {
  297. return to == float.class || to == Float.class || to == double.class || to == Double.class;
  298. }
  299. if(from == int[].class || from == Integer[].class){
  300. return to == Object[].class || to == float[].class || to == Float[].class || to == double[].class || to == Double[].class || to == long[].class || to == Long[].class;
  301. }
  302. return false;
  303. }
  304. @Override
  305. public Object callMethod (Object obj, Object method, Object... arguments) {
  306. Method javaMethod = (Method)method;
  307. try {
  308. return javaMethod.invoke(obj, arguments);
  309. } catch (Throwable t) {
  310. throw new RuntimeException("Couldn't call method '" + javaMethod.getName() + "' with arguments '" + Arrays.toString(arguments)
  311. + "' on object of type '" + obj.getClass().getSimpleName() + "'.", t);
  312. }
  313. }
  314. private static class MethodSignature {
  315. private final String name;
  316. @SuppressWarnings("rawtypes") private final Class[] parameters;
  317. private final int hashCode;
  318. @SuppressWarnings("rawtypes")
  319. public MethodSignature (String name, Class[] parameters) {
  320. this.name = name;
  321. this.parameters = parameters;
  322. final int prime = 31;
  323. int hash = 1;
  324. hash = prime * hash + ((name == null) ? 0 : name.hashCode());
  325. hash = prime * hash + Arrays.hashCode(parameters);
  326. hashCode = hash;
  327. }
  328. @Override
  329. public int hashCode () {
  330. return hashCode;
  331. }
  332. @Override
  333. public boolean equals (Object obj) {
  334. if (this == obj) {
  335. return true;
  336. }
  337. if (obj == null) {
  338. return false;
  339. }
  340. if (getClass() != obj.getClass()) {
  341. return false;
  342. }
  343. JavaReflection.MethodSignature other = (JavaReflection.MethodSignature)obj;
  344. if (name == null) {
  345. if (other.name != null) {
  346. return false;
  347. }
  348. } else if (!name.equals(other.name)) {
  349. return false;
  350. }
  351. if (!Arrays.equals(parameters, other.parameters)) {
  352. return false;
  353. }
  354. return true;
  355. }
  356. }
  357. }