001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.web.directive; 017 018import com.jfinal.kit.LogKit; 019import com.jfinal.template.expr.ast.MethodKeyBuilder; 020import io.jboot.app.JdkUtil; 021import javassist.ClassPool; 022import javassist.CtClass; 023import javassist.CtMethod; 024import javassist.NotFoundException; 025 026import java.lang.reflect.Method; 027import java.lang.reflect.Modifier; 028import java.util.Arrays; 029import java.util.HashMap; 030import java.util.LinkedHashMap; 031import java.util.Map; 032 033/** 034 * JFinalEnumObject 的主要目的是为了动态创建一个包装 Enum 枚举的类,方便模板引擎直接调用。 035 * <p> 036 * 添加枚举类型,便于在模板中使用 037 * 038 * <pre> 039 * 例子: 040 * 1:定义枚举类型 041 * 042 * @JFinalSharedEnum 043 * public enum UserType { 044 * 045 * ADMIN(1,"管理员"), 046 * USER(2,"用户"); 047 * 048 * private int value; 049 * private String text; 050 * 051 * UserType(int value, String text) { 052 * this.value = value; 053 * this.text = text; 054 * } 055 * 056 * 057 * public static String text(Integer value) { 058 * for (UserType type : values()) { 059 * if (type.value == value) { 060 * return type.text; 061 * } 062 * } 063 * return null 064 * } 065 * 066 * } 067 * 068 * 069 * 2:模板中使用 070 * ### 以下的对象 u 通过 Controller 中的 setAttr("u", UserType.ADMIN) 传递 071 * 072 * #if( u == UserType.ADMIN ) 073 * #(UserType.ADMIN) 074 * 075 * #(UserType.ADMIN.name()) 076 * 077 * #(UserType.ADMIN.hello()) 078 * #end 079 * 080 * 或者 081 * 082 * #(UserType.text(1)) 083 * 084 * </pre> 085 */ 086public class SharedEnumObject extends LinkedHashMap<String, Object> { 087 088 089 private Class<? extends Enum<?>> enumClass; 090 private Map<Long, Method> staticMethods; 091 092 093 void init(Class<? extends Enum<?>> enumClass, Map<Long, Method> staticMethods) { 094 this.enumClass = enumClass; 095 this.staticMethods = staticMethods; 096 for (Enum<?> e : enumClass.getEnumConstants()) { 097 put(e.name(), e); 098 } 099 } 100 101 102 protected Object invokeEnumMethod(String methodName) { 103 return doInvokeEnumMethod(methodName); 104 } 105 106 protected Object invokeEnumMethod(String methodName, Object para1) { 107 return doInvokeEnumMethod(methodName, para1); 108 } 109 110 111 protected Object invokeEnumMethod(String methodName, Object para1, Object para2) { 112 return doInvokeEnumMethod(methodName, para1, para2); 113 } 114 115 protected Object invokeEnumMethod(String methodName, Object para1, Object para2, Object para3) { 116 return doInvokeEnumMethod(methodName, para1, para2, para3); 117 } 118 119 protected Object invokeEnumMethod(String methodName, Object para1, Object para2, Object para3, Object para4) { 120 return doInvokeEnumMethod(methodName, para1, para2, para3, para4); 121 } 122 123 protected Object invokeEnumMethod(String methodName, Object para1, Object para2, Object para3, Object para4, Object para5) { 124 return doInvokeEnumMethod(methodName, para1, para2, para3, para4, para5); 125 } 126 127 private Object doInvokeEnumMethod(String methodName, Object... paras) { 128 Method method = findMethod(methodName, paras); 129 if (method == null) { 130 throw new RuntimeException("Can not find the method: " + methodName + " with paras: " + Arrays.toString(paras) + " in enum: " + this.enumClass); 131 } 132 try { 133 return method.invoke(null, paras); 134 } catch (Exception ex) { 135 throw new RuntimeException(ex.toString(), ex); 136 } 137 } 138 139 140// public Object value(Object text){ 141// return invokeEnumMethod("value",text); 142// } 143 144 public Object invoke(String method, Object... paras) { 145 return doInvokeEnumMethod(method, paras); 146 } 147 148 149 public static SharedEnumObject create(Class<? extends Enum<?>> enumClass) { 150 try { 151 ClassPool pool = ClassPool.getDefault(); 152 CtClass objectCtClass = pool.getCtClass(Object.class.getName()); 153 CtClass supperClass = pool.get(SharedEnumObject.class.getName()); 154 155 CtClass newClass = pool.makeClass(SharedEnumObject.class.getName() + "_$$_" + enumClass.getSimpleName()); 156 newClass.setSuperclass(supperClass); 157 newClass.setModifiers(Modifier.PUBLIC); 158 159 Map<Long, Method> enumStaticMethods = findEnumStaticMethods(enumClass); 160 161 if (enumStaticMethods != null) { 162 for (Method originalMethod : enumStaticMethods.values()) { 163 boolean isReturnVoid = (void.class == originalMethod.getReturnType()); 164 CtClass returnClass = isReturnVoid ? CtClass.voidType : objectCtClass; 165 166 CtClass[] parameterClassArray = createParameterClassArray(originalMethod, pool); 167 CtMethod ctMethod = new CtMethod(returnClass, originalMethod.getName(), parameterClassArray, newClass); 168 ctMethod.setModifiers(Modifier.PUBLIC); 169 170 if (isReturnVoid) { 171 ctMethod.setBody("{invokeEnumMethod(\"" + originalMethod.getName() + "\",$$);}"); 172 } else { 173 ctMethod.setBody("{return invokeEnumMethod(\"" + originalMethod.getName() + "\",$$);}"); 174 } 175 176 newClass.addMethod(ctMethod); 177 } 178 } 179 180 // jdk 17 181 // toClass() must add neighbor class in jdk17 182 // neighbor: A class belonging to the same package that this class belongs to 183 184 SharedEnumObject ret = JdkUtil.isJdk1x() ? (SharedEnumObject) newClass.toClass().newInstance() 185 : (SharedEnumObject) newClass.toClass(SharedEnumObject.class).newInstance(); 186 ret.init(enumClass, enumStaticMethods); 187 return ret; 188 } catch (Exception e) { 189 e.printStackTrace(); 190 } 191 192 return null; 193 } 194 195 196 private static CtClass[] createParameterClassArray(Method originalMethod, ClassPool pool) throws NotFoundException { 197 if (originalMethod.getParameterCount() == 0) { 198 return new CtClass[0]; 199 } 200 CtClass[] ret = new CtClass[originalMethod.getParameterCount()]; 201 int index = 0; 202 for (Class<?> clazz : originalMethod.getParameterTypes()) { 203 ret[index++] = pool.getCtClass(clazz.getName()); 204 } 205 return ret; 206 } 207 208 209 private static Map<Long, Method> findEnumStaticMethods(Class<? extends Enum<?>> enumClass) { 210 Map<Long, Method> retMap = null; 211 try { 212 Method[] methods = enumClass.getDeclaredMethods(); 213 for (Method method : methods) { 214 int methodModifiers = method.getModifiers(); 215 if (Modifier.isPublic(methodModifiers) && Modifier.isStatic(methodModifiers)) { 216 if (retMap == null) { 217 retMap = new HashMap<>(); 218 } 219 retMap.put(getMethodKey(enumClass, method.getName(), method.getParameterTypes()), method); 220 } 221 } 222 } catch (Exception ex) { 223 LogKit.logNothing(ex); 224 } 225 return retMap; 226 } 227 228 229 private Method findMethod(String methodName, Object... args) { 230 Class<?>[] argTypes = null; 231 if (args != null && args.length > 0) { 232 argTypes = new Class[args.length]; 233 int index = 0; 234 for (Object arg : args) { 235 argTypes[index++] = arg != null ? arg.getClass() : null; 236 } 237 } 238 239 long key = getMethodKey(this.enumClass, methodName, argTypes); 240 Method method = this.staticMethods.get(key); 241 if (method != null) { 242 return method; 243 } 244 245 for (Method m : this.staticMethods.values()) { 246 if (m.getName().equals(methodName) && isMatchParas(m.getParameterTypes(), args)) { 247 this.staticMethods.put(key, m); 248 return m; 249 } 250 } 251 252 return null; 253 } 254 255 private boolean isMatchParas(Class<?>[] parameterTypes, Object[] args) { 256 if (args == null || args.length == 0) { 257 return parameterTypes.length == 0; 258 } 259 260 if (parameterTypes.length != args.length) { 261 return false; 262 } 263 264 for (int i = 0; i < parameterTypes.length; i++) { 265 if (args[i] != null && !parameterTypes[i].isAssignableFrom(args[i].getClass())) { 266 return false; 267 } 268 } 269 return true; 270 } 271 272 273 private static Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) { 274 return MethodKeyBuilder.getInstance().getMethodKey(targetClass, methodName, argTypes); 275 } 276 277 278}