From a73c992290930e617c78241bae9fe20cb18a01a9 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Fri, 30 Jun 2023 11:36:33 +0200
Subject: GlueGen JavaCallback: Resolve key mapping of callback and associated
 resources via 'JavaCallbackKey' config and custom `SetCallback-KeyClass`

Updated unit test and doc accordingly.
Unit tests handle OpenAL's AL_SOFT_callback_buffer and AL_SOFT_events.

Tested global scope (no key, default) and 1 key (default) and 1 key (custom class).

Added more query functions, which all only take the `SetCallbackFunction` key arguments as specified.

Cleaned up JavaCallback* config class field naminig scheme.

Added 'synchronized (..Map) { }' block in crucial `SetCallbackFunction`,
rendering implementation thread safe.
---
 .../com/jogamp/gluegen/CMethodBindingEmitter.java  |  32 +-
 src/java/com/jogamp/gluegen/JavaConfiguration.java |  98 +++--
 src/java/com/jogamp/gluegen/JavaEmitter.java       |  62 ++--
 .../jogamp/gluegen/JavaMethodBindingEmitter.java   | 221 +++++++++--
 .../test/junit/generation/Test4JavaCallback.java   | 404 ++++++++++++++++++++-
 .../jogamp/gluegen/test/junit/generation/test2.c   |  80 ++++
 .../jogamp/gluegen/test/junit/generation/test2.cfg | 107 +++++-
 .../jogamp/gluegen/test/junit/generation/test2.h   |  32 +-
 8 files changed, 911 insertions(+), 125 deletions(-)

(limited to 'src')

diff --git a/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java
index 3975315..43d11b7 100644
--- a/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java
+++ b/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java
@@ -154,7 +154,7 @@ public class CMethodBindingEmitter extends FunctionEmitter {
 
     javaCallback = cfg.setFuncToJavaCallbackMap.get(binding.getName());
     if( null != javaCallback ) {
-        jcbNativeBasename = CodeGenUtils.capitalizeString( javaCallback.setFuncName+javaCallback.simpleCbClazzName.replace("_", "") );
+        jcbNativeBasename = CodeGenUtils.capitalizeString( javaCallback.setFuncName+javaCallback.cbSimpleClazzName.replace("_", "") );
         jcbCMethodEmitter = new CMethodBindingEmitter(javaCallback.cbFuncBinding,
                                                       unit, javaPackageName, javaClassName, isOverloadedBinding,
                                                       isJavaMethodStatic, forImplementingMethodCall,
@@ -324,12 +324,13 @@ public class CMethodBindingEmitter extends FunctionEmitter {
    */
   public final MachineDataInfo getMachineDataInfo() { return machDesc; }
 
+  private static final boolean DEBUG_JAVACALLBACK = false;
 
   @Override
   protected void emitReturnType()  {
     if( null != javaCallback ) {
         LOG.log(INFO, "BindCFunc.R.JavaCallback: {0}: {1}", binding.getName(), javaCallback);
-        final String userParamArgName = javaCallback.cbFuncBinding.getArgumentName(javaCallback.userParamIdx);
+        final String userParamArgName = javaCallback.cbFuncBinding.getArgumentName(javaCallback.cbFuncUserParamIdx);
         final Type cReturnType = javaCallback.cbFuncBinding.getCReturnType();
         final JavaType jretType = javaCallback.cbFuncBinding.getJavaReturnType();
         unit.emitln("typedef struct {");
@@ -362,7 +363,6 @@ public class CMethodBindingEmitter extends FunctionEmitter {
             unit.emitln("  T_"+jcbNativeBasename+"* cb = (T_"+jcbNativeBasename+"*) "+userParamArgName+";");
             unit.emitln("  // C Params: "+javaCallback.cbFuncBinding.getCParameterList(new StringBuilder(), false, null).toString());
             unit.emitln("  // J Params: "+javaCallback.cbFuncBinding.getJavaParameterList(new StringBuilder()).toString());
-            // unit.emitln("  fprintf(stderr, \"YYY Callback01 user %p, id %ld, msg %s\\n\", cb, id, msg);");
 
             if( !cReturnType.isVoid() ) {
                 unit.emit("  "+cReturnType.getCName()+" _res = ("+cReturnType.getCName()+") ");
@@ -371,10 +371,8 @@ public class CMethodBindingEmitter extends FunctionEmitter {
             }
             unit.emit("(*env)->Call" + CodeGenUtils.capitalizeString( jretType.getName() ) +"Method(env, cb->cbFunc, cb->cbMethodID, ");
             // javaCallback.cbFuncCEmitter.emitBodyPassCArguments();
-            if( 0 < jcbCMethodEmitter.emitJavaCallbackBodyPassJavaArguments(javaCallback) ) {
-                unit.emit(", ");
-            }
-            unit.emitln("cb->userParam);");
+            jcbCMethodEmitter.emitJavaCallbackBodyPassJavaArguments(javaCallback, "cb->userParam");
+            unit.emitln(");");
 
             // javaCallback.cbFuncCEmitter.emitBodyUserVariableAssignments();
             // javaCallback.cbFuncCEmitter.emitBodyVariablePostCallCleanup();
@@ -393,7 +391,7 @@ public class CMethodBindingEmitter extends FunctionEmitter {
   /* pp */ int emitJavaCallbackBodyCToJavaPreCall(final JavaCallbackInfo jcbi)  {
     int count = 0;
     for (int i = 0; i < binding.getNumArguments(); i++) {
-      if( i == jcbi.userParamIdx ) {
+      if( i == jcbi.cbFuncUserParamIdx ) {
         continue;
       }
       if( emitBodyMapCToJNIType(i, true /* addLocalVar */) ) {
@@ -402,18 +400,19 @@ public class CMethodBindingEmitter extends FunctionEmitter {
     }
     return count;
   }
-  /* pp */ int emitJavaCallbackBodyPassJavaArguments(final JavaCallbackInfo jcbi) {
+  /* pp */ int emitJavaCallbackBodyPassJavaArguments(final JavaCallbackInfo jcbi, final String userParamVarName) {
     int count = 0;
     boolean needsComma = false;
     for (int i = 0; i < binding.getNumArguments(); i++) {
-      if( i == jcbi.userParamIdx ) {
-        continue;
-      }
       if (needsComma) {
         unit.emit(", ");
         needsComma = false;
       }
-      unit.emit( binding.getArgumentName(i) + "_jni" );
+      if( i == jcbi.cbFuncUserParamIdx ) {
+          unit.emit( userParamVarName );
+      } else {
+          unit.emit( binding.getArgumentName(i) + "_jni" );
+      }
       needsComma = true;
       ++count;
     }
@@ -552,7 +551,9 @@ public class CMethodBindingEmitter extends FunctionEmitter {
         unit.emitln("  {");
         unit.emitln("    jlong v = (jlong) (intptr_t) "+nativeUserParamVarName+";");
         unit.emitln("    (*env)->SetLongArrayRegion(env, jnativeUserParam, 0, (jsize)1, &v);");
-        // unit.emitln("    fprintf(stderr, \"YYY MessageCallback01 user %p -> native %p\\n\", "+userParamArgName+", "+nativeUserParamVarName+");");
+        if( DEBUG_JAVACALLBACK ) {
+            unit.emitln("    fprintf(stderr, \"YYY user %p -> native %p\\n\", "+userParamArgName+", "+nativeUserParamVarName+");");
+        }
         unit.emitln("  }");
         unit.emitln();
     }
@@ -565,9 +566,10 @@ public class CMethodBindingEmitter extends FunctionEmitter {
     unit.emitln("}");
     unit.emitln();
     if( null != jcb ) {
+        final String capIfaceName = CodeGenUtils.capitalizeString( getInterfaceName() );
         unit.emitln("JNIEXPORT void JNICALL");
         unit.emit(JavaEmitter.getJNIMethodNamePrefix(getJavaPackageName(), getJavaClassName()));
-        unit.emitln("_release"+getInterfaceName()+"Impl(JNIEnv *env, jobject _unused, jlong jnativeUserParam) {");
+        unit.emitln("_release"+capIfaceName+"MapImpl(JNIEnv *env, jobject _unused, jlong jnativeUserParam) {");
         unit.emitln("  T_"+jcbNativeBasename+"* nativeUserParam = (T_"+jcbNativeBasename+"*) (intptr_t) jnativeUserParam;");
         unit.emitln("  if( NULL != nativeUserParam ) {");
         unit.emitln("    (*env)->DeleteGlobalRef(env, nativeUserParam->cbFunc);");
diff --git a/src/java/com/jogamp/gluegen/JavaConfiguration.java b/src/java/com/jogamp/gluegen/JavaConfiguration.java
index 17ac547..1fe2747 100644
--- a/src/java/com/jogamp/gluegen/JavaConfiguration.java
+++ b/src/java/com/jogamp/gluegen/JavaConfiguration.java
@@ -152,18 +152,21 @@ public class JavaConfiguration {
 
     /** JavaCallback configuration definition (static) */
     public static class JavaCallbackDef {
-      final String setFuncName;
       final String cbFuncTypeName;
-      final int userParamIdx;
-      JavaCallbackDef(final String setFuncName, final String cbFuncTypeName, final int userParamIdx) {
-          this.setFuncName = setFuncName;
+      final int cbFuncUserParamIdx;
+      final String setFuncName;
+      final List<Integer> setFuncKeyIndices = new ArrayList<Integer>();
+      final String setFuncKeyClassName; // optional
+      JavaCallbackDef(final String cbFuncTypeName, final int cbFuncUserParamIdx, final String setFuncName, final String setFuncKeyClassName) {
           this.cbFuncTypeName = cbFuncTypeName;
-          this.userParamIdx = userParamIdx;
+          this.cbFuncUserParamIdx = cbFuncUserParamIdx;
+          this.setFuncName = setFuncName;
+          this.setFuncKeyClassName = setFuncKeyClassName;
       }
       @Override
       public String toString() {
-          return String.format("JavaCallbackDef[set %s, cb %s, userParamIdx %d]",
-                  setFuncName, cbFuncTypeName, userParamIdx);
+          return String.format("JavaCallbackDef[cbFunc[type %s, userParamIdx %d], set[%s, keys %s, KeyClass %s]]",
+                  cbFuncTypeName, cbFuncUserParamIdx, setFuncName, setFuncKeyIndices.toString(), setFuncKeyClassName);
       }
     }
     private final List<JavaCallbackDef> javaCallbackList = new ArrayList<JavaCallbackDef>();
@@ -1377,6 +1380,8 @@ public class JavaConfiguration {
       readArgumentIsString(tok, filename, lineNo);
     } else if (cmd.equalsIgnoreCase("JavaCallbackDef")) {
       readJavaCallbackDef(tok, filename, lineNo);
+    } else if (cmd.equalsIgnoreCase("JavaCallbackKey")) {
+      readJavaCallbackKey(tok, filename, lineNo);
     } else if (cmd.equalsIgnoreCase("ExtendedInterfaceSymbolsIgnore")) {
       readExtendedIntfImplSymbols(tok, filename, lineNo, true, false, false);
     } else if (cmd.equalsIgnoreCase("ExtendedInterfaceSymbolsOnly")) {
@@ -1626,8 +1631,14 @@ public class JavaConfiguration {
     try {
       final String setFuncName = tok.nextToken();
       final String cbFuncTypeName = tok.nextToken();
-      final Integer userParamIdx = Integer.valueOf(tok.nextToken());
-      final JavaCallbackDef jcd = new JavaCallbackDef(setFuncName, cbFuncTypeName, userParamIdx);
+      final Integer cbFuncUserParamIdx = Integer.valueOf(tok.nextToken());
+      final String cbFuncKeyClassName;
+      if( tok.hasMoreTokens() ) {
+          cbFuncKeyClassName = tok.nextToken();
+      } else {
+          cbFuncKeyClassName = null;
+      }
+      final JavaCallbackDef jcd = new JavaCallbackDef(cbFuncTypeName, cbFuncUserParamIdx, setFuncName, cbFuncKeyClassName);
       javaCallbackList.add(jcd);
       javaCallbackSetFuncToDef.put(setFuncName, jcd);
     } catch (final NoSuchElementException e) {
@@ -1636,6 +1647,26 @@ public class JavaConfiguration {
     }
   }
 
+  protected void readJavaCallbackKey(final StringTokenizer tok, final String filename, final int lineNo) {
+    try {
+      final String setFuncName = tok.nextToken();
+      final JavaCallbackDef jcd = javaCallbackSetFuncToDef.get(setFuncName);
+      if( null == jcd ) {
+          throw new IllegalArgumentException("JavaCallbackDef '"+setFuncName+"\' not (yet) defined.");
+      }
+      while( tok.hasMoreTokens() ) {
+          final int idx = Integer.valueOf(tok.nextToken());
+          if( 0 > idx ) {
+              throw new IllegalArgumentException("JavaCallbackKey '"+setFuncName+"\' index "+idx+" not in range [0..n].");
+          }
+          jcd.setFuncKeyIndices.add( idx );
+      }
+    } catch (final NoSuchElementException e) {
+      throw new RuntimeException("Error parsing \"JavaCallbackKey\" command at line " + lineNo +
+        " in file \"" + filename + "\"", e);
+    }
+  }
+
   protected void readExtendedIntfImplSymbols(final StringTokenizer tok, final String filename, final int lineNo, final boolean forInterface, final boolean forImplementation, final boolean onlyList) {
     File javaFile;
     BufferedReader javaReader;
@@ -2245,44 +2276,53 @@ public class JavaConfiguration {
    * @see JavaConfiguration#setFuncToJavaCallbackMap
    */
   public static class JavaCallbackInfo {
-      final String setFuncName;
       final String cbFuncTypeName;
-      final String simpleCbClazzName;
-      final String fqCbClazzName;
+      final String cbSimpleClazzName;
+      final String cbFQClazzName;
       final String cbMethodSignature;
       final FunctionType cbFuncType;
       final MethodBinding cbFuncBinding;
-      final int userParamIdx;
+      final int cbFuncUserParamIdx;
+
       final Type userParamType;
       final String userParamName;
+
+      final String setFuncName;
+      final List<Integer> setFuncKeyIndices;
+      final String setFuncKeyClassName;
       boolean setFuncProcessed;
       int setFuncCBParamIdx;
       int setFuncUserParamIdx;
 
-      public JavaCallbackInfo(final String setFuncName, final String cbFuncTypeName, final String simpleClazzName, final String fqClazzName, final String methodSignature,
-                              final FunctionType cbFuncType, final MethodBinding cbFuncBinding, final int userParamIdx) {
-          this.setFuncName = setFuncName;
+      public JavaCallbackInfo(final String cbFuncTypeName, final String cbSimpleClazzName, final String cbFQClazzName, final String cbMethodSignature,
+                              final FunctionType cbFuncType, final MethodBinding cbFuncBinding, final int cbFuncUserParamIdx,
+                              final String setFuncName, final List<Integer> setFuncKeyIndices, final String setFuncKeyClassName) {
           this.cbFuncTypeName = cbFuncTypeName;
-          this.simpleCbClazzName = simpleClazzName;
-          this.fqCbClazzName = fqClazzName;
-          this.cbMethodSignature = methodSignature;
+          this.cbSimpleClazzName = cbSimpleClazzName;
+          this.cbFQClazzName = cbFQClazzName;
+          this.cbMethodSignature = cbMethodSignature;
           this.cbFuncType = cbFuncType;
           this.cbFuncBinding = cbFuncBinding;
           int paramIdx = -2;
           Type paramType = null;
           String paramName = null;
-          if( 0 <= userParamIdx && userParamIdx < cbFuncType.getNumArguments() ) {
-              final Type t = cbFuncType.getArgumentType(userParamIdx);
+          if( 0 <= cbFuncUserParamIdx && cbFuncUserParamIdx < cbFuncType.getNumArguments() ) {
+              final Type t = cbFuncType.getArgumentType(cbFuncUserParamIdx);
               if( null != t && t.isPointer() ) {
                   // OK '<something>*'
-                  paramIdx = userParamIdx;
-                  paramName = cbFuncType.getArgumentName(userParamIdx);
+                  paramIdx = cbFuncUserParamIdx;
+                  paramName = cbFuncType.getArgumentName(cbFuncUserParamIdx);
                   paramType = t.getTargetType();
               }
           }
-          this.userParamIdx = paramIdx;
+          this.cbFuncUserParamIdx = paramIdx;
+
           this.userParamType = paramType;
           this.userParamName = paramName;
+
+          this.setFuncName = setFuncName;
+          this.setFuncKeyIndices = setFuncKeyIndices;
+          this.setFuncKeyClassName = setFuncKeyClassName;
           this.setFuncProcessed = false;
           this.setFuncCBParamIdx = -1;
           this.setFuncUserParamIdx = -1;
@@ -2303,12 +2343,16 @@ public class JavaConfiguration {
 
       @Override
       public String toString() {
-          return String.format("JavaCallbackInfo[set %s(ok %b, cbIdx %d, upIdx %d), cb %s%s, userParam[idx %d, '%s', %s], %s]",
-                  setFuncName, setFuncProcessed, setFuncCBParamIdx, setFuncUserParamIdx,
+          return String.format("JavaCallbackInfo[cbFunc[%s%s, userParam[idx %d, '%s', %s], set[%s(ok %b, cbIdx %d, upIdx %d, keys %s, KeyClass '%s'], %s]",
                   cbFuncTypeName, cbMethodSignature,
-                  userParamIdx, userParamName, userParamType.getSignature(null).toString(), cbFuncType.toString(cbFuncTypeName, false, true));
+                  cbFuncUserParamIdx, userParamName, userParamType.getSignature(null).toString(),
+                  setFuncName, setFuncProcessed, setFuncCBParamIdx, setFuncUserParamIdx,
+                  setFuncKeyIndices.toString(), setFuncKeyClassName,
+                  cbFuncType.toString(cbFuncTypeName, false, true));
       }
   }
   /** Mapped binding name to {@link JavaCallbackInfo} */
   /* pp */ final Map<String, JavaCallbackInfo> setFuncToJavaCallbackMap = new HashMap<String, JavaCallbackInfo>();
+  final Set<String> emittedJavaCallbackUserParamClasses = new HashSet<String>();
+
 }
diff --git a/src/java/com/jogamp/gluegen/JavaEmitter.java b/src/java/com/jogamp/gluegen/JavaEmitter.java
index 7dad237..7674e1f 100644
--- a/src/java/com/jogamp/gluegen/JavaEmitter.java
+++ b/src/java/com/jogamp/gluegen/JavaEmitter.java
@@ -75,7 +75,6 @@ import com.jogamp.common.os.DynamicLookupHelper;
 import com.jogamp.common.os.MachineDataInfo;
 import com.jogamp.common.util.ArrayHashMap;
 import com.jogamp.gluegen.ASTLocusTag.ASTLocusTagProvider;
-import com.jogamp.gluegen.FunctionEmitter.EmissionModifier;
 import com.jogamp.gluegen.JavaConfiguration.JavaCallbackDef;
 import com.jogamp.gluegen.JavaConfiguration.JavaCallbackInfo;
 import com.jogamp.gluegen.Logging.LoggerIf;
@@ -1461,27 +1460,46 @@ public class JavaEmitter implements GlueEmitter {
       funcSym.addAliasedName(jcbd.cbFuncTypeName);
       LOG.log(INFO, "JavaCallback: fSym {0}, {1}", funcSym.getAliasedString(), jcbd);
 
-      final String simpleClazzName = CodeGenUtils.capitalizeString(jcbd.cbFuncTypeName);
-      final String fqClazzName = cfg.packageName()+"."+cfg.className()+"."+simpleClazzName;
-      final StringBuilder methodSignature = new StringBuilder();
-      javaUnit.emitln("  /** JavaCallback interface: "+jcbd.cbFuncTypeName+" -> "+funcType.toString(jcbd.cbFuncTypeName, false, true)+" */");
-      javaUnit.emitln("  public static interface "+simpleClazzName+" {");
-      final List<MethodBinding> mbs = generateFunctionInterfaceCode(javaUnit, funcSym, jcbd.userParamIdx, methodSignature);
-      javaUnit.emitln("  }");
-      javaUnit.emitln();
-      if( 1 != mbs.size() ) {
-          throw new UnsupportedOperationException("Multiple bindings generated where only 1 is allowed for func "+funcType.toString(jcbd.cbFuncTypeName, false, true));
-      }
-      final MethodBinding mb = mbs.get(0);
-      if( !mb.getJavaReturnType().isVoid() && !mb.getJavaReturnType().isPrimitive() ) {
-          throw new UnsupportedOperationException("Non void or non-primitive callback return types not suppored. Java "+
-                  mb.getJavaReturnType()+", func "+funcType.toString(jcbd.cbFuncTypeName, false, true));
-      }
-      final JavaCallbackInfo jcbi = new JavaCallbackInfo(jcbd.setFuncName, jcbd.cbFuncTypeName, simpleClazzName, fqClazzName,
-                                                         methodSignature.toString(), funcType, mb, jcbd.userParamIdx);
-      cfg.setFuncToJavaCallbackMap.put(jcbd.setFuncName, jcbi);
-      LOG.log(INFO, "JavaCallbackInfo: Added {0}", jcbi);
+      final String cbSimpleClazzName = CodeGenUtils.capitalizeString(jcbd.cbFuncTypeName);
+      final String cbFQClazzName = cfg.packageName()+"."+cfg.className()+"."+cbSimpleClazzName;
+
+      final JavaCallbackInfo jcbi0 = javaCallbackInterfaceMap.get(cbFQClazzName);
+      if( null != jcbi0 ) {
+          // Reuse callback-func interface, can't duplicate
+          if( jcbi0.cbFuncUserParamIdx != jcbd.cbFuncUserParamIdx ) {
+              throw new UnsupportedOperationException("Reused FuncTypeName "+jcbd.cbFuncTypeName+" used with different FuncUserParamIdx "+jcbi0.cbFuncUserParamIdx+" -> "+jcbd.cbFuncUserParamIdx+". Func "+
+                      funcType.toString(jcbd.cbFuncTypeName, false, true));
+          }
+          final JavaCallbackInfo jcbi1 = new JavaCallbackInfo(jcbd.cbFuncTypeName, cbSimpleClazzName, cbFQClazzName, jcbi0.cbMethodSignature,
+                                                              funcType, jcbi0.cbFuncBinding, jcbi0.cbFuncUserParamIdx,
+                                                              jcbd.setFuncName, jcbd.setFuncKeyIndices, jcbd.setFuncKeyClassName);
+          cfg.setFuncToJavaCallbackMap.put(jcbd.setFuncName, jcbi1);
+          LOG.log(INFO, "JavaCallbackInfo: Reusing {0} -> {1}", jcbd.setFuncName, jcbi0);
+      } else {
+          final StringBuilder cbMethodSignature = new StringBuilder();
+          javaUnit.emitln("  /** JavaCallback interface: "+jcbd.cbFuncTypeName+" -> "+funcType.toString(jcbd.cbFuncTypeName, false, true)+" */");
+          javaUnit.emitln("  public static interface "+cbSimpleClazzName+" {");
+          final List<MethodBinding> mbs = generateFunctionInterfaceCode(javaUnit, funcSym, jcbd.cbFuncUserParamIdx, cbMethodSignature);
+          javaUnit.emitln("  }");
+          javaUnit.emitln();
+          if( 1 != mbs.size() ) {
+              throw new UnsupportedOperationException("Multiple bindings generated where only 1 is allowed for func "+funcType.toString(jcbd.cbFuncTypeName, false, true));
+          }
+          final MethodBinding cbFuncBinding = mbs.get(0);
+          if( !cbFuncBinding.getJavaReturnType().isVoid() && !cbFuncBinding.getJavaReturnType().isPrimitive() ) {
+              throw new UnsupportedOperationException("Non void or non-primitive callback return types not suppored. Java "+
+                      cbFuncBinding.getJavaReturnType()+", func "+funcType.toString(jcbd.cbFuncTypeName, false, true));
+          }
+          final JavaCallbackInfo jcbi1 = new JavaCallbackInfo(jcbd.cbFuncTypeName, cbSimpleClazzName, cbFQClazzName, cbMethodSignature.toString(),
+                                                              funcType, cbFuncBinding, jcbd.cbFuncUserParamIdx,
+                                                              jcbd.setFuncName, jcbd.setFuncKeyIndices, jcbd.setFuncKeyClassName);
+          cfg.setFuncToJavaCallbackMap.put(jcbd.setFuncName, jcbi1);
+          javaCallbackInterfaceMap.put(cbFQClazzName, jcbi1);
+          LOG.log(INFO, "JavaCallbackInfo: Added {0} -> {1}", jcbd.setFuncName, jcbi1);
+      }
   }
+  private final Map<String, JavaCallbackInfo> javaCallbackInterfaceMap = new HashMap<String, JavaCallbackInfo>();
+
   private List<MethodBinding> generateFunctionInterfaceCode(final JavaCodeUnit javaUnit, final FunctionSymbol funcSym, final int userParamIdx, final StringBuilder methodSignature)  {
       // Emit method call and associated native code
       MethodBinding mb = bindFunction(funcSym, true  /* forInterface */, machDescJava, null, null);
@@ -3085,7 +3103,7 @@ public class JavaEmitter implements GlueEmitter {
       {
           // Replace JavaCallback type with generated interface name
           jcbiSetFuncCBParamIdx=i;
-          mappedType = JavaType.createForNamedClass( jcbi.fqCbClazzName );
+          mappedType = JavaType.createForNamedClass( jcbi.cbFQClazzName );
       } else if( null != jcbi && jcbi.userParamName.equals( cArgName ) &&
                  ( !jcbi.setFuncProcessed || i == jcbi.setFuncUserParamIdx ) &&
                  cArgType.isPointer() && jcbi.userParamType.equals( cArgType.getTargetType() ) )
diff --git a/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java
index 047a637..2bc453d 100644
--- a/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java
+++ b/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java
@@ -1,4 +1,4 @@
-/*
+/**
  * Copyright (c) 2010-2023 JogAmp Community. All rights reserved.
  * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
  *
@@ -50,6 +50,8 @@ import com.jogamp.gluegen.cgram.types.Type;
 
 import java.io.PrintWriter;
 import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -455,6 +457,114 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
     return getArgumentName(i) + "_offset";
   }
 
+  private static final boolean DEBUG_JAVACALLBACK = false;
+
+  private final void emitJavaCallbackKeyClass(final String KeyClassName) {
+      unit.emitln("  private static class "+KeyClassName+" {");
+      binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> {
+          if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) {
+              unit.emitln("    private final "+jType+" "+name+";");
+              return true;
+          } else {
+              return false;
+          }
+      } );
+      unit.emitln("    "+KeyClassName+"("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+") {");
+      binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> {
+          if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) {
+              unit.emitln("      this."+name+" = "+name+";");
+              return true;
+          } else {
+              return false;
+          }
+      } );
+      unit.emitln("    }");
+      unit.emitln("    @Override");
+      unit.emitln("    public boolean equals(final Object o) {");
+      unit.emitln("      if( this == o ) {");
+      unit.emitln("        return true;");
+      unit.emitln("      }");
+      unit.emitln("      if( !(o instanceof "+KeyClassName+") ) {");
+      unit.emitln("        return false;");
+      unit.emitln("      }");
+      {
+          final int count = binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> {
+              if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) {
+                  if( 0 == consumedCount ) {
+                      unit.emitln("      final "+KeyClassName+" o2 = ("+KeyClassName+")o;");
+                      unit.emit  ("      return ");
+                  } else {
+                      unit.emitln(" &&");
+                      unit.emit  ("             ");
+                  }
+                  if( jType.isPrimitive() || idx == javaCallback.setFuncUserParamIdx ) {
+                      unit.emit(name+" == o2."+name);
+                  } else {
+                      unit.emit(name+".equals( o2."+name+" )");
+                  }
+                  return true;
+              } else {
+                  return false;
+              }
+          } );
+          if( 0 == count ) {
+              unit.emit("      return true");
+          }
+          unit.emitln(";");
+      }
+      unit.emitln("    }");
+      unit.emitln("    @Override");
+      unit.emitln("    public int hashCode() {");
+      {
+          final int count = binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> {
+              if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) {
+                  if( 0 == consumedCount ) {
+                      unit.emitln("      // 31 * x == (x << 5) - x");
+                      unit.emit  ("      int hash = ");
+                  } else {
+                      unit.emit  ("      hash = ((hash << 5) - hash) + ");
+                  }
+                  if( jType.isPrimitive() ) {
+                      if( jType.isLong() ) {
+                          unit.emitln("Long.valueOf( "+name+" ).hashCode();");
+                      } else {
+                          unit.emitln(name+";");
+                      }
+                  } else {
+                      if( idx == javaCallback.setFuncUserParamIdx ) {
+                          unit.emitln("System.identityHashCode( "+name+" );");
+                      } else {
+                          unit.emitln(name+".hashCode();");
+                      }
+                  }
+                  return true;
+              } else {
+                  return false;
+              }
+          } );
+          if( 0 == count ) {
+              unit.emitln("      return 0;");
+          } else {
+              unit.emitln("      return hash;");
+          }
+      }
+      unit.emitln("    }");
+      unit.emitln("  }");
+      unit.emitln();
+  }
+  private final void emitJavaCallbackUsrParamClass(final String UsrParamClassName) {
+      unit.emitln("  private static class "+UsrParamClassName+" {");
+      unit.emitln("    final "+javaCallback.cbFuncTypeName+" func;");
+      unit.emitln("    final Object param;");
+      unit.emitln("    final long nativeParam;");
+      unit.emitln("    "+UsrParamClassName+"("+javaCallback.cbFuncTypeName+" func, Object param, long nativeParam) {");
+      unit.emitln("      this.func = func;");
+      unit.emitln("      this.param = param;");
+      unit.emitln("      this.nativeParam = nativeParam;");
+      unit.emitln("    }");
+      unit.emitln("  }");
+  }
+
   @Override
   protected void emitBody()  {
     if (!emitBody) {
@@ -474,37 +584,68 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
       unit.emitln("  }");
     }
     if( null != javaCallback && !isPrivateNativeMethod ) {
+        final String capIfaceName = CodeGenUtils.capitalizeString( getInterfaceName() );
+        final String lowIfaceName = CodeGenUtils.decapitalizeString( getInterfaceName() );
         unit.emitln();
-        final String userParamArgName = binding.getArgumentName(javaCallback.setFuncUserParamIdx);
-        unit.emit("  public boolean is"+getInterfaceName()+"Mapped(final Object "+userParamArgName+")");
         if( isInterface() ) {
-            unit.emitln(";");
+            unit.emitln("  public boolean is"+capIfaceName+"Mapped("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
+            unit.emitln("  public "+javaCallback.cbFuncTypeName+" get"+capIfaceName+"("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
+            unit.emitln("  public Object get"+capIfaceName+"UserParam("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
         } else {
-            unit.emitln(" {");
-            unit.emitln("    return null != "+javaCallback.cbFuncTypeName+"UsrMap.get("+userParamArgName+");");
+            final String usrMapInstanceName = lowIfaceName+"UsrMap";
+            final boolean customKeyClass;
+            final String KeyClassName;
+            if( null != javaCallback.setFuncKeyClassName ) {
+                customKeyClass = true;;
+                KeyClassName = javaCallback.setFuncKeyClassName;
+            } else {
+                customKeyClass = false;
+                KeyClassName = CodeGenUtils.capitalizeString(capIfaceName+"Key");
+            }
+            final String UsrParamClassName = CodeGenUtils.capitalizeString( javaCallback.cbFuncTypeName+"UserParam" );
+            final String fqUsrParamClassName = cfg.packageName()+"."+cfg.className()+"."+UsrParamClassName;
+            unit.emitln("  public final boolean is"+capIfaceName+"Mapped("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+") {");
+            unit.emitln("    final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
+            unit.emitln("    return null != "+usrMapInstanceName+".get(key);");
             unit.emitln("  }");
-            unit.emitln("  private final void release"+getInterfaceName()+"(final Object "+userParamArgName+") {");
-            unit.emitln("    final "+javaCallback.cbFuncTypeName+"UserParam v = "+javaCallback.cbFuncTypeName+"UsrMap.remove("+userParamArgName+");");
-            // unit.emitln("    System.err.println(\"ZZZ Release v \"+v+\", v.nativeParam 0x\"+Long.toHexString(null!=v?v.nativeParam:0));");
-            unit.emitln("    if( null != v ) {");
-            unit.emitln("      release"+getInterfaceName()+"Impl(v.nativeParam);");
-            unit.emitln("    }");
+            unit.emitln("  public final "+javaCallback.cbFuncTypeName+" get"+capIfaceName+"("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+") {");
+            unit.emitln("    final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
+            unit.emitln("    final "+UsrParamClassName+" value = "+usrMapInstanceName+".get(key);");
+            unit.emitln("    return null != value ? value.func : null;");
+            unit.emitln("  }");
+            unit.emitln("  public final Object get"+capIfaceName+"UserParam("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+") {");
+            unit.emitln("    final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
+            unit.emitln("    final "+UsrParamClassName+" value = "+usrMapInstanceName+".get(key);");
+            unit.emitln("    return null != value ? value.param : null;");
+            unit.emitln("  }");
+            unit.emitln("  private final void add"+capIfaceName+"Map("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, true).toString()+UsrParamClassName+" value) {");
+            unit.emitln("    final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
+            unit.emitln("    "+usrMapInstanceName+".put(key, value);");
+            if( DEBUG_JAVACALLBACK ) {
+                unit.emitln("    System.err.println(\"ZZZ Map \"+key+\" -> value.nativeParam 0x\"+Long.toHexString(null!=value?value.nativeParam:0));");
+            }
             unit.emitln("  }");
-            unit.emitln("  private static class "+javaCallback.cbFuncTypeName+"UserParam {");
-            unit.emitln("    @SuppressWarnings(\"unused\")");
-            unit.emitln("    final "+javaCallback.cbFuncTypeName+" func;");
-            unit.emitln("    @SuppressWarnings(\"unused\")");
-            unit.emitln("    final Object param;");
-            unit.emitln("    @SuppressWarnings(\"unused\")");
-            unit.emitln("    final long nativeParam;");
-            unit.emitln("    "+javaCallback.cbFuncTypeName+"UserParam("+javaCallback.cbFuncTypeName+" func, Object param, long nativeParam) {");
-            unit.emitln("      this.func = func;");
-            unit.emitln("      this.param = param;");
-            unit.emitln("      this.nativeParam = nativeParam;");
+            unit.emitln("  private final void release"+capIfaceName+"Map("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+") {");
+            unit.emitln("    final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");");
+            unit.emitln("    final "+UsrParamClassName+" value = "+usrMapInstanceName+".remove(key);");
+            if( DEBUG_JAVACALLBACK ) {
+                unit.emitln("    System.err.println(\"ZZZ Release \"+key+\" -> value.nativeParam 0x\"+Long.toHexString(null!=value?value.nativeParam:0));");
+            }
+            unit.emitln("    if( null != value ) {");
+            unit.emitln("      release"+capIfaceName+"MapImpl(value.nativeParam);");
             unit.emitln("    }");
             unit.emitln("  }");
-            unit.emitln("  private final java.util.Map<Object, "+javaCallback.cbFuncTypeName+"UserParam> "+javaCallback.cbFuncTypeName+"UsrMap = new java.util.HashMap<Object, "+javaCallback.cbFuncTypeName+"UserParam>();");
-            unit.emitln("  private native void release"+getInterfaceName()+"Impl(long nativeUserParam);");
+            unit.emitln("  private native void release"+capIfaceName+"MapImpl(long nativeUserParam);");
+            unit.emitln();
+            if( !customKeyClass ) {
+                emitJavaCallbackKeyClass(KeyClassName);
+            }
+            if( !cfg.emittedJavaCallbackUserParamClasses.contains(fqUsrParamClassName) ) {
+                emitJavaCallbackUsrParamClass(UsrParamClassName);
+                cfg.emittedJavaCallbackUserParamClasses.add(fqUsrParamClassName);
+            }
+            unit.emitln("  private final java.util.Map<"+KeyClassName+", "+UsrParamClassName+"> "+usrMapInstanceName+" = new java.util.HashMap<"+KeyClassName+", "+UsrParamClassName+">();");
+            unit.emitln();
         }
     }
   }
@@ -620,14 +761,18 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
   protected void emitReturnVariableSetupAndCall(final MethodBinding binding) {
     final JavaType returnType = binding.getJavaReturnType();
     boolean needsResultAssignment = false;
+    final String capIfaceName = CodeGenUtils.capitalizeString( getInterfaceName() );
 
     if( null != javaCallback ) {
-        final String userParamArgName = binding.getArgumentName(javaCallback.setFuncUserParamIdx);
-        unit.emitln("    release"+getInterfaceName()+"("+userParamArgName+"); // Ensure a previously mapped instance is released");
-        unit.emitln("    final long[] nativeUserParam = { 0 };");
+        final String lowIfaceName = CodeGenUtils.decapitalizeString( getInterfaceName() );
+        final String usrMapInstanceName = lowIfaceName+"UsrMap";
+        unit.emitln("    synchronized( "+usrMapInstanceName+" ) {");
+        unit.emitln("      final long[] nativeUserParam = { 0 };");
+        unit.emitln("      release"+capIfaceName+"Map("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+"); // Ensure a previously mapped instance is released");
+        unit.emitln();
     }
     if (!returnType.isVoid()) {
-      unit.emit("    ");
+      unit.emit("      ");
       if (returnType.isCompoundTypeWrapper() ||
           returnType.isNIOBuffer()) {
         unit.emitln("final ByteBuffer _res;");
@@ -645,9 +790,9 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
     }
 
     if (needsResultAssignment) {
-      unit.emit("    _res = ");
+      unit.emit("      _res = ");
     } else {
-      unit.emit("    ");
+      unit.emit("      ");
       if (!returnType.isVoid()) {
         unit.emit("return ");
       }
@@ -659,10 +804,16 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
     if( null != javaCallback ) {
         final String funcArgName = binding.getArgumentName(javaCallback.setFuncCBParamIdx);
         final String userParamArgName = binding.getArgumentName(javaCallback.setFuncUserParamIdx);
-        // unit.emitln("    System.err.println(\"ZZZ returned nativeUserParam \"+nativeUserParam[0]);");
-        unit.emitln("    if( 0 != nativeUserParam[0] ) {");
-        unit.emitln("        "+javaCallback.cbFuncTypeName+"UsrMap.put("+userParamArgName+", new "+javaCallback.cbFuncTypeName+"UserParam("+funcArgName+", "+userParamArgName+", nativeUserParam[0]));");
-        unit.emitln("    }");
+        final String UsrParamClassName = CodeGenUtils.capitalizeString( javaCallback.cbFuncTypeName+"UserParam" );
+        if( DEBUG_JAVACALLBACK ) {
+            unit.emitln("    System.err.println(\"ZZZ returned nativeUserParam 0x\"+Long.toHexString(nativeUserParam[0]));");
+        }
+        unit.emitln();
+        unit.emitln("      if( 0 != nativeUserParam[0] ) {");
+        unit.emitln("          add"+capIfaceName+"Map("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, true).toString()+
+                                                      "new "+UsrParamClassName+"("+funcArgName+", "+userParamArgName+", nativeUserParam[0]));");
+        unit.emitln("      }");
+        unit.emitln("    } // synchronized ");
     }
 
     emitPostCallCleanup(binding);
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java b/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java
index e80be6f..e43d73e 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java
@@ -31,6 +31,7 @@ package com.jogamp.gluegen.test.junit.generation;
 import java.io.IOException;
 
 import com.jogamp.common.os.NativeLibrary;
+import com.jogamp.gluegen.test.junit.generation.Bindingtest2.ALBUFFERCALLBACKTYPESOFT;
 import com.jogamp.gluegen.test.junit.generation.Bindingtest2.T2_CallbackFunc01;
 import com.jogamp.gluegen.test.junit.generation.impl.Bindingtest2Impl;
 
@@ -83,30 +84,30 @@ public class Test4JavaCallback extends BaseClass {
         final T2_CallbackFunc01 myCallback01 = new T2_CallbackFunc01() {
             @Override
             public void callback(final long id, final String msg, final Object userParam) {
-                final MyUserParam myUserParam = (MyUserParam)userParam;
+                final MyUserParam01 myUserParam = (MyUserParam01)userParam;
                 id_res[0] = id + myUserParam.i;
                 msg_res[0] = msg;
                 myUserParam.j += id_res[0];
-                System.err.println("chapter10.myCallback01: "+id+", '"+msg+"'");
+                System.err.println("chapter01.myCallback01: "+id+", '"+msg+"'");
             }
         };
         final T2_CallbackFunc01 myCallback02 = new T2_CallbackFunc01() {
             @Override
             public void callback(final long id, final String msg, final Object userParam) {
-                final MyUserParam myUserParam = (MyUserParam)userParam;
+                final MyUserParam01 myUserParam = (MyUserParam01)userParam;
                 id_res[0] = id;
                 msg_res[0] = msg;
                 myUserParam.j += id_res[0];
-                System.err.println("chapter10.myCallback02: "+id+", '"+msg+"'");
+                System.err.println("chapter01.myCallback02: "+id+", '"+msg+"'");
             }
         };
-        final MyUserParam myUserParam01 = new MyUserParam(10);
+        final MyUserParam01 myUserParam01 = new MyUserParam01(10);
         Assert.assertEquals(10, myUserParam01.i);
         Assert.assertEquals( 0, myUserParam01.j);
-        Assert.assertEquals(false, bt2.isMessageCallback01Mapped(myUserParam01));
+        Assert.assertEquals(false, bt2.isMessageCallback01Mapped());
 
         bt2.MessageCallback01(myCallback01, myUserParam01);
-        Assert.assertEquals(true, bt2.isMessageCallback01Mapped(myUserParam01));
+        Assert.assertEquals(true, bt2.isMessageCallback01Mapped());
         Assert.assertEquals(-1, id_res[0]);
         Assert.assertEquals(null, msg_res[0]);
         Assert.assertEquals(10, myUserParam01.i);
@@ -132,7 +133,7 @@ public class Test4JavaCallback extends BaseClass {
         // Switch the callback function
         // The previously mapped myUserParam01 gets released and remapped to new callback
         bt2.MessageCallback01(myCallback02, myUserParam01);
-        Assert.assertEquals(true, bt2.isMessageCallback01Mapped(myUserParam01));
+        Assert.assertEquals(true, bt2.isMessageCallback01Mapped());
         Assert.assertEquals(       42+10, id_res[0]);
         Assert.assertEquals(      msgNo2, msg_res[0]);
         Assert.assertEquals(          10, myUserParam01.i);
@@ -149,7 +150,7 @@ public class Test4JavaCallback extends BaseClass {
 
         // Just release the callback and mapped myUserParam01
         bt2.MessageCallback01(null, myUserParam01);
-        Assert.assertEquals(false, bt2.isMessageCallback01Mapped(myUserParam01));
+        Assert.assertEquals(false, bt2.isMessageCallback01Mapped());
         {
             final String msgNo4 = "My Fourth JavaCallback message";
             bt2.InjectMessageCallback01( 21, msgNo4);
@@ -160,11 +161,392 @@ public class Test4JavaCallback extends BaseClass {
             Assert.assertEquals(1+42+10+404+10, myUserParam01.j);
         }
     }
-    private static class MyUserParam {
+    private static class MyUserParam01 {
         final long i;
         long j;
-        public MyUserParam(final long i) { this.i = i; j=0; }
+        public MyUserParam01(final long i) { this.i = i; j=0; }
+
+        @Override
+        public boolean equals(final Object o) {
+            if( this == o ) {
+                return true;
+            }
+            if( !(o instanceof MyUserParam01) ) {
+                return false;
+            }
+            return false; // we require identity!
+        }
+        @Override
+        public int hashCode() {
+            return System.identityHashCode(this); // we require identity!
+        }
+    }
+
+    /**
+     * Test Bindingtest2 with ALBUFFERCALLBACKTYPESOFT JavaCallback via alBufferCallback1()
+     * using the default AlBufferCallback1Key class.
+     */
+    @Test
+    public void chapter02() throws Exception {
+        final Bindingtest2 bt2 = new Bindingtest2Impl();
+
+        final long[] id_res = { -1 };
+        final ALBUFFERCALLBACKTYPESOFT myCallback01 = new ALBUFFERCALLBACKTYPESOFT() {
+            @Override
+            public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) {
+                final MyUserParam02 myUserParam = (MyUserParam02)userptr;
+                id_res[0] = sampledata + numbytes + myUserParam.i;
+                myUserParam.j = id_res[0];
+                myUserParam.buffer = buffer;
+                System.err.println("chapter02.myCallback01: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes);
+            }
+        };
+        final ALBUFFERCALLBACKTYPESOFT myCallback02 = new ALBUFFERCALLBACKTYPESOFT() {
+            @Override
+            public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) {
+                final MyUserParam02 myUserParam = (MyUserParam02)userptr;
+                id_res[0] = sampledata * numbytes + myUserParam.i;
+                myUserParam.j = id_res[0];
+                myUserParam.buffer = buffer;
+                System.err.println("chapter02.myCallback02: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes);
+            }
+        };
+        final int buffer1 = 1;
+        final int buffer2 = 2;
+        final int buffer3 = 3;
+        final MyUserParam02 myUserParam01 = new MyUserParam02( 1);
+        final MyUserParam02 myUserParam02 = new MyUserParam02( 2);
+        Assert.assertEquals( 1, myUserParam01.i);
+        Assert.assertEquals( 0, myUserParam01.j);
+        Assert.assertEquals( 0, myUserParam01.buffer);
+        Assert.assertEquals( 2, myUserParam02.i);
+        Assert.assertEquals( 0, myUserParam02.j);
+        Assert.assertEquals( 0, myUserParam02.buffer);
+
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer3));
+
+        // 1st mapping: buffer1 -> myCallback01, myUserParam01
+        bt2.alBufferCallback0(buffer1, 0, 0, myCallback01, myUserParam01);
+        Assert.assertEquals(true,  bt2.isAlBufferCallback0Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback0UserParam(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback0(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer3));
+
+        // 2nd mapping: buffer2 -> myCallback02, myUserParam02
+        bt2.alBufferCallback0(buffer2, 0, 0, myCallback02, myUserParam02);
+        Assert.assertEquals(true,  bt2.isAlBufferCallback0Mapped(buffer1));
+        Assert.assertEquals(true,  bt2.isAlBufferCallback0Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback0UserParam(buffer1));
+        Assert.assertEquals(myUserParam02, bt2.getAlBufferCallback0UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback0(buffer1));
+        Assert.assertEquals(myCallback02,  bt2.getAlBufferCallback0(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer3));
+
+        {
+            bt2.alBufferCallback0Inject(buffer1, 10, 100); // buffer1 -> myCallback01, myUserParam01
+            Assert.assertEquals(10+100+1, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(10+100+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals(       0, myUserParam02.j);
+            Assert.assertEquals(       0, myUserParam02.buffer);
+        }
+        {
+            bt2.alBufferCallback0Inject(buffer2, 10, 100); // buffer2 -> myCallback02, myUserParam02
+            Assert.assertEquals(10*100+2, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(10+100+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals(10*100+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+
+        // Switch the callback function for buffer2 -> myCallback01, myUserParam02
+        bt2.alBufferCallback0(buffer2, 0, 0, myCallback01, myUserParam02);
+        Assert.assertEquals(true,  bt2.isAlBufferCallback0Mapped(buffer1));
+        Assert.assertEquals(true,  bt2.isAlBufferCallback0Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback0UserParam(buffer1));
+        Assert.assertEquals(myUserParam02, bt2.getAlBufferCallback0UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback0(buffer1));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback0(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer3));
+
+        {
+            bt2.alBufferCallback0Inject(buffer1, 11, 101); // buffer1 -> myCallback01, myUserParam01
+            Assert.assertEquals(11+101+1, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(11+101+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals(10*100+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+        {
+            bt2.alBufferCallback0Inject(buffer2,  1,  10); // buffer2 -> myCallback01, myUserParam02
+            Assert.assertEquals( 1+ 10+2, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(11+101+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals( 1+ 10+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+
+        // Just release the buffer2 callback and mapped resources
+        bt2.alBufferCallback0(buffer2, 0, 0, null, myUserParam02); // usrptr is not key, only buffer is key!
+        Assert.assertEquals(true,  bt2.isAlBufferCallback0Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback0UserParam(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback0(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer3));
+
+        // Just release the buffer1 callback and mapped resources
+        bt2.alBufferCallback0(buffer1, 0, 0, null, null); // usrptr is not key, only buffer is key!
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback0Mapped(buffer3));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0UserParam(buffer3));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback0(buffer3));
+
+        {
+            bt2.alBufferCallback0Inject(buffer2,  1,  10); // unmapped, no change in data
+            Assert.assertEquals( 1+ 10+2, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(11+101+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals( 1+ 10+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+    }
+
+    /**
+     * Test Bindingtest2 with ALBUFFERCALLBACKTYPESOFT JavaCallback via alBufferCallback1()
+     * using our custom CustomAlBufferCallback1Key class.
+     */
+    @Test
+    public void chapter03() throws Exception {
+        final Bindingtest2 bt2 = new Bindingtest2Impl();
+
+        final long[] id_res = { -1 };
+        final ALBUFFERCALLBACKTYPESOFT myCallback01 = new ALBUFFERCALLBACKTYPESOFT() {
+            @Override
+            public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) {
+                final MyUserParam02 myUserParam = (MyUserParam02)userptr;
+                id_res[0] = sampledata + numbytes + myUserParam.i;
+                myUserParam.j = id_res[0];
+                myUserParam.buffer = buffer;
+                System.err.println("chapter03.myCallback01: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes);
+            }
+        };
+        final ALBUFFERCALLBACKTYPESOFT myCallback02 = new ALBUFFERCALLBACKTYPESOFT() {
+            @Override
+            public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) {
+                final MyUserParam02 myUserParam = (MyUserParam02)userptr;
+                id_res[0] = sampledata * numbytes + myUserParam.i;
+                myUserParam.j = id_res[0];
+                myUserParam.buffer = buffer;
+                System.err.println("chapter03.myCallback02: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes);
+            }
+        };
+        final int buffer1 = 1;
+        final int buffer2 = 2;
+        final int buffer3 = 3;
+        final MyUserParam02 myUserParam01 = new MyUserParam02( 1);
+        final MyUserParam02 myUserParam02 = new MyUserParam02( 2);
+        Assert.assertEquals( 1, myUserParam01.i);
+        Assert.assertEquals( 0, myUserParam01.j);
+        Assert.assertEquals( 0, myUserParam01.buffer);
+        Assert.assertEquals( 2, myUserParam02.i);
+        Assert.assertEquals( 0, myUserParam02.j);
+        Assert.assertEquals( 0, myUserParam02.buffer);
+
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer3));
+
+        // 1st mapping: buffer1 -> myCallback01, myUserParam01
+        bt2.alBufferCallback1(buffer1, 0, 0, myCallback01, myUserParam01);
+        Assert.assertEquals(true,  bt2.isAlBufferCallback1Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback1UserParam(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback1(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer3));
+
+        // 2nd mapping: buffer2 -> myCallback02, myUserParam02
+        bt2.alBufferCallback1(buffer2, 0, 0, myCallback02, myUserParam02);
+        Assert.assertEquals(true,  bt2.isAlBufferCallback1Mapped(buffer1));
+        Assert.assertEquals(true,  bt2.isAlBufferCallback1Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback1UserParam(buffer1));
+        Assert.assertEquals(myUserParam02, bt2.getAlBufferCallback1UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback1(buffer1));
+        Assert.assertEquals(myCallback02,  bt2.getAlBufferCallback1(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer3));
+
+        {
+            bt2.alBufferCallback1Inject(buffer1, 10, 100); // buffer1 -> myCallback01, myUserParam01
+            Assert.assertEquals(10+100+1, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(10+100+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals(       0, myUserParam02.j);
+            Assert.assertEquals(       0, myUserParam02.buffer);
+        }
+        {
+            bt2.alBufferCallback1Inject(buffer2, 10, 100); // buffer2 -> myCallback02, myUserParam02
+            Assert.assertEquals(10*100+2, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(10+100+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals(10*100+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+
+        // Switch the callback function for buffer2 -> myCallback01, myUserParam02
+        bt2.alBufferCallback1(buffer2, 0, 0, myCallback01, myUserParam02);
+        Assert.assertEquals(true,  bt2.isAlBufferCallback1Mapped(buffer1));
+        Assert.assertEquals(true,  bt2.isAlBufferCallback1Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback1UserParam(buffer1));
+        Assert.assertEquals(myUserParam02, bt2.getAlBufferCallback1UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback1(buffer1));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback1(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer3));
+
+        {
+            bt2.alBufferCallback1Inject(buffer1, 11, 101); // buffer1 -> myCallback01, myUserParam01
+            Assert.assertEquals(11+101+1, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(11+101+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals(10*100+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+        {
+            bt2.alBufferCallback1Inject(buffer2,  1,  10); // buffer2 -> myCallback01, myUserParam02
+            Assert.assertEquals( 1+ 10+2, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(11+101+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals( 1+ 10+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+
+        // Just release the buffer2 callback and mapped resources
+        bt2.alBufferCallback1(buffer2, 0, 0, null, myUserParam02); // usrptr is not key, only buffer is key!
+        Assert.assertEquals(true,  bt2.isAlBufferCallback1Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer3));
+        Assert.assertEquals(myUserParam01, bt2.getAlBufferCallback1UserParam(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer3));
+        Assert.assertEquals(myCallback01,  bt2.getAlBufferCallback1(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer3));
+
+        // Just release the buffer1 callback and mapped resources
+        bt2.alBufferCallback1(buffer1, 0, 0, null, null); // usrptr is not key, only buffer is key!
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer1));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer2));
+        Assert.assertEquals(false, bt2.isAlBufferCallback1Mapped(buffer3));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1UserParam(buffer3));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer1));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer2));
+        Assert.assertEquals(null,          bt2.getAlBufferCallback1(buffer3));
+
+        {
+            bt2.alBufferCallback1Inject(buffer2,  1,  10); // unmapped, no change in data
+            Assert.assertEquals( 1+ 10+2, id_res[0]);
+            Assert.assertEquals(       1, myUserParam01.i);
+            Assert.assertEquals(11+101+1, myUserParam01.j);
+            Assert.assertEquals(       1, myUserParam01.buffer);
+            Assert.assertEquals(       2, myUserParam02.i);
+            Assert.assertEquals( 1+ 10+2, myUserParam02.j);
+            Assert.assertEquals(       2, myUserParam02.buffer);
+        }
+    }
+    private static class MyUserParam02 {
+        final long i;
+        long j;
+        int buffer;
+        public MyUserParam02(final long i) { this.i = i; j=0; buffer=0; }
+
+        @Override
+        public boolean equals(final Object o) {
+            if( this == o ) {
+                return true;
+            }
+            if( !(o instanceof MyUserParam02) ) {
+                return false;
+            }
+            return false; // we require identity!
+        }
+        @Override
+        public int hashCode() {
+            return System.identityHashCode(this); // we require identity!
+        }
+    }
+
+    public static class CustomAlBufferCallback1Key {
+        private final int buffer;
+        public CustomAlBufferCallback1Key(final int buffer) {
+            this.buffer = buffer;
+        }
+        @Override
+        public boolean equals(final Object o) {
+            if( this == o ) {
+                return true;
+            }
+            if( !(o instanceof CustomAlBufferCallback1Key) ) {
+                return false;
+            }
+            final CustomAlBufferCallback1Key o2 = (CustomAlBufferCallback1Key)o;
+            return buffer == o2.buffer;
+        }
+        @Override
+        public int hashCode() {
+            return buffer;
+        }
+        @Override
+        public String toString() {
+            return "CustomALKey[this "+toHexString(System.identityHashCode(this))+", buffer "+buffer+"]";
+        }
     }
+    static private String toHexString(final int v) { return "0x"+Integer.toHexString(v); }
 
     public static void main(final String args[]) throws IOException {
         final String tstname = Test4JavaCallback.class.getName();
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c
index 9715f11..7a7cb43 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c
@@ -86,6 +86,10 @@ int Release(T2_InitializeOptions* Options) {
     Options->CustomFuncB2 = NULL;
 }
 
+//
+//
+//
+
 static T2_CallbackFunc01 t2_callback01 = NULL;
 static void* t2_callback01_userparam = NULL;
 
@@ -104,3 +108,79 @@ void InjectMessageCallback01(size_t id, const char* msg) {
     }
 }
 
+//
+//
+//
+
+static ALEVENTPROCSOFT alEventCallback_cb = NULL;
+static void* alEventCallback_up = NULL;
+
+void alEventCallback(ALEVENTPROCSOFT callback, void *userParam) {
+    alEventCallback_cb = callback;
+    alEventCallback_up = userParam;
+}
+void alEventCallbackInject(int eventType, int object, int param, const char* msg) {
+    if( NULL != alEventCallback_cb ) {
+        fprintf(stderr, "XXX InjectMessageCallback01 func %p, user %p\n", alEventCallback_cb, alEventCallback_up);
+        fflush(NULL);
+        (*alEventCallback_cb)(eventType, object, param, strlen(msg), msg, alEventCallback_up);
+    }
+}
+
+//
+//
+//
+
+static const int MAX_AL_BUFFER = 5;
+static ALBUFFERCALLBACKTYPESOFT alBufferCallback0_callback[] = { NULL, NULL, NULL, NULL, NULL };
+static void* alBufferCallback0_userptr[] = { NULL, NULL, NULL, NULL, NULL };
+
+void alBufferCallback0(int buffer /* key */, int format, int freq, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) {
+    if( buffer < 0 || MAX_AL_BUFFER <= buffer ) {
+        fprintf(stderr, "Error: alBufferCallback0: buffer not in range [0..%d), is %d\n", MAX_AL_BUFFER, buffer);
+    } else {
+        alBufferCallback0_callback[buffer] = callback;
+        alBufferCallback0_userptr[buffer] = userptr;
+        fprintf(stderr, "XXX alBufferCallback0 buffer %d -> func %p, user %p\n", buffer, callback, userptr);
+    }
+    fflush(NULL);
+}
+void alBufferCallback0Inject(int buffer, int sampledata, int numbytes) {
+    if( buffer < 0 || MAX_AL_BUFFER <= buffer ) {
+        fprintf(stderr, "Error: alBufferCallback0Inject: buffer not in range [0..%d), is %d\n", MAX_AL_BUFFER, buffer);
+    }
+    if( NULL != alBufferCallback0_callback[buffer] ) {
+        fprintf(stderr, "XXX alBufferCallback0Inject: buffer %d, func %p, user %p\n", buffer, alBufferCallback0_callback[buffer], alBufferCallback0_userptr[buffer]);
+        fflush(NULL);
+        (*alBufferCallback0_callback[buffer])(buffer, alBufferCallback0_userptr[buffer], sampledata, numbytes);
+    }
+}
+
+//
+//
+//
+
+static ALBUFFERCALLBACKTYPESOFT alBufferCallback1_callback[] = { NULL, NULL, NULL, NULL, NULL };
+static void* alBufferCallback1_userptr[] = { NULL, NULL, NULL, NULL, NULL };
+
+void alBufferCallback1(int buffer /* key */, int format, int freq, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) {
+    if( buffer < 0 || MAX_AL_BUFFER <= buffer ) {
+        fprintf(stderr, "Error: alBufferCallback1: buffer not in range [0..%d), is %d\n", MAX_AL_BUFFER, buffer);
+    } else {
+        alBufferCallback1_callback[buffer] = callback;
+        alBufferCallback1_userptr[buffer] = userptr;
+        fprintf(stderr, "XXX alBufferCallback1 buffer %d -> func %p, user %p\n", buffer, callback, userptr);
+    }
+    fflush(NULL);
+}
+void alBufferCallback1Inject(int buffer, int sampledata, int numbytes) {
+    if( buffer < 0 || MAX_AL_BUFFER <= buffer ) {
+        fprintf(stderr, "Error: alBufferCallback1Inject: buffer not in range [0..%d), is %d\n", MAX_AL_BUFFER, buffer);
+    }
+    if( NULL != alBufferCallback1_callback[buffer] ) {
+        fprintf(stderr, "XXX alBufferCallback1Inject: buffer %d, func %p, user %p\n", buffer, alBufferCallback1_callback[buffer], alBufferCallback1_userptr[buffer]);
+        fflush(NULL);
+        (*alBufferCallback1_callback[buffer])(buffer, alBufferCallback1_userptr[buffer], sampledata, numbytes);
+    }
+}
+
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg
index 94ea0af..de713cf 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg
@@ -55,24 +55,111 @@ ArgumentIsString T2_CallbackFunc01 1
 ArgumentIsString InjectMessageCallback01 1
 
 # Define a JavaCallback.
-#   Set JavaCallback via function `MessageCallback01` if `T2_CallbackFunc01` argument is non-null, otherwise removes the callback and associated resources.
-#   It uses `usrParam` as the resource-key to map to the hidden native-usrParam object,
-#   hence a matching 'usrParam' must be passed for setting and removal of the callback.
+#   Set JavaCallback via function `MessageCallback01` if `T2_CallbackFunc01` argument is non-null, otherwise removes the mapped callback and associated resources.
 #
 #   It uses the function-pointer argument `T2_CallbackFunc01` as the callback function type
-#   and marks `T2_CallbackFunc01`s 3rd argument (index 2) as the mandatory user-param for Java Object mapping.
+#   and marks `T2_CallbackFunc01`s 3rd argument (index 2) as the mandatory user-param.
 #
-#   Note: An explicit `isMessageCallback01Mapped(Object usrParam)` is being created to explicitly query whether `usrParam` maps to the associated resources.
+#   This callback has no keys defines, rendering it of global scope!
+#
+#   Explicit queries are generated, passing the keys as paramters
+#   - `boolean isMessageCallback01Mapped()` queries whether `MessageCallback0` is mapped globally
+#   - `T2_CallbackFunc01 getMessageCallback01()` returns the global T2_CallbackFunc01, null if not mapped
+#   - `Object getMessageCallback01UserParam()` returns the global `usrParam` object, null if not mapped
 JavaCallbackDef  MessageCallback01 T2_CallbackFunc01 2
+#
+# End JavaCallback
 
+# Begin JavaCallback.
+#
+# typedef void ( * ALEVENTPROCSOFT)(int eventType, int object, int param, int length, const char *message, void *userParam);
+# void alEventCallback(ALEVENTPROCSOFT callback, void *userParam /* identity-key */);
+# void alEventCallbackInject(int eventType, int object, int param, const char* msg);
+ArgumentIsString ALEVENTPROCSOFT 4
+ArgumentIsString alEventCallbackInject 3
+
+# Define a JavaCallback (OpenAL AL_SOFT_events)
+#   Set JavaCallback via function `alEventCallback` if `ALEVENTPROCSOFT` argument is non-null, otherwise removes the mapped callback and associated resources.
+#
+#   It uses the function-pointer argument `ALEVENTPROCSOFT` as the callback function type
+#   and marks `ALEVENTPROCSOFT`s 6th argument (index 5) as the mandatory user-param.
+#
+#   This callback has no keys defines, rendering it of global scope!
+#   The global key-less scope matches `AL_SOFT_events` semantics.
+#
+#   Explicit queries are generated, passing the keys as paramters
+#   - `boolean isAlEventCallbackMapped()` queries whether `alEventCallback` is mapped globally
+#   - `ALEVENTPROCSOFT getAlEventCallback()` returns the global ALEVENTPROCSOFT, null if not mapped
+#   - `Object getAlEventCallbackUserParam()` returns the global `userParam` object, null if not mapped
+JavaCallbackDef  alEventCallback ALEVENTPROCSOFT 5
+JavaCallbackKey  alEventCallback
 #
 # End JavaCallback
 
-# typedef void ( * T2_CallbackFunc02)(const T2_Callback02UserType* usrParam);
-# void MessageCallback02(T2_CallbackFunc02 cbFunc, const T2_Callback02UserType* usrParam);
-# void InjectMessageCallback02(size_t id, const char* msg);
-ArgumentIsString InjectMessageCallback02 1
-JavaCallbackDef  MessageCallback02 T2_CallbackFunc02 0
+# Begin JavaCallback (OpanAL AL_SOFT_callback_buffer)
+#
+# // typedef void ( * ALBUFFERCALLBACKTYPESOFT)(int buffer, void *userptr, void *sampledata, int numbytes);
+# typedef void ( * ALBUFFERCALLBACKTYPESOFT)(int buffer, void *userptr, int sampledata, int numbytes);
+#
+# void alBufferCallback0(int buffer /* key */, int format, int freq, ALBUFFERCALLBACKTYPESOFT callback, void *userptr /* identity-key */);
+#
+# // void alBufferCallback0Inject(int buffer, void *sampledata, int numbytes);
+# void alBufferCallback0Inject(int buffer, int sampledata, int numbytes);
+
+# Define a JavaCallback.
+#   Set JavaCallback via function `alBufferCallback0` if `ALBUFFERCALLBACKTYPESOFT` argument is non-null, otherwise removes the mapped callback and associated resources.
+#
+#   It uses the function-pointer argument `ALBUFFERCALLBACKTYPESOFT` as the callback function type
+#   and marks `ALBUFFERCALLBACKTYPESOFT`s 2nd argument (index 1) as the mandatory user-param.
+#
+#   This callback defines one key, `buffer`, index 0 of alBufferCallback0(..) parameter list, limiting it to buffer-name scope!
+#   The `buffer` key allows setting one callback per buffer-name, compatible with the `AL_SOFT_callback_buffer` spec.
+#
+#   Explicit queries are generated, passing the keys as paramters
+#   - `boolean isAlBufferCallback0Mapped(int buffer)` queries whether `alBufferCallback0` is mapped to `buffer`.
+#   - `ALBUFFERCALLBACKTYPESOFT getAlBufferCallback0(int buffer)` returns the `buffer` mapped ALEVENTPROCSOFT, null if not mapped
+#   - `Object getAlBufferCallback0UserParam(int buffer)` returns the `buffer` mapped `userptr` object, null if not mapped
+JavaCallbackDef  alBufferCallback0 ALBUFFERCALLBACKTYPESOFT 1
+JavaCallbackKey  alBufferCallback0 0
+#
+# End JavaCallback
+
+# Begin JavaCallback (OpanAL AL_SOFT_callback_buffer, variant 2)
+#
+# Reuses: ALBUFFERCALLBACKTYPESOFT, see above.
+#
+# void alBufferCallback1(int buffer /* key */, int format, int freq, ALBUFFERCALLBACKTYPESOFT callback, void *userptr /* identity-key */);
+#
+# // void alBufferCallback1Inject(int buffer, void *sampledata, int numbytes);
+# void alBufferCallback1Inject(int buffer, int sampledata, int numbytes);
+
+# Define a JavaCallback.
+#   Set JavaCallback via function `alBufferCallback1` if `ALBUFFERCALLBACKTYPESOFT` argument is non-null, otherwise removes the mapped callback and associated resources.
+#
+#   It uses the function-pointer argument `ALBUFFERCALLBACKTYPESOFT` as the callback function type
+#   and marks `ALBUFFERCALLBACKTYPESOFT`s 2nd argument (index 1) as the mandatory user-param.
+#
+#   This callback defines one key, `buffer`, index 0 of alBufferCallback1(..) parameter list, limiting it to buffer-name scope!
+#   The `buffer` key allows setting one callback per buffer-name, compatible with the `AL_SOFT_callback_buffer` spec.
+#
+#   Explicit queries are generated, passing the keys as paramters
+#   - `boolean isAlBufferCallback1Mapped(int buffer)` queries whether `alBufferCallback1` is mapped to `buffer`.
+#   - `ALBUFFERCALLBACKTYPESOFT getAlBufferCallback1(int buffer)` returns the `buffer` mapped ALEVENTPROCSOFT, null if not mapped
+#   - `Object getAlBufferCallback1UserParam(int buffer)` returns the `buffer` mapped `userptr` object, null if not mapped
+JavaCallbackDef  alBufferCallback1 ALBUFFERCALLBACKTYPESOFT 1 com.jogamp.gluegen.test.junit.generation.Test4JavaCallback.CustomAlBufferCallback1Key
+JavaCallbackKey  alBufferCallback1 0
+#
+# End JavaCallback
+
+# Begin JavaCallback
+#
+# typedef void ( * T2_CallbackFunc11)(const T2_Callback11UserType* usrParam);
+# void MessageCallback11(T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam);
+# void InjectMessageCallback11(size_t id, const char* msg);
+ArgumentIsString InjectMessageCallback11 1
+JavaCallbackDef  MessageCallback11 T2_CallbackFunc11 0
+#
+# End JavaCallback
 
 CustomCCode #include "test2.h"
 
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h
index afe94a5..d067390 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h
@@ -67,15 +67,37 @@ void MessageCallback01(T2_CallbackFunc01 cbFunc, void* usrParam);
 void InjectMessageCallback01(size_t id, const char* msg);
 
 //
-// T2_CallbackFunc02
+// ALEVENTPROCSOFT (similar to OpenAL's AL_SOFT_events)
+//
+typedef void ( * ALEVENTPROCSOFT)(int eventType, int object, int param, int length, const char *message, void *userParam);
+
+void alEventCallback(ALEVENTPROCSOFT callback, void *userParam);
+void alEventCallbackInject(int eventType, int object, int param, const char* msg);
+
+//
+// ALBUFFERCALLBACKTYPESOFT (similar to OpenAL's AL_SOFT_callback_buffer)
+//
+// typedef void ( * ALBUFFERCALLBACKTYPESOFT)(int buffer, void *userptr, void *sampledata, int numbytes);
+typedef void ( * ALBUFFERCALLBACKTYPESOFT)(int buffer, void *userptr, int sampledata, int numbytes);
+
+void alBufferCallback0(int buffer /* key */, int format, int freq, ALBUFFERCALLBACKTYPESOFT callback, void *userptr);
+// void alBufferCallback0Inject(int buffer, void *sampledata, int numbytes);
+void alBufferCallback0Inject(int buffer, int sampledata, int numbytes);
+
+void alBufferCallback1(int buffer /* key */, int format, int freq, ALBUFFERCALLBACKTYPESOFT callback, void *userptr);
+// void alBufferCallback1Inject(int buffer, void *sampledata, int numbytes);
+void alBufferCallback1Inject(int buffer, int sampledata, int numbytes);
+
+//
+// T2_CallbackFunc11
 //
 typedef struct {
     int32_t ApiVersion;
     void* Data;
-} T2_Callback02UserType;
+} T2_Callback11UserType;
 
-typedef void ( * T2_CallbackFunc02)(const T2_Callback02UserType* usrParam);
+typedef void ( * T2_CallbackFunc11)(const T2_Callback11UserType* usrParam);
 
-void MessageCallback02(T2_CallbackFunc02 cbFunc, const T2_Callback02UserType* usrParam);
-void InjectMessageCallback02(size_t id, const char* msg);
+void MessageCallback11(T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam);
+void InjectMessageCallback11(size_t id, const char* msg);
 
-- 
cgit v1.2.3