@@ -3,6 +3,7 @@ package jni
33import (
44 "encoding/binary"
55 "fmt"
6+ "strings"
67 "sync"
78 "sync/atomic"
89 "unsafe"
@@ -472,6 +473,20 @@ func newProxyImpl(
472473 proxyObj := capi .CallStaticObjectMethodA (e .ptr , clsProxy , midNewProxyInstance , & args [0 ])
473474 if capi .ExceptionCheck (e .ptr ) == capi .JNI_TRUE {
474475 capi .ExceptionClear (e .ptr )
476+
477+ // Proxy.newProxyInstance only works with interfaces. For abstract
478+ // classes, try to find a pre-compiled adapter class that extends the
479+ // abstract class and delegates to GoAbstractDispatch.invoke().
480+ // Adapter naming convention: simple class name + "Adapter" in
481+ // package "center.dx.jni.generated".
482+ if len (ifaces ) == 1 {
483+ adapterObj := tryAbstractAdapter (e , ifaces [0 ], invHandler )
484+ if adapterObj != 0 {
485+ cleanup = func () { unregisterProxy (handlerID ) }
486+ return & Object {ref : adapterObj }, cleanup , nil
487+ }
488+ }
489+
475490 unregisterProxy (handlerID )
476491 return nil , nil , fmt .Errorf ("jni: %s: Proxy.newProxyInstance failed" , funcName )
477492 }
@@ -487,6 +502,90 @@ func newProxyImpl(
487502 return & Object {ref : proxyObj }, cleanup , nil
488503}
489504
505+ // tryAbstractAdapter attempts to instantiate a pre-compiled Java adapter
506+ // class for an abstract class. These adapters extend the abstract class
507+ // and delegate all method calls to GoAbstractDispatch.invoke(handlerID, ...).
508+ //
509+ // It searches for adapter classes using these naming conventions:
510+ // 1. "center/dx/jni/generated/<SimpleClassName>Adapter"
511+ // 2. "center/dx/gatt/internal/Go<SimpleClassName>"
512+ //
513+ // The adapter constructor must accept a single long parameter (handlerID).
514+ // Returns 0 if no adapter is found.
515+ func tryAbstractAdapter (
516+ e * Env ,
517+ targetClass * Class ,
518+ invHandler capi.Object ,
519+ ) capi.Object {
520+ // Get the class name via Class.getName(). Use the Env wrapper
521+ // to call toString() which returns a Java string we can convert.
522+ clsCls := capi .FindClass (e .ptr , newCString ("java/lang/Class" ))
523+ if clsCls == 0 {
524+ capi .ExceptionClear (e .ptr )
525+ return 0
526+ }
527+ defer capi .DeleteLocalRef (e .ptr , capi .Object (clsCls ))
528+
529+ getNameMID := capi .GetMethodID (e .ptr , clsCls , newCString ("getName" ), newCString ("()Ljava/lang/String;" ))
530+ if getNameMID == nil {
531+ capi .ExceptionClear (e .ptr )
532+ return 0
533+ }
534+
535+ nameObj := capi .CallObjectMethodA (e .ptr , capi .Object (targetClass .ref ), getNameMID , & capi.Jvalue {})
536+ if nameObj == 0 || capi .ExceptionCheck (e .ptr ) == capi .JNI_TRUE {
537+ capi .ExceptionClear (e .ptr )
538+ return 0
539+ }
540+ defer capi .DeleteLocalRef (e .ptr , nameObj )
541+
542+ fullName := e .GoString ((* String )(unsafe .Pointer (& Object {ref : nameObj })))
543+
544+ // Extract simple class name.
545+ simpleName := fullName
546+ if idx := strings .LastIndex (fullName , "." ); idx >= 0 {
547+ simpleName = fullName [idx + 1 :]
548+ }
549+
550+ // Try adapter class names.
551+ adapterNames := []string {
552+ "center/dx/jni/generated/" + simpleName + "Adapter" ,
553+ "center/dx/gatt/internal/Go" + simpleName ,
554+ }
555+
556+ for _ , name := range adapterNames {
557+ adapterCls := findClassWithFallback (e .ptr , newCString (name ), strings .ReplaceAll (name , "/" , "." ))
558+ if adapterCls == 0 {
559+ continue
560+ }
561+
562+ // Find constructor: <init>(long handlerID)
563+ ctorMID := capi .GetMethodID (e .ptr , adapterCls , newCString ("<init>" ), newCString ("(J)V" ))
564+ if ctorMID == nil {
565+ capi .ExceptionClear (e .ptr )
566+ capi .DeleteLocalRef (e .ptr , capi .Object (adapterCls ))
567+ continue
568+ }
569+
570+ // Get the handlerID from the GoInvocationHandler.
571+ handlerIDVal := capi .GetLongField (e .ptr , invHandler , fidHandlerID )
572+
573+ // Create adapter instance.
574+ var idArg capi.Jvalue
575+ binary .NativeEndian .PutUint64 (idArg [:], uint64 (handlerIDVal ))
576+ obj := capi .NewObjectA (e .ptr , adapterCls , ctorMID , & idArg )
577+ capi .DeleteLocalRef (e .ptr , capi .Object (adapterCls ))
578+ if obj == 0 || capi .ExceptionCheck (e .ptr ) == capi .JNI_TRUE {
579+ capi .ExceptionClear (e .ptr )
580+ continue
581+ }
582+
583+ return obj
584+ }
585+
586+ return 0
587+ }
588+
490589// throwGoError throws a Java RuntimeException with the given Go error
491590// message. Uses raw capi calls to avoid checkException recursion.
492591func throwGoError (
0 commit comments