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}