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,27 @@ 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+ map .put (method .getName (), method );
174+ } else if (!Arrays .equals (existing .getParameterTypes (), method .getParameterTypes ())) {
175+ String msg = String .format ("There may be only one handler method with the given name. "
176+ + "Class '%s' (considering its superclasses) "
177+ + "contains several handler methods with the same name: '%s'" ,
178+ baseClass .getName (), method .getName ());
179+ throw new IllegalStateException (msg );
180+ }
181+ });
182+ }
183+ return map .values ().stream ();
184+ }
185+
164186 private List <Method > getInstrumentableMethods (Class <?> parent ) {
165- return getDeclaredCallables (parent ).filter (method -> {
187+ return getAllCallables (parent ).filter (method -> {
166188 boolean isCallable = method .isAnnotationPresent (ClientCallable .class );
167189 boolean isLegacyCallable = method .isAnnotationPresent (LegacyClientCallable .class );
168190 boolean hasJsonValueReturn = JsonValue .class .isAssignableFrom (method .getReturnType ());
@@ -238,7 +260,7 @@ private byte[] generateBytecode(String className, Class<?> parent) {
238260 cw .visit (Opcodes .V1_8 , Opcodes .ACC_PUBLIC , internalClassName , null , internalParentName , null );
239261
240262 generateConstructor (cw , internalParentName );
241- generateClientCallableOverrides (cw , parent , internalParentName );
263+ generateClientCallableOverrides (cw , parent , internalClassName , internalParentName );
242264
243265 cw .visitEnd ();
244266 return cw .toByteArray ();
@@ -255,13 +277,99 @@ private void generateConstructor(ClassWriter cw, String internalParentName) {
255277 }
256278
257279 private void generateClientCallableOverrides (ClassWriter cw , Class <?> parent ,
258- String internalParentName ) {
280+ String internalClassName , String internalParentName ) {
281+ List <String > privateMethodNames = new ArrayList <>();
259282 for (Method method : getInstrumentableMethods (parent )) {
260- generateMethodOverride (cw , method , internalParentName );
283+ if (Modifier .isPrivate (method .getModifiers ())) {
284+ privateMethodNames .add (method .getName ());
285+ createLookupHelper (cw , method );
286+ cw .visitField (Opcodes .ACC_PRIVATE | Opcodes .ACC_STATIC | Opcodes .ACC_FINAL ,
287+ method .getName (),
288+ Type .getDescriptor (MethodHandle .class ), null , null );
289+ }
290+ generateMethodOverride (cw , method , internalClassName , internalParentName );
261291 }
292+
293+ if (!privateMethodNames .isEmpty ()) {
294+ MethodVisitor mv = cw .visitMethod (Opcodes .ACC_STATIC , "<clinit>" , "()V" , null , null );
295+ mv .visitCode ();
296+ for (String name : privateMethodNames ) {
297+ mv .visitMethodInsn (Opcodes .INVOKESTATIC ,
298+ internalClassName ,
299+ "lookup_" +name ,
300+ "()" +Type .getDescriptor (MethodHandle .class ),
301+ false );
302+ mv .visitFieldInsn (Opcodes .PUTSTATIC ,
303+ internalClassName , name ,
304+ "Ljava/lang/invoke/MethodHandle;" );
305+ }
306+ mv .visitInsn (Opcodes .RETURN );
307+ mv .visitMaxs (0 , 0 );
308+ mv .visitEnd ();
309+ }
310+ }
311+
312+ private void createLookupHelper (ClassWriter cw , Method method ) {
313+ MethodVisitor mv = cw .visitMethod (Opcodes .ACC_PRIVATE | Opcodes .ACC_STATIC ,
314+ "lookup_" + method .getName (), "()" + Type .getDescriptor (MethodHandle .class ), null , null );
315+
316+ // Invoke static MethodHandles.lookup()
317+ mv .visitMethodInsn (Opcodes .INVOKESTATIC ,
318+ "java/lang/invoke/MethodHandles" ,
319+ "lookup" ,
320+ "()Ljava/lang/invoke/MethodHandles$Lookup;" ,
321+ false );
322+
323+ // Load the Owner class
324+ mv .visitLdcInsn (Type .getType (method .getDeclaringClass ()));
325+
326+ // Load the Method Name
327+ mv .visitLdcInsn (method .getName ());
328+
329+ // Create Class[] array
330+ Class <?> argTypes [] = method .getParameterTypes ();
331+ pushInt (mv , (short ) argTypes .length );
332+ mv .visitTypeInsn (Opcodes .ANEWARRAY , "java/lang/Class" );
333+
334+ // Load the specific Class objects and store in array
335+ for (short i = 0 ; i < argTypes .length ; i ++) {
336+ mv .visitInsn (Opcodes .DUP );
337+ pushInt (mv , i );
338+ loadClassConstant (mv , argTypes [i ]);
339+ mv .visitInsn (Opcodes .AASTORE );
340+ }
341+
342+ // Invoke getDeclaredMethod
343+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
344+ "java/lang/Class" ,
345+ "getDeclaredMethod" ,
346+ "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;" ,
347+ false );
348+
349+ // Invoke method.setAccessible(true)
350+ mv .visitInsn (Opcodes .DUP );
351+ mv .visitInsn (Opcodes .ICONST_1 );
352+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
353+ "java/lang/reflect/Method" ,
354+ "setAccessible" ,
355+ "(Z)V" ,
356+ false );
357+
358+ // Invoke Lookup.unresolve(method)
359+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
360+ "java/lang/invoke/MethodHandles$Lookup" ,
361+ "unreflect" ,
362+ "(Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;" ,
363+ false );
364+
365+ // Return result
366+ mv .visitInsn (Opcodes .ARETURN );
367+
368+ mv .visitMaxs (0 , 0 );
369+ mv .visitEnd ();
262370 }
263371
264- private void generateMethodOverride (ClassWriter cw , Method method , String internalParentName ) {
372+ private void generateMethodOverride (ClassWriter cw , Method method , String internalClassName , String internalParentName ) {
265373 boolean hasJsonValueReturn = !hasLegacyVaadin () && JsonValue .class .isAssignableFrom (method .getReturnType ());
266374 boolean hasJsonValueParams = !hasLegacyVaadin () && hasJsonValueParameters (method );
267375
@@ -275,6 +383,14 @@ private void generateMethodOverride(ClassWriter cw, Method method, String intern
275383 mv .visitAnnotation (Type .getDescriptor (ClientCallable .class ), true );
276384 mv .visitCode ();
277385
386+ boolean isPrivate = Modifier .isPrivate (method .getModifiers ());
387+ if (isPrivate ) {
388+ // Load MethodHandle from static field
389+ mv .visitFieldInsn (Opcodes .GETSTATIC ,
390+ internalClassName , method .getName (),
391+ "Ljava/lang/invoke/MethodHandle;" );
392+ }
393+
278394 // Load 'this'
279395 mv .visitVarInsn (Opcodes .ALOAD , 0 );
280396
@@ -301,9 +417,24 @@ private void generateMethodOverride(ClassWriter cw, Method method, String intern
301417 }
302418 }
303419
304- // Call super.methodName(params) with original descriptor
305- mv .visitMethodInsn (Opcodes .INVOKESPECIAL , internalParentName , method .getName (), superDescriptor ,
306- false );
420+ if (isPrivate ) {
421+ // Call private method
422+ String descriptor =
423+ "(" + Type .getDescriptor (method .getDeclaringClass ())
424+ + superDescriptor .substring (1 );
425+ mv .visitMethodInsn (Opcodes .INVOKEVIRTUAL ,
426+ "java/lang/invoke/MethodHandle" ,
427+ "invokeExact" ,
428+ descriptor ,
429+ false );
430+ } else {
431+ // Call super.methodName(params) with original descriptor
432+ mv .visitMethodInsn (Opcodes .INVOKESPECIAL ,
433+ internalParentName ,
434+ method .getName (),
435+ superDescriptor ,
436+ false );
437+ }
307438
308439 if (hasJsonValueReturn ) {
309440 // Store result in local variable
@@ -339,6 +470,42 @@ private void generateMethodOverride(ClassWriter cw, Method method, String intern
339470 mv .visitEnd ();
340471 }
341472
473+ private void pushInt (MethodVisitor mv , short value ) {
474+ if (value >= -1 && value <= 5 ) {
475+ mv .visitInsn (Opcodes .ICONST_0 + value );
476+ } else if (value >= Byte .MIN_VALUE && value <= Byte .MAX_VALUE ) {
477+ mv .visitIntInsn (Opcodes .BIPUSH , value );
478+ } else {
479+ mv .visitIntInsn (Opcodes .SIPUSH , value );
480+ }
481+ }
482+
483+ private void loadClassConstant (MethodVisitor mv , Class <?> clazz ) {
484+ if (clazz .isPrimitive ()) {
485+ if (clazz == int .class ) {
486+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Integer" , "TYPE" , "Ljava/lang/Class;" );
487+ } else if (clazz == boolean .class ) {
488+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Boolean" , "TYPE" , "Ljava/lang/Class;" );
489+ } else if (clazz == byte .class ) {
490+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Byte" , "TYPE" , "Ljava/lang/Class;" );
491+ } else if (clazz == char .class ) {
492+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Character" , "TYPE" , "Ljava/lang/Class;" );
493+ } else if (clazz == short .class ) {
494+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Short" , "TYPE" , "Ljava/lang/Class;" );
495+ } else if (clazz == float .class ) {
496+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Float" , "TYPE" , "Ljava/lang/Class;" );
497+ } else if (clazz == long .class ) {
498+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Long" , "TYPE" , "Ljava/lang/Class;" );
499+ } else if (clazz == double .class ) {
500+ mv .visitFieldInsn (Opcodes .GETSTATIC , "java/lang/Double" , "TYPE" , "Ljava/lang/Class;" );
501+ } else {
502+ throw new IllegalArgumentException ("Unsupported type: " + clazz );
503+ }
504+ } else {
505+ mv .visitLdcInsn (Type .getType (clazz ));
506+ }
507+ }
508+
342509 private int loadParameter (MethodVisitor mv , Class <?> paramType , int localVarIndex ) {
343510 if (!paramType .isPrimitive ()) {
344511 mv .visitVarInsn (Opcodes .ALOAD , localVarIndex );
0 commit comments