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}