001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.transformer.bytecode.reflection;
020
021import java.io.ByteArrayInputStream;
022import java.io.File;
023import java.io.IOException;
024import java.lang.reflect.Constructor;
025import java.lang.reflect.Field;
026import java.lang.reflect.InvocationTargetException;
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.util.HashMap;
030import java.util.Map;
031
032import lucee.commons.io.IOUtil;
033import lucee.commons.io.res.Resource;
034import lucee.commons.io.res.ResourceProvider;
035import lucee.commons.io.res.ResourcesImpl;
036import lucee.commons.io.res.util.ResourceUtil;
037import lucee.commons.lang.ClassUtil;
038import lucee.commons.lang.ExtendableClassLoader;
039import lucee.commons.lang.PhysicalClassLoader;
040import lucee.commons.lang.StringUtil;
041import lucee.runtime.PageContext;
042import lucee.runtime.functions.arrays.ArrayNew;
043import lucee.runtime.op.Caster;
044import lucee.runtime.type.util.ArrayUtil;
045import lucee.transformer.bytecode.util.ASMConstants;
046import lucee.transformer.bytecode.util.ASMUtil;
047import lucee.transformer.bytecode.util.Types;
048
049import org.apache.commons.collections.map.ReferenceMap;
050import org.objectweb.asm.ClassWriter;
051import org.objectweb.asm.Label;
052import org.objectweb.asm.Opcodes;
053import org.objectweb.asm.Type;
054import org.objectweb.asm.commons.GeneratorAdapter;
055
056public class ASMProxyFactory {
057
058        public static final Type ASM_METHOD=Type.getType(ASMMethod.class);
059        public static final Type CLASS404=Type.getType(ClassNotFoundException.class);
060        public static final Type CLASS_UTIL=Type.getType(ClassUtil.class);
061        
062        
063        
064        //private static final org.objectweb.asm.commons.Method CONSTRUCTOR = 
065    //  new org.objectweb.asm.commons.Method("<init>",Types.VOID,new Type[]{Types.CLASS_LOADER,Types.CLASS});
066        private static final org.objectweb.asm.commons.Method CONSTRUCTOR = 
067        new org.objectweb.asm.commons.Method("<init>",Types.VOID,new Type[]{Types.CLASS,Types.CLASS_ARRAY});
068
069        private static final org.objectweb.asm.commons.Method LOAD_CLASS = new org.objectweb.asm.commons.Method(
070                        "loadClass",
071                        Types.CLASS,
072                        new Type[]{Types.STRING});
073        
074
075        // public static Class loadClass(String className, Class defaultValue) {
076        private static final org.objectweb.asm.commons.Method LOAD_CLASS_EL = new org.objectweb.asm.commons.Method(
077                        "loadClass",
078                        Types.CLASS,
079                        new Type[]{Types.STRING,Types.CLASS});
080        
081
082        // public String getName();
083        private static final org.objectweb.asm.commons.Method GET_NAME = new org.objectweb.asm.commons.Method(
084                        "getName",
085                        Types.STRING,
086                        new Type[]{});
087        
088        // public int getModifiers();
089        private static final org.objectweb.asm.commons.Method GET_MODIFIERS = new org.objectweb.asm.commons.Method(
090                        "getModifiers",
091                        Types.INT_VALUE,
092                        new Type[]{});
093
094        // public Class getReturnType();
095        private static final org.objectweb.asm.commons.Method GET_RETURN_TYPE_AS_STRING = new org.objectweb.asm.commons.Method(
096                        "getReturnTypeAsString",
097                        Types.STRING,
098                        new Type[]{});
099
100        
101        
102        
103        private static final org.objectweb.asm.commons.Method INVOKE = new org.objectweb.asm.commons.Method(
104                        "invoke",
105                        Types.OBJECT,
106                        new Type[]{Types.OBJECT,Types.OBJECT_ARRAY});
107        
108        
109        // primitive to reference type
110        private static final org.objectweb.asm.commons.Method BOOL_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.BOOLEAN,new Type[]{Types.BOOLEAN_VALUE});
111        private static final org.objectweb.asm.commons.Method SHORT_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.SHORT,new Type[]{Types.SHORT_VALUE});
112        private static final org.objectweb.asm.commons.Method INT_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.INTEGER,new Type[]{Types.INT_VALUE});
113        private static final org.objectweb.asm.commons.Method LONG_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.LONG,new Type[]{Types.LONG_VALUE});
114        private static final org.objectweb.asm.commons.Method FLT_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.FLOAT,new Type[]{Types.FLOAT_VALUE});
115        private static final org.objectweb.asm.commons.Method DBL_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.DOUBLE,new Type[]{Types.DOUBLE_VALUE});
116        private static final org.objectweb.asm.commons.Method CHR_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.CHARACTER,new Type[]{Types.CHARACTER});
117        private static final org.objectweb.asm.commons.Method BYT_VALUE_OF = new org.objectweb.asm.commons.Method("valueOf",Types.BYTE,new Type[]{Types.BYTE_VALUE});
118        
119        // reference type to primitive
120        private static final org.objectweb.asm.commons.Method BOOL_VALUE = new org.objectweb.asm.commons.Method("booleanValue",Types.BOOLEAN_VALUE,new Type[]{});
121        private static final org.objectweb.asm.commons.Method SHORT_VALUE = new org.objectweb.asm.commons.Method("shortValue",Types.SHORT_VALUE,new Type[]{});
122        private static final org.objectweb.asm.commons.Method INT_VALUE = new org.objectweb.asm.commons.Method("intValue",Types.INT_VALUE,new Type[]{});
123        private static final org.objectweb.asm.commons.Method LONG_VALUE = new org.objectweb.asm.commons.Method("longValue",Types.LONG_VALUE,new Type[]{});
124        private static final org.objectweb.asm.commons.Method FLT_VALUE = new org.objectweb.asm.commons.Method("floatValue",Types.FLOAT_VALUE,new Type[]{});
125        private static final org.objectweb.asm.commons.Method DBL_VALUE = new org.objectweb.asm.commons.Method("doubleValue",Types.DOUBLE_VALUE,new Type[]{});
126        private static final org.objectweb.asm.commons.Method CHR_VALUE = new org.objectweb.asm.commons.Method("charValue",Types.CHAR,new Type[]{});
127        private static final org.objectweb.asm.commons.Method BYT_VALUE = new org.objectweb.asm.commons.Method("byteValue",Types.BYTE_VALUE,new Type[]{});
128        
129        private static final org.objectweb.asm.commons.Method ASM_METHOD_CONSTRUCTOR = new org.objectweb.asm.commons.Method(
130                        "<init>",
131                        Types.VOID,
132                        new Type[]{Types.CLASS,Types.CLASS_ARRAY}
133                );
134        
135        
136        private static final Map<String,ASMMethod>methods=new ReferenceMap();
137
138        public static void main(String[] args) throws Throwable {
139                ResourceProvider frp = ResourcesImpl.getFileResourceProvider();
140                Resource root = frp.getResource("/Users/mic/Projects/Lucee/webroot/WEB-INF/lucee/cfclasses/wrappers/");
141                root.mkdir();
142                PhysicalClassLoader pcl = new PhysicalClassLoader(root);
143                //PhysicalClassLoader pcl = (PhysicalClassLoader)ThreadLocalPageContext.getConfig().getRPCClassLoader(false);
144
145                ASMProxyFactory.getClass(pcl, root, ArrayNew.class);
146                
147                
148                ASMMethod method = ASMProxyFactory.getMethod(pcl, root, ArrayNew.class, "call", new Class[]{PageContext.class});
149                //print.e(method.invoke(null, new Object[]{null}));
150                
151                
152                
153                
154        }
155        
156        public static ASMClass getClass(ExtendableClassLoader pcl,Resource classRoot,Class clazz) throws IOException, InstantiationException, IllegalAccessException, IllegalArgumentException, SecurityException, InvocationTargetException, NoSuchMethodException{
157                Type type = Type.getType(clazz); 
158
159            // Fields
160            Field[] fields = clazz.getFields();
161            for(int i=0;i<fields.length;i++){
162                if(Modifier.isPrivate(fields[i].getModifiers())) continue;
163                createField(type,fields[i]);
164            }
165            
166            // Methods
167            Method[] methods = clazz.getMethods();
168            Map<String,ASMMethod> amethods=new HashMap<String, ASMMethod>();
169            for(int i=0;i<methods.length;i++){
170                if(Modifier.isPrivate(methods[i].getModifiers())) continue;
171                amethods.put(methods[i].getName(), getMethod(pcl,classRoot,type,clazz,methods[i]));
172            }
173            
174            return new ASMClass(clazz.getName(),amethods);
175            
176        }
177        
178        private static void createField(Type type, Field field) {
179                // TODO Auto-generated method stub
180                
181        }
182
183        public static ASMMethod getMethod(ExtendableClassLoader pcl, Resource classRoot, Class clazz, String methodName, Class[] parameters) throws IOException, InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
184                String className = createMethodName(clazz,methodName,parameters);
185                
186                // check if already in memory cache
187                ASMMethod asmm = methods.get(className);
188                if(asmm!=null){
189                        //print.e("use loaded from memory");
190                        return asmm;
191                }
192                
193                // try to load existing ASM Class
194                Class<?> asmClass;
195                try {
196                        asmClass = pcl.loadClass(className);
197                        //print.e("use existing class");
198                }
199                catch (ClassNotFoundException cnfe) {
200                        Type type = Type.getType(clazz);
201                        Method method = clazz.getMethod(methodName, parameters);
202                        byte[] barr = _createMethod(type, clazz, method, classRoot, className);
203                        asmClass=pcl.loadClass(className, barr);
204                        //print.e("create class");
205                }
206                asmm = newInstance(asmClass,clazz,parameters);
207                //methods.put(className, asmm);
208                return asmm;
209        }
210
211        private static ASMMethod getMethod(ExtendableClassLoader pcl, Resource classRoot, Type type,Class clazz, Method method) throws IOException, InstantiationException, IllegalAccessException, IllegalArgumentException, SecurityException, InvocationTargetException, NoSuchMethodException {
212                String className = createMethodName(clazz,method.getName(),method.getParameterTypes());
213                
214                // check if already in memory cache
215                ASMMethod asmm = methods.get(className);
216                if(asmm!=null)return asmm;
217                
218                // try to load existing ASM Class
219                Class<?> asmClass;
220                try {
221                        asmClass = pcl.loadClass(className);
222                }
223                catch (ClassNotFoundException cnfe) {
224                        byte[] barr = _createMethod(type, clazz, method, classRoot, className);
225                        asmClass=pcl.loadClass(className, barr);
226                }
227                
228                asmm = newInstance(asmClass,clazz,method.getParameterTypes());
229                methods.put(className, asmm);
230                return asmm;
231        }
232
233        private static ASMMethod newInstance(Class<?> asmClass, Class<?> decClass, Class[] params) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, NoSuchMethodException {
234                Constructor<ASMMethod> constr = (Constructor<ASMMethod>) asmClass.getConstructor(
235                                new Class[]{
236                                        Class.class,
237                                        Class[].class
238                                }
239                );
240                return constr.newInstance(new Object[]{
241                                decClass,
242                                params
243                        });
244                 
245                //return (ASMMethod) asmClass.newInstance();
246        }
247        
248        
249        private static String createMethodName(Class clazz,String methodName,Class[] paramTypes) {
250                StringBuilder sb = new StringBuilder("method.")
251                .append(clazz.getName())
252                .append(methodName);
253                
254                paramNames(sb,paramTypes);
255                
256                return sb.toString();
257        }
258
259        private static byte[] _createMethod(Type type,Class clazz, Method method,Resource classRoot, String className) throws IOException {
260                Class<?> rtn = method.getReturnType();
261            Type rtnType = Type.getType(rtn);
262            
263                className=className.replace('.',File.separatorChar);
264                ClassWriter cw = ASMUtil.getClassWriter();
265            cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, className, null, ASM_METHOD.getInternalName(), null);
266                
267
268// CONSTRUCTOR
269
270            GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC,CONSTRUCTOR,null,null,cw);
271            
272            Label begin = new Label();
273        adapter.visitLabel(begin);
274                adapter.loadThis();
275        
276            adapter.visitVarInsn(Opcodes.ALOAD, 1);
277                adapter.visitVarInsn(Opcodes.ALOAD, 2);
278        
279                adapter.invokeConstructor(ASM_METHOD, CONSTRUCTOR);
280                adapter.visitInsn(Opcodes.RETURN);
281                
282                Label end = new Label();
283                adapter.visitLabel(end);
284                
285                adapter.endMethod();
286                
287        /*
288         
289            GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC,CONSTRUCTOR,null,null,cw);
290            
291            Label begin = new Label();
292        adapter.visitLabel(begin);
293                adapter.loadThis();
294        
295            // clazz
296        adapter.visitVarInsn(Opcodes.ALOAD, 2);
297        
298        // parameterTypes 
299        Class<?>[] params = method.getParameterTypes();
300        Type[] paramTypes = new Type[params.length];
301            ArrayVisitor av=new ArrayVisitor();
302            av.visitBegin(adapter, Types.CLASS, params.length);
303            for(int i=0;i<params.length;i++){
304                paramTypes[i]=Type.getType(params[i]);
305                av.visitBeginItem(adapter, i);
306                        loadClass(adapter,params[i]);
307                av.visitEndItem(adapter);
308            }
309            av.visitEnd();
310            
311                adapter.invokeConstructor(ASM_METHOD, ASM_METHOD_CONSTRUCTOR);
312                adapter.visitInsn(Opcodes.RETURN);
313                
314                Label end = new Label();
315                adapter.visitLabel(end);
316                
317                adapter.endMethod();
318         */
319                
320                
321                
322        // METHOD getName();
323                adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC , GET_NAME, null, null, cw);
324                adapter.push(method.getName());
325                adapter.visitInsn(Opcodes.ARETURN);
326        adapter.endMethod();
327                
328    // METHOD getModifiers();
329                adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC , GET_MODIFIERS, null, null, cw);
330                adapter.push(method.getModifiers());
331                adapter.visitInsn(Opcodes.IRETURN);
332        adapter.endMethod();
333        
334
335                
336    // METHOD getReturnType();
337        adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC , GET_RETURN_TYPE_AS_STRING, null, null, cw);
338                
339                adapter.push(method.getReturnType().getName());
340                adapter.visitInsn(Opcodes.ARETURN);
341        
342                adapter.endMethod();
343        
344                
345        
346        
347        // METHOD INVOKE
348                boolean isStatic = Modifier.isStatic(method.getModifiers());
349                adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC , INVOKE, null, null, cw);
350        Label start=adapter.newLabel();
351        adapter.visitLabel(start);
352        
353        // load Object
354        if(!isStatic) {
355                adapter.visitVarInsn(Opcodes.ALOAD, 1);
356                adapter.checkCast(type);
357                }
358                
359        // load params
360        Class<?>[] params = method.getParameterTypes();
361
362        Type[] paramTypes = new Type[params.length];
363            for(int i=0;i<params.length;i++){
364                paramTypes[i]=Type.getType(params[i]);
365            }
366
367        for(int i=0;i<params.length;i++){
368                adapter.visitVarInsn(Opcodes.ALOAD, 2);
369                adapter.push(i);
370                //adapter.visitInsn(Opcodes.ICONST_0);
371                adapter.visitInsn(Opcodes.AALOAD);
372                
373                adapter.checkCast(toReferenceType(params[i],paramTypes[i]));
374                
375                // cast
376                if(params[i]==boolean.class) adapter.invokeVirtual(Types.BOOLEAN, BOOL_VALUE);
377                else if(params[i]==short.class) adapter.invokeVirtual(Types.SHORT, SHORT_VALUE);
378                else if(params[i]==int.class) adapter.invokeVirtual(Types.INTEGER, INT_VALUE);
379                else if(params[i]==float.class) adapter.invokeVirtual(Types.FLOAT, FLT_VALUE);
380                else if(params[i]==long.class) adapter.invokeVirtual(Types.LONG, LONG_VALUE);
381                else if(params[i]==double.class) adapter.invokeVirtual(Types.DOUBLE, DBL_VALUE);
382                else if(params[i]==char.class) adapter.invokeVirtual(Types.CHARACTER, CHR_VALUE);
383                else if(params[i]==byte.class) adapter.invokeVirtual(Types.BYTE, BYT_VALUE);
384                //else adapter.checkCast(paramTypes[i]);
385                
386        }
387        
388        
389        // call method
390        final org.objectweb.asm.commons.Method m = new org.objectweb.asm.commons.Method(method.getName(),rtnType,paramTypes);
391        if(isStatic)adapter.invokeStatic(type, m);
392        else adapter.invokeVirtual(type, m);
393         
394        
395        // return
396        if(rtn==void.class) ASMConstants.NULL(adapter);
397        
398        
399        // cast result to object
400        if(rtn==boolean.class) adapter.invokeStatic(Types.BOOLEAN, BOOL_VALUE_OF);
401        else if(rtn==short.class) adapter.invokeStatic(Types.SHORT, SHORT_VALUE_OF);
402        else if(rtn==int.class) adapter.invokeStatic(Types.INTEGER, INT_VALUE_OF);
403        else if(rtn==long.class) adapter.invokeStatic(Types.LONG, LONG_VALUE_OF);
404        else if(rtn==float.class) adapter.invokeStatic(Types.FLOAT, FLT_VALUE_OF);
405        else if(rtn==double.class) adapter.invokeStatic(Types.DOUBLE, DBL_VALUE_OF);
406        else if(rtn==char.class) adapter.invokeStatic(Types.CHARACTER, CHR_VALUE_OF);
407        else if(rtn==byte.class) adapter.invokeStatic(Types.BYTE, BYT_VALUE_OF);
408        
409        adapter.visitInsn(Opcodes.ARETURN);
410        
411        adapter.endMethod();
412                
413        
414                
415        if(classRoot!=null) {
416                Resource classFile=classRoot.getRealResource(className+".class");
417                return store(cw.toByteArray(),classFile);
418        }
419        return cw.toByteArray();
420        }
421        
422
423        private static Type toReferenceType(Class<?> clazz, Type defaultValue) {
424                if(int.class==clazz) return Types.INTEGER;
425                else if(long.class==clazz) return Types.LONG;
426                else if(char.class==clazz) return Types.CHARACTER;
427                else if(byte.class==clazz) return Types.BYTE;
428                else if(float.class==clazz) return Types.FLOAT;
429                else if(double.class==clazz) return Types.DOUBLE;
430                else if(boolean.class==clazz) return Types.BOOLEAN;
431                else if(short.class==clazz) return Types.SHORT;
432                return defaultValue;
433        }
434
435        private static void loadClass(GeneratorAdapter adapter, Class<?> clazz) {
436                if(void.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
437                
438                // primitive types
439                else if(int.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;");
440                else if(long.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;");
441                else if(char.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;");
442                else if(byte.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;");
443                else if(float.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;");
444                else if(double.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;");
445                else if(boolean.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
446                else if(short.class==clazz) adapter.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;");
447                
448                // TODO ref types
449                
450                else {
451                    adapter.visitVarInsn(Opcodes.ALOAD, 1);
452                adapter.push(clazz.getName());
453                        adapter.invokeVirtual(Types.CLASS_LOADER,LOAD_CLASS );
454                }
455        }
456
457        private static void paramNames(StringBuilder sb, Class<?>[] params) {
458                if(ArrayUtil.isEmpty(params)) return;
459                
460                for(int i=0;i<params.length;i++){
461                        sb.append('$');
462                        if(params[i].isArray())
463                                sb.append(StringUtil.replace(Caster.toClassName(params[i]).replace('.', '_'),"[]","_arr",false));
464                        else
465                                sb.append(params[i].getName().replace('.', '_'));
466                }
467        }
468
469        private static byte[] store(byte[] barr,Resource classFile) throws IOException {
470                // create class file
471        ResourceUtil.touch(classFile);
472        //print.e(classFile);
473        IOUtil.copy(new ByteArrayInputStream(barr), classFile,true);
474       return barr;
475        }
476        
477}