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.util;
020
021import java.io.ByteArrayInputStream;
022import java.io.IOException;
023import java.lang.reflect.Constructor;
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Method;
026import java.util.Arrays;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Map;
030import java.util.Set;
031
032import lucee.commons.io.IOUtil;
033import lucee.commons.io.res.Resource;
034import lucee.commons.io.res.util.ResourceUtil;
035import lucee.commons.lang.KeyGenerator;
036import lucee.commons.lang.PhysicalClassLoader;
037import lucee.runtime.Component;
038import lucee.runtime.PageContext;
039import lucee.runtime.PageContextImpl;
040import lucee.runtime.config.ConfigWeb;
041import lucee.runtime.exp.PageException;
042import lucee.runtime.java.JavaProxy;
043import lucee.runtime.op.Caster;
044import lucee.runtime.reflection.Reflector;
045import lucee.transformer.bytecode.visitor.ArrayVisitor;
046
047import org.objectweb.asm.ClassWriter;
048import org.objectweb.asm.FieldVisitor;
049import org.objectweb.asm.Label;
050import org.objectweb.asm.Opcodes;
051import org.objectweb.asm.Type;
052import org.objectweb.asm.commons.GeneratorAdapter;
053
054/**
055 * creates a Java Proxy for components, so you can use componets as java classes following a certain interface or class
056 */
057public class JavaProxyFactory {
058        
059
060        private static final String COMPONENT_NAME="L"+Types.COMPONENT.getInternalName()+";";
061        private static final String CONFIG_WEB_NAME="L"+Types.CONFIG_WEB.getInternalName()+";";
062
063        private static final Type JAVA_PROXY = Type.getType(JavaProxy.class);
064
065        
066        private static final org.objectweb.asm.commons.Method CALL = new org.objectweb.asm.commons.Method(
067                        "call",
068                        Types.OBJECT,
069                        new Type[]{Types.CONFIG_WEB,Types.COMPONENT,Types.STRING,Types.OBJECT_ARRAY});
070        
071        private static final org.objectweb.asm.commons.Method CONSTRUCTOR = new org.objectweb.asm.commons.Method(
072                        "<init>",
073                        Types.VOID,
074                        new Type[]{
075                                        Types.CONFIG_WEB,
076                                        Types.COMPONENT
077                                }
078                );
079        private static final org.objectweb.asm.commons.Method SUPER_CONSTRUCTOR = new org.objectweb.asm.commons.Method(
080                        "<init>",
081                        Types.VOID,
082                        new Type[]{}
083                );
084
085        private static final org.objectweb.asm.commons.Method TO_BOOLEAN = new org.objectweb.asm.commons.Method(
086                        "toBoolean",
087                        Types.BOOLEAN_VALUE,
088                        new Type[]{Types.OBJECT});
089        private static final org.objectweb.asm.commons.Method TO_FLOAT = new org.objectweb.asm.commons.Method(
090                        "toFloat",
091                        Types.FLOAT_VALUE,
092                        new Type[]{Types.OBJECT});
093        private static final org.objectweb.asm.commons.Method TO_INT = new org.objectweb.asm.commons.Method(
094                        "toInt",
095                        Types.INT_VALUE,
096                        new Type[]{Types.OBJECT});
097        private static final org.objectweb.asm.commons.Method TO_DOUBLE = new org.objectweb.asm.commons.Method(
098                        "toDouble",
099                        Types.DOUBLE_VALUE,
100                        new Type[]{Types.OBJECT});
101        private static final org.objectweb.asm.commons.Method TO_LONG = new org.objectweb.asm.commons.Method(
102                        "toLong",
103                        Types.LONG_VALUE,
104                        new Type[]{Types.OBJECT});
105        private static final org.objectweb.asm.commons.Method TO_CHAR = new org.objectweb.asm.commons.Method(
106                        "toChar",
107                        Types.CHAR,
108                        new Type[]{Types.OBJECT});
109        private static final org.objectweb.asm.commons.Method TO_BYTE = new org.objectweb.asm.commons.Method(
110                        "toByte",
111                        Types.BYTE_VALUE,
112                        new Type[]{Types.OBJECT});
113        private static final org.objectweb.asm.commons.Method TO_SHORT = new org.objectweb.asm.commons.Method(
114                        "toShort",
115                        Types.SHORT,
116                        new Type[]{Types.OBJECT});
117        private static final org.objectweb.asm.commons.Method TO_ = new org.objectweb.asm.commons.Method(
118                        "to",
119                        Types.OBJECT,
120                        new Type[]{Types.OBJECT,Types.CLASS});
121        
122        
123
124        private static final org.objectweb.asm.commons.Method _BOOLEAN = new org.objectweb.asm.commons.Method(
125                        "toCFML",
126                        Types.OBJECT,
127                        new Type[]{Types.BOOLEAN_VALUE});
128        private static final org.objectweb.asm.commons.Method _FLOAT = new org.objectweb.asm.commons.Method(
129                        "toCFML",
130                        Types.OBJECT,
131                        new Type[]{Types.FLOAT_VALUE});
132        private static final org.objectweb.asm.commons.Method _INT = new org.objectweb.asm.commons.Method(
133                        "toCFML",
134                        Types.OBJECT,
135                        new Type[]{Types.INT_VALUE});
136        private static final org.objectweb.asm.commons.Method _DOUBLE = new org.objectweb.asm.commons.Method(
137                        "toCFML",
138                        Types.OBJECT,
139                        new Type[]{Types.DOUBLE_VALUE});
140        private static final org.objectweb.asm.commons.Method _LONG = new org.objectweb.asm.commons.Method(
141                        "toCFML",
142                        Types.OBJECT,
143                        new Type[]{Types.LONG_VALUE});
144        private static final org.objectweb.asm.commons.Method _CHAR = new org.objectweb.asm.commons.Method(
145                        "toCFML",
146                        Types.OBJECT,
147                        new Type[]{Types.CHAR});
148        private static final org.objectweb.asm.commons.Method _BYTE = new org.objectweb.asm.commons.Method(
149                        "toCFML",
150                        Types.OBJECT,
151                        new Type[]{Types.BYTE_VALUE});
152        private static final org.objectweb.asm.commons.Method _SHORT = new org.objectweb.asm.commons.Method(
153                        "toCFML",
154                        Types.OBJECT,
155                        new Type[]{Types.SHORT});
156        private static final org.objectweb.asm.commons.Method _OBJECT = new org.objectweb.asm.commons.Method(
157                        "toCFML",
158                        Types.OBJECT,
159                        new Type[]{Types.OBJECT});
160
161        
162        
163        
164        
165        
166/*
167
168        public static Object to(Object obj, Class clazz) {
169                return obj;
170        }*/
171        
172        
173        
174
175        /*public static Object createProxy(Config config,Component cfc, String className) throws PageException, IOException {
176                return createProxy(cfc, null, ClassUtil.loadClass(config.getClassLoader(), className));
177        }*/
178
179        public static Object createProxy(PageContext pc, Component cfc, Class extendz,Class... interfaces) throws PageException, IOException {
180                PageContextImpl pci=(PageContextImpl) pc;
181                if(extendz==null) extendz=Object.class;
182                if(interfaces==null) interfaces=new Class[0];
183                else {
184                        for(int i=0;i<interfaces.length;i++){
185                                if(!interfaces[i].isInterface()) 
186                                        throw new IOException("definition ["+interfaces[i].getName()+"] is a class and not a interface");
187                        }
188                }
189                
190                
191                
192                Type typeExtends = Type.getType(extendz); 
193                Type[] typeInterfaces = ASMUtil.toTypes(interfaces); 
194                String[] strInterfaces=new String[typeInterfaces.length];
195                for(int i=0;i<strInterfaces.length;i++){
196                        strInterfaces[i]=typeInterfaces[i].getInternalName();
197                }
198                
199                
200                String className=createClassName(extendz,interfaces);
201        //Mapping mapping = cfc.getPageSource().getMapping();
202                
203        // get ClassLoader
204        PhysicalClassLoader cl=null;
205                try {
206                        cl = (PhysicalClassLoader) pci.getRPCClassLoader(false);// mapping.getConfig().getRPCClassLoader(false)
207                } catch (IOException e) {
208                        throw Caster.toPageException(e);
209                }
210                Resource classFile = cl.getDirectory().getRealResource(className.concat(".class"));
211                
212                // check if already exists, if yes return
213                if(classFile.exists()) {
214                        //Object obj=newInstance(cl,className,cfc);
215                        // if(obj!=null) return obj;
216                }
217                
218                /*
219                String classNameOriginal=component.getPageSource().getFullClassName();
220        String realOriginal=classNameOriginal.replace('.','/');
221                Resource classFileOriginal = mapping.getClassRootDirectory().getRealResource(realOriginal.concat(".class"));
222                */      
223                ClassWriter cw = ASMUtil.getClassWriter();
224            
225                cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, className, null, typeExtends.getInternalName(), strInterfaces);
226                //BytecodeContext statConstr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.STATIC_CONSTRUCTOR);
227                //BytecodeContext constr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.CONSTRUCTOR);
228                
229                
230                // field Component
231                FieldVisitor _fv = cw.visitField(Opcodes.ACC_PRIVATE, "cfc", COMPONENT_NAME, null, null);
232                _fv.visitEnd();
233                _fv = cw.visitField(Opcodes.ACC_PRIVATE, "config", CONFIG_WEB_NAME, null, null);
234                _fv.visitEnd();
235                
236                 // Constructor
237        GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC,CONSTRUCTOR,null,null,cw);
238        Label begin = new Label();
239        adapter.visitLabel(begin);
240                adapter.loadThis();
241        adapter.invokeConstructor(Types.OBJECT, SUPER_CONSTRUCTOR);
242        
243        //adapter.putField(JAVA_PROXY, arg1, arg2)
244
245        adapter.visitVarInsn(Opcodes.ALOAD, 0);
246        adapter.visitVarInsn(Opcodes.ALOAD, 1);
247        adapter.visitFieldInsn(Opcodes.PUTFIELD, className, "config", CONFIG_WEB_NAME);
248
249        adapter.visitVarInsn(Opcodes.ALOAD, 0);
250        adapter.visitVarInsn(Opcodes.ALOAD, 2);
251        adapter.visitFieldInsn(Opcodes.PUTFIELD, className, "cfc", COMPONENT_NAME);
252                
253        adapter.visitInsn(Opcodes.RETURN);
254                Label end = new Label();
255                adapter.visitLabel(end);
256                adapter.visitLocalVariable("config",CONFIG_WEB_NAME, null, begin, end, 1);
257                adapter.visitLocalVariable("cfc",COMPONENT_NAME, null, begin, end, 2);
258                
259        //adapter.returnValue();
260        adapter.endMethod();
261        
262                
263                // create methods
264                Set<Class> cDone=new HashSet<Class>();
265                Map<String,Class> mDone=new HashMap<String,Class>();
266                for(int i=0;i<interfaces.length;i++){
267                        _createProxy(cw,cDone,mDone, cfc, interfaces[i],className);
268                }
269        cw.visitEnd();
270        
271        
272        // create class file
273        byte[] barr = cw.toByteArray();
274        
275        try {
276                ResourceUtil.touch(classFile);
277                IOUtil.copy(new ByteArrayInputStream(barr), classFile,true);
278                
279                cl = (PhysicalClassLoader) pci.getRPCClassLoader(true);
280                Class<?> clazz = cl.loadClass(className, barr);
281                return newInstance(clazz, pc.getConfig(),cfc);
282        }
283        catch(Throwable t) {
284                throw Caster.toPageException(t);
285        }
286        }
287
288        private static void _createProxy(ClassWriter cw, Set<Class> cDone,Map<String,Class> mDone, Component cfc, Class clazz, String className) throws IOException {
289        if(cDone.contains(clazz)) return;
290                
291                cDone.add(clazz);
292                
293                // super class
294                Class superClass = clazz.getSuperclass();
295                if(superClass!=null)_createProxy(cw, cDone,mDone, cfc, superClass,className);
296                
297                // interfaces
298                Class[] interfaces = clazz.getInterfaces();
299                if(interfaces!=null)for(int i=0;i<interfaces.length;i++){
300                        _createProxy(cw,cDone,mDone, cfc, interfaces[i],className);
301                }
302                
303                Method[] methods = clazz.getMethods();
304                if(methods!=null)for(int i=0;i<methods.length;i++){
305                        _createMethod(cw,mDone,methods[i],className);
306                }  
307        }
308
309        private static void _createMethod(ClassWriter cw, Map<String,Class> mDone, Method src, String className) throws IOException {
310                Class<?>[] classArgs = src.getParameterTypes();
311                Class<?> classRtn = src.getReturnType();
312                
313                String str=src.getName()+"("+Reflector.getDspMethods(classArgs)+")";
314                Class rtnClass = mDone.get(str);
315                if(rtnClass!=null) {
316                        if(rtnClass!=classRtn) throw new IOException("there is a conflict with method ["+str+"], this method is declared more than once with different return types.");
317                        return;
318                }
319                mDone.put(str,classRtn);
320                
321                Type[] typeArgs = ASMUtil.toTypes(classArgs); 
322                Type typeRtn = Type.getType(classRtn); 
323                
324                org.objectweb.asm.commons.Method method = new org.objectweb.asm.commons.Method(
325                        src.getName(),
326                        typeRtn,
327                        typeArgs
328                        );
329         GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL , method, null, null, cw);
330         //BytecodeContext bc = new BytecodeContext(statConstr,constr,null,null,keys,cw,className,adapter,method,writeLog);
331         Label start=adapter.newLabel();
332         adapter.visitLabel(start);
333         
334         
335         //JavaProxy.call(cfc,"add",new Object[]{arg0})
336         // config
337         adapter.visitVarInsn(Opcodes.ALOAD, 0);
338         adapter.visitFieldInsn(Opcodes.GETFIELD, className, "config", CONFIG_WEB_NAME);
339         
340         // cfc
341         adapter.visitVarInsn(Opcodes.ALOAD, 0);
342         adapter.visitFieldInsn(Opcodes.GETFIELD, className, "cfc", COMPONENT_NAME);
343         
344         // name
345         adapter.push(src.getName());
346         
347         // arguments
348         ArrayVisitor av=new ArrayVisitor();
349         av.visitBegin(adapter,Types.OBJECT,typeArgs.length);
350         for(int y=0;y<typeArgs.length;y++){
351                        av.visitBeginItem(adapter, y);
352                                adapter.loadArg(y);
353                                if(classArgs[y]==boolean.class) adapter.invokeStatic(JAVA_PROXY, _BOOLEAN);
354                                else if(classArgs[y]==byte.class) adapter.invokeStatic(JAVA_PROXY, _BYTE);
355                                else if(classArgs[y]==char.class) adapter.invokeStatic(JAVA_PROXY, _CHAR);
356                                else if(classArgs[y]==double.class) adapter.invokeStatic(JAVA_PROXY, _DOUBLE);
357                                else if(classArgs[y]==float.class) adapter.invokeStatic(JAVA_PROXY, _FLOAT);
358                                else if(classArgs[y]==int.class) adapter.invokeStatic(JAVA_PROXY, _INT);
359                                else if(classArgs[y]==long.class) adapter.invokeStatic(JAVA_PROXY, _LONG);
360                                else if(classArgs[y]==short.class) adapter.invokeStatic(JAVA_PROXY, _SHORT);
361                                else {
362                                        adapter.invokeStatic(JAVA_PROXY, _OBJECT);
363                                }
364                                
365                                
366                        av.visitEndItem(adapter);
367         }
368         av.visitEnd();
369         adapter.invokeStatic(JAVA_PROXY, CALL);
370         
371       //JavaProxy.to...(...);
372         int rtn=Opcodes.IRETURN;
373         if(classRtn==boolean.class)    adapter.invokeStatic(JAVA_PROXY, TO_BOOLEAN);
374         else if(classRtn==byte.class)  adapter.invokeStatic(JAVA_PROXY, TO_BYTE);
375         else if(classRtn==char.class)  adapter.invokeStatic(JAVA_PROXY, TO_CHAR);
376         else if(classRtn==double.class){
377                                                                        rtn=Opcodes.DRETURN;
378                                                                        adapter.invokeStatic(JAVA_PROXY, TO_DOUBLE);
379         }
380         else if(classRtn==float.class) {
381                                                                                rtn=Opcodes.FRETURN;
382                                                                                adapter.invokeStatic(JAVA_PROXY, TO_FLOAT);
383         }
384         else if(classRtn==int.class)   adapter.invokeStatic(JAVA_PROXY, TO_INT);
385         else if(classRtn==long.class)  {
386                                                                        rtn=Opcodes.LRETURN;
387                                                                        adapter.invokeStatic(JAVA_PROXY, TO_LONG);
388         }
389         else if(classRtn==short.class) adapter.invokeStatic(JAVA_PROXY, TO_SHORT);
390         else if(classRtn==void.class){
391                                                                        rtn=Opcodes.RETURN;
392                                                                        adapter.pop();
393         }
394         else {
395                                                                                rtn=Opcodes.ARETURN;
396                                                                                adapter.checkCast(typeRtn);
397         }
398         
399         
400         
401         /*mv = cw.visitMethod(ACC_PUBLIC, "add", "(Ljava/lang/Object;)Z", null, null);
402         mv.visitCode();
403         Label l0 = new Label();
404         mv.visitLabel(l0);
405         mv.visitLineNumber(20, l0);
406         mv.visitVarInsn(ALOAD, 0);
407         mv.visitFieldInsn(GETFIELD, "Test", "cfc", "Ljava/lang/Object;");
408         mv.visitLdcInsn("add");
409         mv.visitInsn(ICONST_1);
410         mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
411         mv.visitInsn(DUP);
412         mv.visitInsn(ICONST_0);
413         mv.visitVarInsn(ALOAD, 1);
414         mv.visitInsn(AASTORE);
415         mv.visitMethodInsn(INVOKESTATIC, "JavaProxy", "call", "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
416         mv.visitMethodInsn(INVOKESTATIC, "JavaProxy", "toBoolean", "(Ljava/lang/Object;)Z");
417         mv.visitInsn(IRETURN);
418         Label l1 = new Label();
419         mv.visitLabel(l1);
420         mv.visitLocalVariable("this", "LTest;", null, l0, l1, 0);
421         mv.visitLocalVariable("arg0", "Ljava/lang/Object;", null, l0, l1, 1);
422         mv.visitMaxs(6, 2);
423         mv.visitEnd();*/
424         
425         
426         
427         adapter.visitInsn(rtn);
428         adapter.endMethod();
429                
430                
431        }
432
433        private static Object newInstance(PhysicalClassLoader cl, String className, ConfigWeb config,Component cfc) 
434                throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ClassNotFoundException {
435                        return newInstance(cl.loadClass(className),config,cfc);
436        }
437        private static Object newInstance(Class<?> _clazz,ConfigWeb config, Component cfc) 
438        throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
439                Constructor<?> constr = _clazz.getConstructor(new Class[]{ConfigWeb.class,Component.class});
440                return constr.newInstance(new Object[]{config,cfc});
441        }
442        
443        private static String createClassName(Class extendz, Class[] interfaces) throws IOException {
444                if(extendz==null) extendz=Object.class;
445                
446                
447                
448                StringBuilder sb=new StringBuilder(extendz.getName());
449                if(interfaces!=null && interfaces.length>0){
450                        sb.append(';');
451                        
452                        String[] arr=new String[interfaces.length];
453                        for(int i=0;i<interfaces.length;i++){
454                                arr[i]=interfaces[i].getName();
455                        }
456                        Arrays.sort(arr);
457                        
458                        sb.append(lucee.runtime.type.util.ListUtil.arrayToList(arr, ";"));
459                }
460                
461                String key = KeyGenerator.createVariable(sb.toString());
462                
463                
464                return key;
465        }
466
467
468
469}