Skip to content

Commit 6e4053b

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
feat: add abstract class adapter fallback in NewProxy
When Proxy.newProxyInstance fails (which happens for abstract classes like ScanCallback and BluetoothGattCallback), NewProxy now tries to find a pre-compiled adapter class that extends the abstract class and delegates to GoAbstractDispatch.invoke(). Adapter lookup convention: 1. center.dx.jni.generated.<SimpleClassName>Adapter 2. center.dx.gatt.internal.Go<SimpleClassName> The adapter class must have a constructor taking a single long (the handler ID). If no adapter is found, the original error is returned.
1 parent 4ff837c commit 6e4053b

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

proxy.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package jni
33
import (
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.
492591
func throwGoError(

0 commit comments

Comments
 (0)