2828import java .lang .reflect .Constructor ;
2929import java .lang .reflect .Method ;
3030import java .lang .reflect .Modifier ;
31+ import java .util .ArrayList ;
32+ import java .util .Arrays ;
33+ import java .util .HashMap ;
3134import java .util .List ;
3235import java .util .Map ;
3336import java .util .WeakHashMap ;
@@ -149,10 +152,10 @@ private boolean hasLegacyVaadin() {
149152 return version <= 24 ;
150153 }
151154
152- private static Stream <Method > getDeclaredCallables (Class <?> parent ) {
153- return Stream .of (parent .getDeclaredMethods ()).filter (method -> {
155+ private static Stream <Method > getDeclaredCallables (Class <?> clazz ) {
156+ return Stream .of (clazz .getDeclaredMethods ()).filter (method -> {
154157 int modifiers = method .getModifiers ();
155- if (!Modifier .isStatic (modifiers ) && ! Modifier . isPrivate ( modifiers ) ) {
158+ if (!Modifier .isStatic (modifiers )) {
156159 boolean isCallable = method .isAnnotationPresent (ClientCallable .class );
157160 boolean isLegacyCallable = method .isAnnotationPresent (LegacyClientCallable .class );
158161 return isCallable || isLegacyCallable ;
@@ -161,8 +164,28 @@ private static Stream<Method> getDeclaredCallables(Class<?> parent) {
161164 });
162165 }
163166
167+ private static Stream <Method > getAllCallables (Class <?> baseClass ) {
168+ Map <String , Method > map = new HashMap <>();
169+ for (Class <?> clazz = baseClass ; clazz != Component .class ; clazz = clazz .getSuperclass ()) {
170+ getDeclaredCallables (clazz ).forEach (method -> {
171+ Method existing = map .get (method .getName ());
172+ if (existing != null
173+ && !Arrays .equals (existing .getParameterTypes (), method .getParameterTypes ())) {
174+ String msg = String .format ("There may be only one handler method with the given name. "
175+ + "Class '%s' (considering its superclasses) "
176+ + "contains several handler methods with the same name: '%s'" ,
177+ baseClass .getName (), method .getName ());
178+ throw new IllegalStateException (msg );
179+ } else {
180+ map .put (method .getName (), method );
181+ }
182+ });
183+ }
184+ return map .values ().stream ();
185+ }
186+
164187 private List <Method > getInstrumentableMethods (Class <?> parent ) {
165- return getDeclaredCallables (parent ).filter (method -> {
188+ return getAllCallables (parent ).filter (method -> {
166189 boolean isCallable = method .isAnnotationPresent (ClientCallable .class );
167190 boolean isLegacyCallable = method .isAnnotationPresent (LegacyClientCallable .class );
168191 boolean hasJsonValueReturn = JsonValue .class .isAssignableFrom (method .getReturnType ());
@@ -238,7 +261,7 @@ private byte[] generateBytecode(String className, Class<?> parent) {
238261 cw .visit (Opcodes .V1_8 , Opcodes .ACC_PUBLIC , internalClassName , null , internalParentName , null );
239262
240263 generateConstructor (cw , internalParentName );
241- generateClientCallableOverrides (cw , parent , internalParentName );
264+ generateClientCallableOverrides (cw , parent , internalClassName , internalParentName );
242265
243266 cw .visitEnd ();
244267 return cw .toByteArray ();
@@ -255,13 +278,99 @@ private void generateConstructor(ClassWriter cw, String internalParentName) {
255278 }
256279
257280 private void generateClientCallableOverrides (ClassWriter cw , Class <?> parent ,
258- String internalParentName ) {
281+ String internalClassName , String internalParentName ) {
282+ List <String > privateMethodNames = new ArrayList <>();
259283 for (Method method : getInstrumentableMethods (parent )) {
260- generateMethodOverride (cw , method , internalParentName );
284+ if (Modifier .isPrivate (method .getModifiers ())) {
285+ privateMethodNames .add (method .getName ());
286+ createLookupHelper (cw , method );
287+ cw .visitField (Opcodes .ACC_PRIVATE | Opcodes .ACC_STATIC | Opcodes .ACC_FINAL ,
288+ method .getName (),
289+ Type .getDescriptor (MethodHandle .class ), null , null );
290+ }
291+ generateMethodOverride (cw , method , internalClassName , internalParentName );
292+ }
293+
294+ if (!privateMethodNames .isEmpty ()) {
295+ MethodVisitor mv = cw .visitMethod (Opcodes .ACC_STATIC , "<clinit>" , "()V" , null , null );
296+ mv .visitCode ();
297+ for (String name : privateMethodNames ) {
298+ mv .visitMethodInsn (Opcodes .INVOKESTATIC ,
299+ internalClassName ,
300+ "lookup_" +name ,
301+ "()" +Type .getDescriptor (MethodHandle .class ),
302+ false );
303+ mv .visitFieldInsn (Opcodes .PUTSTATIC ,
304+ internalClassName , name ,
305+ "Ljava/lang/invoke/MethodHandle;" );
306+ }
307+ mv .visitInsn (Opcodes .RETURN );
308+ mv .visitMaxs (0 , 0 );
309+ mv .visitEnd ();
261310 }
262311 }
263312
264- private void generateMethodOverride (ClassWriter cw , Method method , String internalParentName ) {
313+ private void createLookupHelper (ClassWriter cw , Method method ) {
314+ MethodVisitor mv = cw .visitMethod (Opcodes .ACC_PRIVATE | Opcodes .ACC_STATIC ,
315+ "lookup_" + method .getName (), "()" + Type .getDescriptor (MethodHandle .class ), null , null );
316+
317+ // Invoke static MethodHandles.lookup()
318+ mv .visitMethodInsn (Opcodes .INVOKESTATIC ,
319+ "java/lang/invoke/MethodHandles" ,
320+ "lookup" ,
321+ "()Ljava/lang/invoke/MethodHandles$Lookup;" ,
322+ false );
323+
324+ // Load the Owner class
325+ mv .visitLdcInsn (Type .getObjectType (Type .getInternalName (method .getDeclaringClass ())));
326+
327+ // Load the Method Name
328+ mv .visitLdcInsn (method .getName ());
329+
330+ // Create Class[] array
331+ Class <?> argTypes [] = method .getParameterTypes ();
332+ pushInt (mv , (short ) argTypes .length );
333+ mv .visitTypeInsn (Opcodes .ANEWARRAY , "java/lang/Class" );
334+
335+ // Load the specific Class objects and store in array
336+ for (short i = 0 ; i < argTypes .length ; i ++) {
337+ mv .visitInsn (Opcodes .DUP );
338+ pushInt (mv , i );
339+ loadClassConstant (mv , argTypes [i ]);
340+ mv .visitInsn (Opcodes .AASTORE );
341+ }
342+
343+ // Invoke getDeclaredMethod
344+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
345+ "java/lang/Class" ,
346+ "getDeclaredMethod" ,
347+ "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;" ,
348+ false );
349+
350+ // Invoke method.setAccessible(true)
351+ mv .visitInsn (Opcodes .DUP );
352+ mv .visitInsn (Opcodes .ICONST_1 );
353+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
354+ "java/lang/reflect/Method" ,
355+ "setAccessible" ,
356+ "(Z)V" ,
357+ false );
358+
359+ // Invoke Lookup.unresolve(method)
360+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
361+ "java/lang/invoke/MethodHandles$Lookup" ,
362+ "unreflect" ,
363+ "(Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;" ,
364+ false );
365+
366+ // Return result
367+ mv .visitInsn (Opcodes .ARETURN );
368+
369+ mv .visitMaxs (0 , 0 );
370+ mv .visitEnd ();
371+ }
372+
373+ private void generateMethodOverride (ClassWriter cw , Method method , String internalClassName , String internalParentName ) {
265374 boolean hasJsonValueReturn = !hasLegacyVaadin () && JsonValue .class .isAssignableFrom (method .getReturnType ());
266375 boolean hasJsonValueParams = !hasLegacyVaadin () && hasJsonValueParameters (method );
267376
@@ -275,6 +384,14 @@ private void generateMethodOverride(ClassWriter cw, Method method, String intern
275384 mv .visitAnnotation (Type .getDescriptor (ClientCallable .class ), true );
276385 mv .visitCode ();
277386
387+ boolean isPrivate = Modifier .isPrivate (method .getModifiers ());
388+ if (isPrivate ) {
389+ // Load MethodHandle from static field
390+ mv .visitFieldInsn (Opcodes .GETSTATIC ,
391+ internalClassName , method .getName (),
392+ "Ljava/lang/invoke/MethodHandle;" );
393+ }
394+
278395 // Load 'this'
279396 mv .visitVarInsn (Opcodes .ALOAD , 0 );
280397
@@ -301,9 +418,24 @@ private void generateMethodOverride(ClassWriter cw, Method method, String intern
301418 }
302419 }
303420
304- // Call super.methodName(params) with original descriptor
305- mv .visitMethodInsn (Opcodes .INVOKESPECIAL , internalParentName , method .getName (), superDescriptor ,
306- false );
421+ if (isPrivate ) {
422+ // Call private method
423+ String descriptor =
424+ "(" + Type .getDescriptor (method .getDeclaringClass ())
425+ + superDescriptor .substring (1 );
426+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
427+ "java/lang/invoke/MethodHandle" ,
428+ "invokeExact" ,
429+ descriptor ,
430+ false );
431+ } else {
432+ // Call super.methodName(params) with original descriptor
433+ mv .visitMethodInsn (Opcodes .INVOKESPECIAL ,
434+ internalParentName ,
435+ method .getName (),
436+ superDescriptor ,
437+ false );
438+ }
307439
308440 if (hasJsonValueReturn ) {
309441 // Store result in local variable
@@ -339,6 +471,42 @@ private void generateMethodOverride(ClassWriter cw, Method method, String intern
339471 mv .visitEnd ();
340472 }
341473
474+ private void pushInt (MethodVisitor mv , short value ) {
475+ if (value >= -1 && value <= 5 ) {
476+ mv .visitInsn (Opcodes .ICONST_0 + value );
477+ } else if (value >= Byte .MIN_VALUE && value <= Byte .MAX_VALUE ) {
478+ mv .visitIntInsn (Opcodes .BIPUSH , value );
479+ } else {
480+ mv .visitIntInsn (Opcodes .SIPUSH , value );
481+ }
482+ }
483+
484+ private void loadClassConstant (MethodVisitor mv , Class <?> clazz ) {
485+ if (clazz .isPrimitive ()) {
486+ if (clazz == int .class ) {
487+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Integer" , "TYPE" , "Ljava/lang/Class;" );
488+ } else if (clazz == boolean .class ) {
489+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Boolean" , "TYPE" , "Ljava/lang/Class;" );
490+ } else if (clazz == byte .class ) {
491+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Byte" , "TYPE" , "Ljava/lang/Class;" );
492+ } else if (clazz == char .class ) {
493+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Character" , "TYPE" , "Ljava/lang/Class;" );
494+ } else if (clazz == short .class ) {
495+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Short" , "TYPE" , "Ljava/lang/Class;" );
496+ } else if (clazz == float .class ) {
497+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Float" , "TYPE" , "Ljava/lang/Class;" );
498+ } else if (clazz == long .class ) {
499+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Long" , "TYPE" , "Ljava/lang/Class;" );
500+ } else if (clazz == double .class ) {
501+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Double" , "TYPE" , "Ljava/lang/Class;" );
502+ } else {
503+ throw new IllegalArgumentException ("Unsupported type: " + clazz );
504+ }
505+ } else {
506+ mv .visitLdcInsn (Type .getType (clazz ));
507+ }
508+ }
509+
342510 private int loadParameter (MethodVisitor mv , Class <?> paramType , int localVarIndex ) {
343511 if (!paramType .isPrimitive ()) {
344512 mv .visitVarInsn (Opcodes .ALOAD , localVarIndex );
0 commit comments