001/**
002 * Copyright (C) 2006-2025 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}