001/** 002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com 003 * 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 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 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 org.talend.sdk.component.runtime.manager.asm; 017 018import static java.util.stream.Collectors.toList; 019import static org.apache.xbean.asm9.ClassReader.SKIP_CODE; 020import static org.apache.xbean.asm9.ClassReader.SKIP_DEBUG; 021import static org.apache.xbean.asm9.ClassReader.SKIP_FRAMES; 022import static org.apache.xbean.asm9.Opcodes.AALOAD; 023import static org.apache.xbean.asm9.Opcodes.AASTORE; 024import static org.apache.xbean.asm9.Opcodes.ACC_PRIVATE; 025import static org.apache.xbean.asm9.Opcodes.ACC_PROTECTED; 026import static org.apache.xbean.asm9.Opcodes.ACC_PUBLIC; 027import static org.apache.xbean.asm9.Opcodes.ACC_STATIC; 028import static org.apache.xbean.asm9.Opcodes.ACC_SUPER; 029import static org.apache.xbean.asm9.Opcodes.ACC_SYNTHETIC; 030import static org.apache.xbean.asm9.Opcodes.ACC_VARARGS; 031import static org.apache.xbean.asm9.Opcodes.ACONST_NULL; 032import static org.apache.xbean.asm9.Opcodes.ALOAD; 033import static org.apache.xbean.asm9.Opcodes.ANEWARRAY; 034import static org.apache.xbean.asm9.Opcodes.ARETURN; 035import static org.apache.xbean.asm9.Opcodes.ASTORE; 036import static org.apache.xbean.asm9.Opcodes.ATHROW; 037import static org.apache.xbean.asm9.Opcodes.BIPUSH; 038import static org.apache.xbean.asm9.Opcodes.CHECKCAST; 039import static org.apache.xbean.asm9.Opcodes.DLOAD; 040import static org.apache.xbean.asm9.Opcodes.DRETURN; 041import static org.apache.xbean.asm9.Opcodes.DUP; 042import static org.apache.xbean.asm9.Opcodes.FLOAD; 043import static org.apache.xbean.asm9.Opcodes.FRETURN; 044import static org.apache.xbean.asm9.Opcodes.GETFIELD; 045import static org.apache.xbean.asm9.Opcodes.GETSTATIC; 046import static org.apache.xbean.asm9.Opcodes.ICONST_0; 047import static org.apache.xbean.asm9.Opcodes.ICONST_1; 048import static org.apache.xbean.asm9.Opcodes.ICONST_2; 049import static org.apache.xbean.asm9.Opcodes.ICONST_3; 050import static org.apache.xbean.asm9.Opcodes.ICONST_4; 051import static org.apache.xbean.asm9.Opcodes.ICONST_5; 052import static org.apache.xbean.asm9.Opcodes.IFEQ; 053import static org.apache.xbean.asm9.Opcodes.ILOAD; 054import static org.apache.xbean.asm9.Opcodes.INVOKEINTERFACE; 055import static org.apache.xbean.asm9.Opcodes.INVOKESPECIAL; 056import static org.apache.xbean.asm9.Opcodes.INVOKESTATIC; 057import static org.apache.xbean.asm9.Opcodes.INVOKEVIRTUAL; 058import static org.apache.xbean.asm9.Opcodes.IRETURN; 059import static org.apache.xbean.asm9.Opcodes.LLOAD; 060import static org.apache.xbean.asm9.Opcodes.LRETURN; 061import static org.apache.xbean.asm9.Opcodes.NEW; 062import static org.apache.xbean.asm9.Opcodes.POP; 063import static org.apache.xbean.asm9.Opcodes.PUTFIELD; 064import static org.apache.xbean.asm9.Opcodes.RETURN; 065import static org.apache.xbean.asm9.Opcodes.SIPUSH; 066import static org.apache.xbean.asm9.Opcodes.V1_8; 067 068import java.io.InputStream; 069import java.io.ObjectStreamException; 070import java.io.Serializable; 071import java.lang.annotation.Annotation; 072import java.lang.reflect.Constructor; 073import java.lang.reflect.Field; 074import java.lang.reflect.Method; 075import java.lang.reflect.Modifier; 076import java.util.Collection; 077import java.util.concurrent.atomic.AtomicInteger; 078import java.util.stream.Stream; 079 080import org.apache.xbean.asm9.ClassReader; 081import org.apache.xbean.asm9.ClassWriter; 082import org.apache.xbean.asm9.Label; 083import org.apache.xbean.asm9.MethodVisitor; 084import org.apache.xbean.asm9.Type; 085import org.apache.xbean.asm9.shade.commons.EmptyVisitor; 086import org.talend.sdk.component.api.service.interceptor.InterceptorHandler; 087import org.talend.sdk.component.api.service.interceptor.Intercepts; 088import org.talend.sdk.component.runtime.manager.interceptor.InterceptorHandlerFacade; 089 090import lombok.AllArgsConstructor; 091 092// highly inspired from openwebbeans proxying code 093// 094// goal is mainly to add a writeReplace method to ensure services are serializable when not done by the developer. 095public class ProxyGenerator implements Serializable { 096 097 private static final String FIELD_INTERCEPTOR_HANDLER = "tacokitIntDecHandler"; 098 099 private static final String FIELD_INTERCEPTED_METHODS = "tacokitIntDecMethods"; 100 101 // internal, used by serialization 102 private static final ProxyGenerator SINGLETON = new ProxyGenerator(); 103 104 private final int javaVersion; 105 106 public ProxyGenerator() { 107 javaVersion = determineDefaultJavaVersion(); 108 } 109 110 private int determineDefaultJavaVersion() { 111 final String javaVersionProp = System.getProperty("java.version", "1.8"); 112 if (javaVersionProp.startsWith("1.8")) { // we don't support earlier 113 return V1_8; 114 } 115 // add java 9 test when upgrading asm 116 return V1_8; // return higher one 117 } 118 119 private void createSerialisation(final ClassWriter cw, final String pluginId, final String key) { 120 final MethodVisitor mv = cw 121 .visitMethod(Modifier.PUBLIC, "writeReplace", "()Ljava/lang/Object;", null, 122 new String[] { Type.getType(ObjectStreamException.class).getInternalName() }); 123 124 mv.visitCode(); 125 126 mv.visitTypeInsn(NEW, "org/talend/sdk/component/runtime/serialization/SerializableService"); 127 mv.visitInsn(DUP); 128 mv.visitLdcInsn(pluginId); 129 mv.visitLdcInsn(key); 130 mv 131 .visitMethodInsn(INVOKESPECIAL, "org/talend/sdk/component/runtime/serialization/SerializableService", 132 "<init>", "(Ljava/lang/String;Ljava/lang/String;)V", false); 133 mv.visitInsn(ARETURN); 134 135 mv.visitMaxs(-1, -1); 136 mv.visitEnd(); 137 } 138 139 private String createConstructor(final ClassWriter cw, final Class<?> classToProxy, final String classFileName, 140 final String proxyClassFileName, final Constructor<?> constructor, final boolean withInterceptors) { 141 try { 142 Constructor superDefaultCt; 143 String parentClassFileName; 144 String[] exceptions = null; 145 if (classToProxy.isInterface()) { 146 parentClassFileName = Type.getInternalName(Object.class); 147 superDefaultCt = Object.class.getConstructor(); 148 } else { 149 parentClassFileName = classFileName; 150 if (constructor == null) { 151 superDefaultCt = classToProxy.getConstructor(); 152 } else { 153 superDefaultCt = constructor; 154 155 Class<?>[] exceptionTypes = constructor.getExceptionTypes(); 156 exceptions = exceptionTypes.length == 0 ? null : new String[exceptionTypes.length]; 157 for (int i = 0; i < exceptionTypes.length; i++) { 158 exceptions[i] = Type.getInternalName(exceptionTypes[i]); 159 } 160 } 161 } 162 163 final String descriptor = Type.getConstructorDescriptor(superDefaultCt); 164 final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", descriptor, null, exceptions); 165 mv.visitCode(); 166 mv.visitVarInsn(ALOAD, 0); 167 if (constructor != null) { 168 for (int i = 1; i <= constructor.getParameterTypes().length; i++) { 169 mv.visitVarInsn(ALOAD, i); 170 } 171 } 172 mv.visitMethodInsn(INVOKESPECIAL, parentClassFileName, "<init>", descriptor, false); 173 174 if (withInterceptors) { 175 mv.visitVarInsn(ALOAD, 0); 176 mv.visitInsn(ACONST_NULL); 177 mv 178 .visitFieldInsn(PUTFIELD, proxyClassFileName, FIELD_INTERCEPTOR_HANDLER, 179 Type.getDescriptor(InterceptorHandler.class)); 180 } 181 182 mv.visitInsn(RETURN); 183 mv.visitMaxs(-1, -1); 184 mv.visitEnd(); 185 186 return parentClassFileName; 187 } catch (final NoSuchMethodException e) { 188 throw new IllegalStateException(e); 189 } 190 } 191 192 private void delegateMethod(final ClassWriter cw, final Method method, final String proxyClassFileName, 193 final int methodIndex) { 194 final Class<?> returnType = method.getReturnType(); 195 final Class<?>[] parameterTypes = method.getParameterTypes(); 196 final Class<?>[] exceptionTypes = method.getExceptionTypes(); 197 final int modifiers = method.getModifiers(); 198 if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) { 199 throw new IllegalStateException("It's not possible to proxy a final or static method: " 200 + method.getDeclaringClass().getName() + " " + method.getName()); 201 } 202 203 // push the method definition 204 int modifier = modifiers & (ACC_PUBLIC | ACC_PROTECTED | ACC_VARARGS); 205 206 MethodVisitor mv = cw.visitMethod(modifier, method.getName(), Type.getMethodDescriptor(method), null, null); 207 mv.visitCode(); 208 209 // push try/catch block, to catch declared exceptions, and to catch java.lang.Throwable 210 final Label l0 = new Label(); 211 final Label l1 = new Label(); 212 final Label l2 = new Label(); 213 214 if (exceptionTypes.length > 0) { 215 mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException"); 216 } 217 218 // push try code 219 mv.visitLabel(l0); 220 final String classNameToOverride = method.getDeclaringClass().getName().replace('.', '/'); 221 mv.visitLdcInsn(Type.getType("L" + classNameToOverride + ";")); 222 223 // the following code generates the bytecode for this line of Java: 224 // Method method = <proxy>.class.getMethod("add", new Class[] { <array of function argument classes> }); 225 226 // get the method name to invoke, and push to stack 227 mv.visitLdcInsn(method.getName()); 228 229 // create the Class[] 230 createArrayDefinition(mv, parameterTypes.length, Class.class); 231 232 int length = 1; 233 234 // push parameters into array 235 for (int i = 0; i < parameterTypes.length; i++) { 236 // keep copy of array on stack 237 mv.visitInsn(DUP); 238 239 Class<?> parameterType = parameterTypes[i]; 240 241 // push number onto stack 242 pushIntOntoStack(mv, i); 243 244 if (parameterType.isPrimitive()) { 245 String wrapperType = getWrapperType(parameterType); 246 mv.visitFieldInsn(GETSTATIC, wrapperType, "TYPE", "Ljava/lang/Class;"); 247 } else { 248 mv.visitLdcInsn(Type.getType(parameterType)); 249 } 250 251 mv.visitInsn(AASTORE); 252 253 if (Long.TYPE.equals(parameterType) || Double.TYPE.equals(parameterType)) { 254 length += 2; 255 } else { 256 length++; 257 } 258 } 259 260 // the following code generates bytecode equivalent to: 261 // return ((<returntype>) invocationHandler.invoke(this, {methodIndex}, new Object[] { <function arguments 262 // }))[.<primitive>Value()]; 263 264 final Label l4 = new Label(); 265 mv.visitLabel(l4); 266 267 mv.visitVarInsn(ALOAD, 0); 268 269 // get the invocationHandler field from this class 270 mv 271 .visitFieldInsn(GETFIELD, proxyClassFileName, FIELD_INTERCEPTOR_HANDLER, 272 Type.getDescriptor(InterceptorHandler.class)); 273 274 // add the Method from the static array as first parameter 275 mv.visitFieldInsn(GETSTATIC, proxyClassFileName, FIELD_INTERCEPTED_METHODS, Type.getDescriptor(Method[].class)); 276 277 // push the methodIndex of the current method 278 if (methodIndex < 128) { 279 mv.visitIntInsn(BIPUSH, methodIndex); 280 } else if (methodIndex < 32267) { 281 // for methods > 127 we need to push a short number as index 282 mv.visitIntInsn(SIPUSH, methodIndex); 283 } else { 284 throw new IllegalStateException("Sorry, we only support Classes with 2^15 methods..."); 285 } 286 287 // and now load the Method from the array 288 mv.visitInsn(AALOAD); 289 290 // prepare the parameter array as Object[] and store it on the stack 291 pushMethodParameterArray(mv, parameterTypes); 292 293 // invoke the invocationHandler 294 mv 295 .visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(InterceptorHandler.class), "invoke", 296 "(Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true); 297 298 // cast the result 299 mv.visitTypeInsn(CHECKCAST, getCastType(returnType)); 300 301 if (returnType.isPrimitive() && (!Void.TYPE.equals(returnType))) { 302 // get the primitive value 303 mv 304 .visitMethodInsn(INVOKEVIRTUAL, getWrapperType(returnType), getPrimitiveMethod(returnType), 305 "()" + Type.getDescriptor(returnType), false); 306 } 307 308 // push return 309 mv.visitLabel(l1); 310 if (!Void.TYPE.equals(returnType)) { 311 mv.visitInsn(getReturnInsn(returnType)); 312 } else { 313 mv.visitInsn(POP); 314 mv.visitInsn(RETURN); 315 } 316 317 // catch InvocationTargetException 318 if (exceptionTypes.length > 0) { 319 mv.visitLabel(l2); 320 mv.visitVarInsn(ASTORE, length); 321 322 Label l5 = new Label(); 323 mv.visitLabel(l5); 324 325 for (int i = 0; i < exceptionTypes.length; i++) { 326 Class<?> exceptionType = exceptionTypes[i]; 327 328 mv.visitLdcInsn(Type.getType("L" + exceptionType.getCanonicalName().replace('.', '/') + ";")); 329 mv.visitVarInsn(ALOAD, length); 330 mv 331 .visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", 332 "()Ljava/lang/Throwable;", false); 333 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); 334 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false); 335 336 final Label l6 = new Label(); 337 mv.visitJumpInsn(IFEQ, l6); 338 339 final Label l7 = new Label(); 340 mv.visitLabel(l7); 341 342 mv.visitVarInsn(ALOAD, length); 343 mv 344 .visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", 345 "()Ljava/lang/Throwable;", false); 346 mv.visitTypeInsn(CHECKCAST, getCastType(exceptionType)); 347 mv.visitInsn(ATHROW); 348 mv.visitLabel(l6); 349 350 if (i == (exceptionTypes.length - 1)) { 351 mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException"); 352 mv.visitInsn(DUP); 353 mv.visitVarInsn(ALOAD, length); 354 mv 355 .visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", 356 "(Ljava/lang/Throwable;)V", false); 357 mv.visitInsn(ATHROW); 358 } 359 } 360 } 361 362 // finish this method 363 mv.visitMaxs(0, 0); 364 mv.visitEnd(); 365 } 366 367 private void pushMethodParameterArray(final MethodVisitor mv, final Class<?>[] parameterTypes) { 368 // need to construct the array of objects passed in 369 // create the Object[] 370 createArrayDefinition(mv, parameterTypes.length, Object.class); 371 372 int index = 1; 373 // push parameters into array 374 for (int i = 0; i < parameterTypes.length; i++) { 375 // keep copy of array on stack 376 mv.visitInsn(DUP); 377 378 final Class<?> parameterType = parameterTypes[i]; 379 380 // push number onto stack 381 pushIntOntoStack(mv, i); 382 383 if (parameterType.isPrimitive()) { 384 final String wrapperType = getWrapperType(parameterType); 385 mv.visitVarInsn(getVarInsn(parameterType), index); 386 387 mv 388 .visitMethodInsn(INVOKESTATIC, wrapperType, "valueOf", 389 "(" + Type.getDescriptor(parameterType) + ")L" + wrapperType + ";", false); 390 mv.visitInsn(AASTORE); 391 392 if (Long.TYPE.equals(parameterType) || Double.TYPE.equals(parameterType)) { 393 index += 2; 394 } else { 395 index++; 396 } 397 } else { 398 mv.visitVarInsn(ALOAD, index); 399 mv.visitInsn(AASTORE); 400 index++; 401 } 402 } 403 } 404 405 private int getVarInsn(final Class<?> type) { 406 if (Integer.TYPE.equals(type)) { 407 return ILOAD; 408 } else if (Boolean.TYPE.equals(type)) { 409 return ILOAD; 410 } else if (Character.TYPE.equals(type)) { 411 return ILOAD; 412 } else if (Byte.TYPE.equals(type)) { 413 return ILOAD; 414 } else if (Short.TYPE.equals(type)) { 415 return ILOAD; 416 } else if (Float.TYPE.equals(type)) { 417 return FLOAD; 418 } else if (Long.TYPE.equals(type)) { 419 return LLOAD; 420 } else if (Double.TYPE.equals(type)) { 421 return DLOAD; 422 } 423 throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type"); 424 } 425 426 private void pushIntOntoStack(final MethodVisitor mv, final int i) { 427 if (i == 0) { 428 mv.visitInsn(ICONST_0); 429 } else if (i == 1) { 430 mv.visitInsn(ICONST_1); 431 } else if (i == 2) { 432 mv.visitInsn(ICONST_2); 433 } else if (i == 3) { 434 mv.visitInsn(ICONST_3); 435 } else if (i == 4) { 436 mv.visitInsn(ICONST_4); 437 } else if (i == 5) { 438 mv.visitInsn(ICONST_5); 439 } else if (i > 5 && i <= 255) { 440 mv.visitIntInsn(BIPUSH, i); 441 } else { 442 mv.visitIntInsn(SIPUSH, i); 443 } 444 } 445 446 private void createArrayDefinition(final MethodVisitor mv, final int size, final Class<?> type) { 447 if (size < 0) { 448 throw new IllegalStateException("Array size cannot be less than zero"); 449 } 450 451 pushIntOntoStack(mv, size); 452 453 mv.visitTypeInsn(ANEWARRAY, type.getCanonicalName().replace('.', '/')); 454 } 455 456 private <T> String getSignedClassProxyName(final Class<T> classToProxy) { 457 // avoid java.lang.SecurityException: class's signer information 458 // does not match signer information of other classes in the same package 459 return "org.talend.generated.proxy.signed." + classToProxy.getName(); 460 } 461 462 private String fixPreservedPackages(final String name) { 463 String proxyClassName = name; 464 proxyClassName = fixPreservedPackage(proxyClassName, "java."); 465 proxyClassName = fixPreservedPackage(proxyClassName, "javax."); 466 proxyClassName = fixPreservedPackage(proxyClassName, "sun.misc."); 467 return proxyClassName; 468 } 469 470 private String fixPreservedPackage(final String className, final String forbiddenPackagePrefix) { 471 String fixedClassName = className; 472 473 if (className.startsWith(forbiddenPackagePrefix)) { 474 fixedClassName = 475 "org.talend.generated.proxy.custom." + className.substring(forbiddenPackagePrefix.length()); 476 } 477 478 return fixedClassName; 479 } 480 481 private int findJavaVersion(final Class<?> from) { 482 final String resource = from.getName().replace('.', '/') + ".class"; 483 try (final InputStream stream = from.getClassLoader().getResourceAsStream(resource)) { 484 if (stream == null) { 485 return javaVersion; 486 } 487 final ClassReader reader = new ClassReader(stream); 488 final VersionVisitor visitor = new VersionVisitor(); 489 reader.accept(visitor, SKIP_DEBUG + SKIP_CODE + SKIP_FRAMES); 490 if (visitor.version != 0) { 491 return visitor.version; 492 } 493 } catch (final Exception e) { 494 // no-op 495 } 496 // mainly for JVM classes - outside the classloader, find to fallback on the JVM 497 // version 498 return javaVersion; 499 } 500 501 public boolean hasInterceptors(final Class<?> type) { 502 return Stream 503 .concat(Stream.of(type.getAnnotations()), 504 Stream.of(type.getMethods()).flatMap(m -> Stream.of(m.getAnnotations()))) 505 .anyMatch(this::isInterceptor); 506 } 507 508 private boolean isInterceptor(final Annotation a) { 509 return a.annotationType().isAnnotationPresent(Intercepts.class); 510 } 511 512 public boolean isSerializable(final Class<?> type) { 513 if (Serializable.class.isAssignableFrom(type)) { 514 return true; 515 } 516 try { 517 type.getMethod("writeReplace"); 518 return true; 519 } catch (final NoSuchMethodException e) { 520 // no-op, let's try to generate a proxy 521 } 522 return false; 523 } 524 525 public Class<?> generateProxy(final ClassLoader loader, final Class<?> classToProxy, final String plugin, 526 final String key) { 527 final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 528 final String proxyClassName = fixPreservedPackages( 529 (classToProxy.getSigners() != null ? getSignedClassProxyName(classToProxy) : classToProxy.getName()) 530 + "$$TalendServiceProxy"); 531 final String classFileName = proxyClassName.replace('.', '/'); 532 533 final String[] interfaceNames = { Type.getInternalName(Serializable.class) }; 534 final String superClassName = Type.getInternalName(classToProxy); 535 536 cw 537 .visit(findJavaVersion(classToProxy), ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC, classFileName, null, 538 superClassName, interfaceNames); 539 cw.visitSource(classFileName + ".java", null); 540 541 if (!Serializable.class.isAssignableFrom(classToProxy)) { 542 try { 543 classToProxy.getMethod("writeReplace"); 544 } catch (final NoSuchMethodException e) { 545 createSerialisation(cw, plugin, key); 546 } 547 } 548 549 final boolean hasInterceptors = hasInterceptors(classToProxy); 550 if (hasInterceptors) { 551 cw 552 .visitField(ACC_PRIVATE, FIELD_INTERCEPTOR_HANDLER, Type.getDescriptor(InterceptorHandler.class), 553 null, null) 554 .visitEnd(); 555 cw 556 .visitField(ACC_PRIVATE | ACC_STATIC, FIELD_INTERCEPTED_METHODS, Type.getDescriptor(Method[].class), 557 null, null) 558 .visitEnd(); 559 } 560 561 createConstructor(cw, classToProxy, superClassName, classFileName, 562 Stream.of(classToProxy.getDeclaredConstructors()).filter(c -> { 563 final int modifiers = c.getModifiers(); 564 return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers); 565 }).sorted((o1, o2) -> { // prefer public constructor and then the smallest parameter count 566 final int mod1 = o1.getModifiers(); 567 final int mod2 = o2.getModifiers(); 568 if (Modifier.isProtected(mod1) && !Modifier.isPublic(mod2)) { 569 return 1; 570 } 571 if (Modifier.isProtected(mod2) && !Modifier.isPublic(mod1)) { 572 return -1; 573 } 574 575 return o1.getParameterCount() - o2.getParameterCount(); 576 }) 577 .findFirst() 578 .orElseThrow(() -> new IllegalArgumentException( 579 classToProxy + " has no default constructor, put at least a protected one")), 580 hasInterceptors); 581 582 final Method[] interceptedMethods; 583 if (hasInterceptors) { 584 final Collection<Annotation> globalInterceptors = 585 Stream.of(classToProxy.getAnnotations()).filter(this::isInterceptor).collect(toList()); 586 final AtomicInteger methodIndex = new AtomicInteger(); 587 interceptedMethods = Stream 588 .of(classToProxy.getMethods()) 589 .filter(m -> !"<init>".equals(m.getName()) && (!globalInterceptors.isEmpty() 590 || Stream.of(m.getAnnotations()).anyMatch(this::isInterceptor))) 591 .peek(method -> delegateMethod(cw, method, classFileName, methodIndex.getAndIncrement())) 592 .toArray(Method[]::new); 593 } else { 594 interceptedMethods = null; 595 } 596 597 final Class<Object> objectClass = Unsafes.defineAndLoadClass(loader, proxyClassName, cw.toByteArray()); 598 if (hasInterceptors) { 599 try { 600 final Field interceptedMethodsField = objectClass.getDeclaredField(FIELD_INTERCEPTED_METHODS); 601 interceptedMethodsField.setAccessible(true); 602 interceptedMethodsField.set(null, interceptedMethods); 603 } catch (final Exception e) { 604 throw new IllegalStateException(e); 605 } 606 } 607 return objectClass; 608 } 609 610 public void initialize(final Object proxy, final InterceptorHandler handler) { 611 try { 612 final Field invocationHandlerField = proxy.getClass().getDeclaredField(FIELD_INTERCEPTOR_HANDLER); 613 invocationHandlerField.setAccessible(true); 614 invocationHandlerField.set(proxy, handler); 615 } catch (final IllegalAccessException | NoSuchFieldException e) { 616 throw new IllegalStateException(e); 617 } 618 } 619 620 public InterceptorHandlerFacade getHandler(final Object instance) { 621 try { 622 final Field invocationHandlerField = instance.getClass().getDeclaredField(FIELD_INTERCEPTOR_HANDLER); 623 if (!invocationHandlerField.isAccessible()) { 624 invocationHandlerField.setAccessible(true); 625 } 626 return InterceptorHandlerFacade.class.cast(invocationHandlerField.get(instance)); 627 } catch (final IllegalAccessException | NoSuchFieldException e) { 628 throw new IllegalStateException(e); 629 } 630 } 631 632 private String getWrapperType(final Class<?> type) { 633 if (Integer.TYPE.equals(type)) { 634 return Integer.class.getCanonicalName().replace('.', '/'); 635 } else if (Boolean.TYPE.equals(type)) { 636 return Boolean.class.getCanonicalName().replace('.', '/'); 637 } else if (Character.TYPE.equals(type)) { 638 return Character.class.getCanonicalName().replace('.', '/'); 639 } else if (Byte.TYPE.equals(type)) { 640 return Byte.class.getCanonicalName().replace('.', '/'); 641 } else if (Short.TYPE.equals(type)) { 642 return Short.class.getCanonicalName().replace('.', '/'); 643 } else if (Float.TYPE.equals(type)) { 644 return Float.class.getCanonicalName().replace('.', '/'); 645 } else if (Long.TYPE.equals(type)) { 646 return Long.class.getCanonicalName().replace('.', '/'); 647 } else if (Double.TYPE.equals(type)) { 648 return Double.class.getCanonicalName().replace('.', '/'); 649 } else if (Void.TYPE.equals(type)) { 650 return Void.class.getCanonicalName().replace('.', '/'); 651 } 652 653 throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type"); 654 } 655 656 private int getReturnInsn(final Class<?> type) { 657 if (type.isPrimitive()) { 658 if (Void.TYPE.equals(type)) { 659 return RETURN; 660 } 661 if (Integer.TYPE.equals(type)) { 662 return IRETURN; 663 } 664 if (Boolean.TYPE.equals(type)) { 665 return IRETURN; 666 } 667 if (Character.TYPE.equals(type)) { 668 return IRETURN; 669 } 670 if (Byte.TYPE.equals(type)) { 671 return IRETURN; 672 } 673 if (Short.TYPE.equals(type)) { 674 return IRETURN; 675 } 676 if (Float.TYPE.equals(type)) { 677 return FRETURN; 678 } 679 if (Long.TYPE.equals(type)) { 680 return LRETURN; 681 } 682 if (Double.TYPE.equals(type)) { 683 return DRETURN; 684 } 685 } 686 687 return ARETURN; 688 } 689 690 private String getCastType(final Class<?> returnType) { 691 if (returnType.isPrimitive()) { 692 return getWrapperType(returnType); 693 } 694 return Type.getInternalName(returnType); 695 } 696 697 private String getPrimitiveMethod(final Class<?> type) { 698 if (Integer.TYPE.equals(type)) { 699 return "intValue"; 700 } else if (Boolean.TYPE.equals(type)) { 701 return "booleanValue"; 702 } else if (Character.TYPE.equals(type)) { 703 return "charValue"; 704 } else if (Byte.TYPE.equals(type)) { 705 return "byteValue"; 706 } else if (Short.TYPE.equals(type)) { 707 return "shortValue"; 708 } else if (Float.TYPE.equals(type)) { 709 return "floatValue"; 710 } else if (Long.TYPE.equals(type)) { 711 return "longValue"; 712 } else if (Double.TYPE.equals(type)) { 713 return "doubleValue"; 714 } 715 716 throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type"); 717 } 718 719 Object writeReplace() throws ObjectStreamException { 720 return new Replacer(); 721 } 722 723 @AllArgsConstructor 724 private static class Replacer implements Serializable { 725 726 Object readResolve() throws ObjectStreamException { 727 return ProxyGenerator.SINGLETON; 728 } 729 } 730 731 private static class VersionVisitor extends EmptyVisitor { 732 733 private int version; 734 735 @Override 736 public void visit(final int version, final int access, final String name, final String signature, 737 final String superName, final String[] interfaces) { 738 super.visit(version, access, name, signature, superName, interfaces); 739 this.version = version; 740 } 741 } 742}