diff -pruN 1.5.0-1/.azure/build.yml 1.6.0-1/.azure/build.yml
--- 1.5.0-1/.azure/build.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/build.yml	2025-06-01 03:57:43.000000000 +0000
@@ -1,4 +1,14 @@
 # JPype CI pipeline
+
+# NOTE: 
+# - Some commented-out sections (e.g., Python 3.14, JDK 22) are placeholders for future versions.
+#   These will be activated once the corresponding tools are available on Azure (usually ~4 months after release).
+# - Debug jobs are only activated when issues cannot be replicated locally; otherwise, they remain disabled.
+# - macOS coverage is limited to the oldest and newest supported Python/JDK versions, as most issues are platform-independent and our primary support is for Linux.
+# - We only test against LTS (Long-Term Support) JDK releases, as newer versions (e.g., JDK 22) are not yet available on Azure.
+# - Fast tests are enabled on macOS to avoid long build times, especially for tests with high overhead (e.g., leak checkers).
+# - Documentation jobs are run only on Ubuntu, since documentation generation is OS-agnostic and only needs to be validated for HTML publication.
+
 trigger:
   branches:
     include:
@@ -15,9 +25,11 @@ trigger:
     - test/*
 
 variables:
-# indicate whether the testsuite should skip long running tests or not.
+# indicate whether the testsuite should skip long-running tests or not.
 - name: jpypetest.fast
   value: 'false'
+- name: system.debug
+  value: 'true'
 
 jobs:
 - job: Deps
@@ -50,45 +62,64 @@ jobs:
   dependsOn: Deps
   strategy:
     matrix:
-      linux-3.8:
-        imageName: "ubuntu-latest"
-        python.version: '3.8'
-      linux-3.9:
+      # Linux
+      linux_py39_jdk11:
         imageName: "ubuntu-latest"
         python.version: '3.9'
-      linux-3.10:
+        jdk.version: '11'
+      linux_py310_jdk17:
         imageName: "ubuntu-latest"
         python.version: '3.10'
-      linux-3.11:
+        jdk.version: '17'
+      linux_py311_jdk17:
         imageName: "ubuntu-latest"
         python.version: '3.11'
-      linux-3.12:
+        jdk.version: '17'
+      linux_py312_jdk17:
         imageName: "ubuntu-latest"
         python.version: '3.12'
-      windows-3.8:
-        imageName: "windows-2019"
-        python.version: '3.8'
-      windows-3.9:
-        imageName: "windows-2019"
+        jdk.version: '17'
+      linux_py313_jdk17:
+        imageName: "ubuntu-latest"
+        python.version: "3.13"
+        jdk.version: '17' # jdk 22 is not there yet.
+      #linux_py314_jdk22:
+      #  imageName: "ubuntu-latest"
+      #  python.version: "3.14.0-alpha.0"
+      #  jdk.version: '22'
+      # Windows
+      windows_py39_jdk11:
+        imageName: "windows-2022"
         python.version: '3.9'
-        #jpypetest.fast: 'true'
-      windows-3.10:
-        imageName: "windows-2019"
+        jdk.version: '11'
+      windows_py310_jdk11:
+        imageName: "windows-2022"
         python.version: '3.10'
-      windows-3.11:
-        imageName: "windows-2019"
+        jdk.version: '11'
+      windows_py311_jdk17:
+        imageName: "windows-2022"
         python.version: '3.11'
-      windows-3.12:
-        imageName: "windows-2019"
+        jdk.version: '17'
+      windows_py312_jdk21:
+        imageName: "windows-2022"
         python.version: '3.12'
-      mac-3.9:
-        imageName: "macos-11"
+        jdk.version: '21'
+      # OSX, we only test an old Python version with JDK8 and recent Py with recent JDK.
+      mac_py39_jdk11:
+        imageName: "macos-13"
         python.version: '3.9'
         jpypetest.fast: 'true'
+        jdk.version: '11'
+      mac_py312_jdk17:
+        imageName: "macos-13"
+        python.version: '3.12'
+        jpypetest.fast: 'true'
+        jdk.version: '17'
 
   pool:
     vmImage: $(imageName)
   steps:
+
   - template: scripts/deps.yml
   - template: scripts/test.yml
 
@@ -97,13 +128,12 @@ jobs:
   dependsOn: Deps
   strategy:
     matrix:
-      linux-3.8:
+      linux_py38_jdk11:
         imageName: "ubuntu-16.04"
-        jdk_version: "1.11"
-        python.version: '3.8'
+        jdk.version: "11"
+        python.version: '3.9'
   pool:
     vmImage: $(imageName)
   steps:
   - template: scripts/deps.yml
   - template: scripts/debug.yml
-
diff -pruN 1.5.0-1/.azure/doc-requirements.txt 1.6.0-1/.azure/doc-requirements.txt
--- 1.5.0-1/.azure/doc-requirements.txt	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/doc-requirements.txt	2025-06-01 03:57:43.000000000 +0000
@@ -1,10 +1,8 @@
-
 # TODO: consider unpinning these?
-Pygments==2.15.0
-docutils==0.14
-commonmark==0.8.1
-recommonmark==0.5.0
-
+Pygments
+docutils
+commonmark
+recommonmark
 sphinx
 sphinx-rtd-theme
 readthedocs-sphinx-ext
diff -pruN 1.5.0-1/.azure/release.yml 1.6.0-1/.azure/release.yml
--- 1.5.0-1/.azure/release.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/release.yml	2025-06-01 03:57:43.000000000 +0000
@@ -1,6 +1,20 @@
 # JPype Release pipeline
+
+
+# NOTES:
+# - This pipeline triggers only on changes to release branches or versioning files.
+# - ManyLinux builds cover major architectures (x86_64, aarch64, i686).
+#   - ARM and musllinux jobs are included as commented placeholders for future support.
+# - All jobs use JDK 11 (LTS); newer JDKs (e.g., JDK 22) are not yet available on Azure.
+# - Python versions are updated as new releases become available on Azure-hosted agents.
+# - macOS and Windows wheels are built for all supported Python versions.
+#   - On macOS, Python is installed via a custom script for compatibility.
+# - Artifact publishing is handled consistently across all platforms.
+# - To debug a specific build job, set its `condition` to `eq(1,1)` and others to `eq(1,0)`.
+
+
 trigger: none
-pr: 
+pr:
   branches:
     include:
     -  releases/*
@@ -10,55 +24,146 @@ pr:
     - .azure/release.yml
 
 variables:
-  package_name: JPype1
+  package_name: jpype1
+  run_goal: 1  # Set to 1 to enable, 0 to disable, 2 on for special targets
 
 stages:
 - stage: Initial
   jobs:
   - job: SourceDistribution
-    pool: 
+    pool:
       vmImage: "ubuntu-latest"
     steps:
     - template: scripts/sdist.yml
+      parameters:
+        artifact: true
     - template: scripts/ivy.yml
 
 - stage: Package
   jobs:
-  # From https://iscinumpy.gitlab.io/post/azure-devops-python-wheels/
-  - job: ManyLinux
-    condition: eq(1,1)
-    strategy:
-      matrix:
-          # Disabled because it is fetching beta images
-          #        64Bit2014:
-          #          arch: aarch64
-          #          plat: manylinux2014_aarch64
-          #          image: quay.io/pypa/manylinux2014_aarch64
-          #          python.architecture: aarch64
-        64Bit:
-          arch: x86_64
-          plat: manylinux2014_x86_64
-          image: quay.io/pypa/manylinux2014_x86_64
-          python.architecture: x64
-        32Bit:
-          arch: i686
-          plat: manylinux2014_i686
-          image: quay.io/pypa/manylinux2014_i686
-          python.architecture: x86
+    # Consider switch to manylinux_2_28 for next release
+    
+  # x86_64 build
+  - job: ManyLinux_x86_64
+    condition: eq(variables['run_goal'], 1)
+    timeoutInMinutes: 360
+    displayName: "Build manylinux2014_x86_64"
     pool:
       vmImage: "ubuntu-latest"
+    variables:
+      arch: x86_64
+      plat: manylinux2014_x86_64
+      image: quay.io/pypa/manylinux2014_x86_64
+      python.architecture: x64
     steps:
-    - template: scripts/deps.yml
-    - template: scripts/wheels-linux.yml
-    - template: scripts/publish-dist.yml
+      - template: scripts/deps.yml
+      - template: scripts/wheels-linux.yml
+      - template: scripts/publish-dist.yml
+
+  # aarch64 build
+  - job: ManyLinux_aarch64
+    condition: eq(variables['run_goal'], 1)
+    timeoutInMinutes: 360
+    displayName: "Build manylinux2014_aarch64"
+    pool:
+      vmImage: "ubuntu-latest"
+    variables:
+      arch: aarch64
+      plat: manylinux2014_aarch64
+      image: quay.io/pypa/manylinux2014_aarch64
+      python.architecture: aarch64
+    steps:
+      - template: scripts/deps.yml
+      - template: scripts/wheels-linux.yml
+      - template: scripts/publish-dist.yml
+
+  # i686 build
+  - job: ManyLinux_i686
+    condition: eq(variables['run_goal'], 1)
+    timeoutInMinutes: 360
+    displayName: "Build manylinux2014_i686"
+    pool:
+      vmImage: "ubuntu-latest"
+    variables:
+      arch: i686
+      plat: manylinux2014_i686
+      image: quay.io/pypa/manylinux2014_i686
+      python.architecture: x86
+    steps:
+      - template: scripts/deps.yml
+      - template: scripts/wheels-linux.yml
+      - template: scripts/publish-dist.yml
+
+  # Consider adding these after we have the reverse bridge.  
+  # I don't want to promise something I can't deliver long term.
+  - job: Musllinux_x86_64
+    condition: eq(variables['run_goal'], 2)
+    timeoutInMinutes: 360
+    displayName: "Build musllinux_1_1_x86_64"
+    pool:
+      vmImage: "ubuntu-latest"
+    variables:
+      arch: x86_64
+      plat: musllinux_1_1_x86_64
+      image: quay.io/pypa/musllinux_1_1_x86_64
+      python.architecture: x64
+    steps:
+      - template: scripts/deps.yml
+      - template: scripts/wheels-linux.yml
+      - template: scripts/publish-dist.yml
+
+  - job: Musllinux_aarch64
+    condition: eq(variables['run_goal'], 3)
+    timeoutInMinutes: 360
+    displayName: "Build musllinux_1_1_aarch64"
+    pool:
+      vmImage: "ubuntu-latest"
+    variables:
+      arch: aarch64
+      plat: musllinux_1_1_aarch64
+      image: quay.io/pypa/musllinux_1_1_aarch64
+      python.architecture: aarch64
+    steps:
+      - template: scripts/deps.yml
+      - template: scripts/wheels-linux.yml
+      - template: scripts/publish-dist.yml
+
+  - job: Manylinux2014_ppc64le
+    condition: eq(variables['run_goal'], 4)
+    timeoutInMinutes: 360
+    displayName: "Build manylinux2014_ppc64le"
+    pool:
+      vmImage: "ubuntu-latest"
+    variables:
+      arch: ppc64le
+      plat: manylinux2014_ppc64le
+      image: quay.io/pypa/manylinux2014_ppc64le
+      python.architecture: ppc64le
+    steps:
+      - template: scripts/deps.yml
+      - template: scripts/wheels-linux.yml
+      - template: scripts/publish-dist.yml
+
+  - job: Manylinux2014_s390x
+    condition: eq(variables['run_goal'], 5)
+    timeoutInMinutes: 360
+    displayName: "Build manylinux2014_s390x"
+    pool:
+      vmImage: "ubuntu-latest"
+    variables:
+      arch: s390x
+      plat: manylinux2014_s390x
+      image: quay.io/pypa/manylinux2014_s390x
+      python.architecture: s390x
+    steps:
+      - template: scripts/deps.yml
+      - template: scripts/wheels-linux.yml
+      - template: scripts/publish-dist.yml
 
   - job: Windows_x64
-    condition: eq(1,1)
+    condition: eq(variables['run_goal'], 1)
     strategy:
       matrix:
-        Python38:
-          python.version: '3.8'
-          python.architecture: 'x64'
         Python39:
           python.version: '3.9'
           python.architecture: 'x64'
@@ -71,28 +176,29 @@ stages:
         Python312:
           python.version: '3.12'
           python.architecture: 'x64'
+        Python313:
+          python.version: '3.13'
+          python.architecture: 'x64'
     pool:
-      vmImage: "windows-2019"
+      vmImage: "windows-2022"
     steps:
     - template: scripts/deps.yml
-    - task: UsePythonVersion@0
-      inputs:
-        versionSpec: '$(python.version)'
+    - template: scripts/python.yml
+      parameters:
+        version: '$(python.version)'
         architecture: '$(python.architecture)'
     - template: scripts/jdk.yml
       parameters:
-        version: '8'
+        version: '11'
     - template: scripts/wheels.yml
     - template: scripts/publish-dist.yml
 
   - job: OSX
-    condition: eq(1,1)
+    condition: eq(variables['run_goal'], 1)
     variables:
       python.architecture: 'x64'
     strategy:
       matrix:
-        Python38:
-          python.version: '3.8'
         Python39:
           python.version: '3.9'
         Python310:
@@ -101,14 +207,16 @@ stages:
           python.version: '3.11'
         Python312:
           python.version: '3.12'
+        Python313:
+          python.version: '3.13'
     pool:
-      vmImage: "macos-11"
+      vmImage: "macos-13"
     steps:
     - template: scripts/deps.yml
     - script: .azure/scripts/osx-python.sh '$(python.version)'
       displayName: Install Python.org Python
     - template: scripts/jdk.yml
       parameters:
-        version: '8'
+        version: '11'
     - template: scripts/wheels.yml
     - template: scripts/publish-dist.yml
diff -pruN 1.5.0-1/.azure/scripts/build-wheels.sh 1.6.0-1/.azure/scripts/build-wheels.sh
--- 1.5.0-1/.azure/scripts/build-wheels.sh	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/build-wheels.sh	2025-06-01 03:57:43.000000000 +0000
@@ -1,11 +1,21 @@
 #!/bin/bash
 set -e -x
 
-# Collect the pythons
-pys=(/opt/python/cp*/bin)
+pys=()
+echo "Available Python bins:"
+ls -d /opt/python/cp*/bin 
 
-# Exclude specific Pythons (3.6)
-pys=(${pys[@]//*36*/})
+for pybin in /opt/python/cp*/bin; do
+    # Exclude 3.6, 3.7, 3.8 and any alpha/beta/rc release
+    if [[ "$dir" =~ ^cp3(6|7|8|9|14)-cp3(6|7|8|9|14)t?$ ]]; then
+        continue
+    fi
+    pys+=("$pybin")
+done
+
+# Show what you found for debugging
+echo "Found Python bins:"
+printf '%s\n' "${pys[@]}"
 
 # Compile wheels
 for PYBIN in "${pys[@]}"; do
diff -pruN 1.5.0-1/.azure/scripts/coverage.yml 1.6.0-1/.azure/scripts/coverage.yml
--- 1.5.0-1/.azure/scripts/coverage.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/coverage.yml	2025-06-01 03:57:43.000000000 +0000
@@ -10,13 +10,12 @@ steps:
 
 - script: |
     python setup.py test_java
-    pip install gcovr pytest-cov jedi
-    pip install -r test-requirements.txt
-    pip install numpy typing_extensions
+    pip install gcovr pytest-cov -r test-requirements.txt
+    pip install numpy setuptools
   displayName: 'Install requirements'
 
 - script: |
-    python setup.py --enable-coverage --enable-build-jar build_ext --inplace
+    python setup.py develop --enable-coverage --enable-build-jar
   displayName: 'Build'
 
 - script: |
@@ -29,18 +28,18 @@ steps:
     bash <(curl -s https://codecov.io/bash) -f coverage.xml -f coverage_py.xml -f coverage_java.xml -X gcov
   displayName: 'Report'
 
-- task: PublishCodeCoverageResults@1
+- task: PublishCodeCoverageResults@2
   inputs:
     codeCoverageTool: 'JaCoCo'
     summaryFileLocation: coverage_java.xml
     pathToSources: native/java
 
-- task: PublishCodeCoverageResults@1
+- task: PublishCodeCoverageResults@2
   inputs:
     codeCoverageTool: 'cobertura'
     summaryFileLocation: coverage.xml
 
-- task: PublishCodeCoverageResults@1
+- task: PublishCodeCoverageResults@2
   inputs:
     codeCoverageTool: 'cobertura'
     summaryFileLocation: coverage_py.xml
diff -pruN 1.5.0-1/.azure/scripts/debug.yml 1.6.0-1/.azure/scripts/debug.yml
--- 1.5.0-1/.azure/scripts/debug.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/debug.yml	2025-06-01 03:57:43.000000000 +0000
@@ -5,10 +5,13 @@ steps:
   inputs:
     versionSpec: '$(python.version)'
 
+- template: jdk.yml
+  parameters:
+    version: '$(jdk.version)'
+
 - script: |
     sudo apt install gdb
-    python setup.py sdist
-    pip install dist/*
+    pip install ./
   displayName: 'Build module'
 
 - script: python -c "import jpype"
diff -pruN 1.5.0-1/.azure/scripts/jdk.yml 1.6.0-1/.azure/scripts/jdk.yml
--- 1.5.0-1/.azure/scripts/jdk.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/jdk.yml	2025-06-01 03:57:43.000000000 +0000
@@ -1,14 +1,15 @@
 parameters:
 - name: version
   type: string
-  default: 8
+  default: '8'
 
 steps:
-- script: |
-    set v="##vso[task.setvariable variable=JAVA_HOME]%JAVA_HOME_${{parameters.version}}_X64%"
-    echo %v:"=%
-  condition: eq(variables['Agent.OS'], 'Windows_NT')
-
-- script: |
-    echo "##vso[task.setvariable variable=JAVA_HOME]$(JAVA_HOME_${{parameters.version}}_X64)"
-  condition: ne(variables['Agent.OS'], 'Windows_NT')
+  - task: JavaToolInstaller@0
+    inputs:
+      versionSpec: ${{ parameters.version }}
+      jdkArchitectureOption: 'x64'
+      jdkSourceOption: 'PreInstalled'
+  - bash: |
+      echo AGENT_JOBSTATUS = $AGENT_JOBSTATUS
+      if [[ "$AGENT_JOBSTATUS" == "SucceededWithIssues" ]]; then exit 1; fi
+    displayName: JDK ${{ parameters.version }} set as JAVA_HOME.
diff -pruN 1.5.0-1/.azure/scripts/osx-python.sh 1.6.0-1/.azure/scripts/osx-python.sh
--- 1.5.0-1/.azure/scripts/osx-python.sh	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/osx-python.sh	2025-06-01 03:57:43.000000000 +0000
@@ -26,6 +26,10 @@ case $PYTHON_VERSION in
 3.12)
   FULL_VERSION=3.12.0
   INSTALLER_NAME=python-$FULL_VERSION-macos11.pkg
+  ;;
+3.13)
+  FULL_VERSION=3.13.0
+  INSTALLER_NAME=python-$FULL_VERSION-macos11.pkg
 esac
 
 URL=https://www.python.org/ftp/python/$FULL_VERSION/$INSTALLER_NAME
@@ -44,4 +48,3 @@ sudo ln -s /usr/local/bin/python$PYTHON_
 which python
 python --version
 python -m ensurepip
-python -m pip install setuptools wheel
diff -pruN 1.5.0-1/.azure/scripts/python.yml 1.6.0-1/.azure/scripts/python.yml
--- 1.5.0-1/.azure/scripts/python.yml	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/.azure/scripts/python.yml	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,21 @@
+parameters:
+- name: version
+  type: string
+  default: '3.12'
+
+- name: architecture
+  type: string
+  default: 'x64'
+
+steps:
+  - task: UsePythonVersion@0
+    inputs:
+      architecture: ${{ parameters.architecture }}
+      versionSpec: ${{ parameters.version }}
+      disableDownloadFromRegistry: false # boolean. Disable downloading releases from the GitHub registry. Default: false.
+      allowUnstable: true # boolean. Optional. Use when disableDownloadFromRegistry = false. Allow downloading unstable releases. Default: false.
+      githubToken: $(githubToken) # global (secret) variable to allow API access to Github (for not hitting a rate limit while downloading).
+  - bash: |
+      echo AGENT_JOBSTATUS = $AGENT_JOBSTATUS
+      if [[ "$AGENT_JOBSTATUS" == "SucceededWithIssues" ]]; then exit 1; fi
+    displayName: Python ${{ parameters.version }} set as interpreter.
diff -pruN 1.5.0-1/.azure/scripts/sdist.yml 1.6.0-1/.azure/scripts/sdist.yml
--- 1.5.0-1/.azure/scripts/sdist.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/sdist.yml	2025-06-01 03:57:43.000000000 +0000
@@ -1,12 +1,20 @@
+parameters:
+- name: artifact
+  type: boolean
+  default: false
+
 steps:
 - task: UsePythonVersion@0
   inputs:
-    versionSpec: '3.8'
+    versionSpec: '3.10'
 - script: |
-    python -m pip install setuptools
-    python setup.py sdist
-  displayName: Build sdist
+    python -m pip install build twine
+    python -m build ./ --sdist
+    twine check dist/*
+  displayName: Build sdist and check with twine
+
 - task: PublishPipelineArtifact@0
+  condition: and(succeeded(), eq('${{ parameters.artifact }}', true))
   inputs:
     artifactName: 'artifact_SourceDistribution'
     targetPath: 'dist'
diff -pruN 1.5.0-1/.azure/scripts/test.yml 1.6.0-1/.azure/scripts/test.yml
--- 1.5.0-1/.azure/scripts/test.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/test.yml	2025-06-01 03:57:43.000000000 +0000
@@ -1,17 +1,19 @@
 # This task tests individual platforms and versions
 steps:
-- task: UsePythonVersion@0
-  inputs:
-    versionSpec: '$(python.version)'
+
+- template: python.yml
+  parameters:
+    version: '$(python.version)'
 
 - template: jdk.yml
   parameters:
-    version: 11
+    version: '$(jdk.version)'
+
+- template: sdist.yml
 
 - script: |
-    python -m pip install --upgrade pytest setuptools
-    python setup.py build_ext --inplace
-  displayName: 'Build module'
+    python -m pip install -e .
+  displayName: 'Build/install module'
 
 - script: |
     pip install numpy jedi typing_extensions
@@ -19,18 +21,20 @@ steps:
   displayName: 'Check module'
 
 - script: |
+    pip install setuptools
     python setup.py test_java
     pip install -r test-requirements.txt
   displayName: 'Install test'
 
 - script: |
+    python -m pip install -U pytest
     python -m pytest -v --junit-xml=build/test/test.xml test/jpypetest --checkjni
-  displayName: 'Test JDK 11'
+  displayName: 'Test JDK $(jdk.version) and Python $(python.version)'
   condition: eq(variables['jpypetest.fast'], 'false')
 
 - script: |
     python -m pytest -v --junit-xml=build/test/test.xml test/jpypetest --checkjni --fast
-  displayName: 'Test JDK 11 (fast)'
+  displayName: 'Test JDK $(jdk.version) and Python $(python.version) (fast)'
   condition: eq(variables['jpypetest.fast'], 'true')
 
 # presence of jpype/ seems to confuse entry_points so `cd` elsewhere
@@ -45,5 +49,4 @@ steps:
   condition: succeededOrFailed()
   inputs:
      testResultsFiles: 'build/test/test.xml'
-     testRunTitle: 'Publish test results for Python $(python.version) with JDK 11'
-
+     testRunTitle: 'Publish test results for Python $(python.version) with JDK $(jdk.version)'
diff -pruN 1.5.0-1/.azure/scripts/tracing.yml 1.6.0-1/.azure/scripts/tracing.yml
--- 1.5.0-1/.azure/scripts/tracing.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/tracing.yml	2025-06-01 03:57:43.000000000 +0000
@@ -4,6 +4,6 @@ steps:
   inputs:
     versionSpec: '3.8'
 - script: |
-    python setup.py --enable-tracing --enable-build-jar build_ext --inplace
+    python setup.py develop --enable-tracing --enable-build-jar
   displayName: 'Build'
 
diff -pruN 1.5.0-1/.azure/scripts/wheels-linux.yml 1.6.0-1/.azure/scripts/wheels-linux.yml
--- 1.5.0-1/.azure/scripts/wheels-linux.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/wheels-linux.yml	2025-06-01 03:57:43.000000000 +0000
@@ -1,7 +1,4 @@
 steps:
-- task: UsePythonVersion@0
-  inputs:
-    versionSpec: '3.8'
 
 - task: DownloadPipelineArtifact@2
   inputs:
diff -pruN 1.5.0-1/.azure/scripts/wheels.yml 1.6.0-1/.azure/scripts/wheels.yml
--- 1.5.0-1/.azure/scripts/wheels.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.azure/scripts/wheels.yml	2025-06-01 03:57:43.000000000 +0000
@@ -2,8 +2,7 @@
 steps:
 - script: |
     mkdir -p dist
-    python -m pip install --upgrade pip
-    python -m pip install --upgrade wheel setuptools
+    python -m pip install --upgrade pip setuptools -r test-requirements.txt
   displayName: 'Install dependencies'
 
 - script: |
@@ -16,20 +15,25 @@ steps:
   displayName: 'Show wheelhouse'
 
 - script: |
-    python -m pip install JPype1 --no-index -f wheelhouse
+    python -m pip install jpype1 --no-index -f wheelhouse
   displayName: 'Install module'
 
 - script: |
     python setup.py test_java
-    python -m pip install -r test-requirements.txt
-  displayName: 'Install test'
+  displayName: 'Build java tests'
 
 - script: |
     ls -l
     ls lib/
   displayName: 'Check deps'
 
+- task: PublishPipelineArtifact@0
+  inputs:
+    artifactName: 'artifact_$(Agent.JobName)_$(Agent.OS)_$(python.architecture)'
+    targetPath: 'dist'
+
 - script: |
+    rm -Rf jpype
     python -m pytest -v --junit-xml=build/test/test.xml test/jpypetest --checkjni --fast
   displayName: 'Test module'
 
diff -pruN 1.5.0-1/.bumpversion.cfg 1.6.0-1/.bumpversion.cfg
--- 1.5.0-1/.bumpversion.cfg	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.bumpversion.cfg	2025-06-01 03:57:43.000000000 +0000
@@ -1,10 +1,10 @@
 [bumpversion]
-current_version = 1.5.0
+current_version = 1.6.0
 commit = True
 tag = False
-parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\_(?P<release>[a-z]+)(?P<build>\d+))?
+parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<build>\d+))?
 serialize = 
-	{major}.{minor}.{patch}_{release}{build}
+	{major}.{minor}.{patch}.{release}{build}
 	{major}.{minor}.{patch}
 
 [bumpversion:part:release]
@@ -16,13 +16,15 @@ values =
 
 [bumpversion:part:build]
 
-[bumpversion:file:setup.py]
+[bumpversion:file:pyproject.toml]
 
 [bumpversion:file:native/python/pyjp_module.cpp]
 
 [bumpversion:file:jpype/__init__.py]
 
-[bumpversion:file:native/java/org/jpype/JPypeContext.java]
+[bumpversion:file:native/jpype_module/pom.xml]
+
+[bumpversion:file:native/jpype_module/src/main/java/org/jpype/JPypeContext.java]
 
 [bumpversion:file:doc/CHANGELOG.rst]
 search = Latest Changes:
diff -pruN 1.5.0-1/.gitattributes 1.6.0-1/.gitattributes
--- 1.5.0-1/.gitattributes	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.gitattributes	2025-06-01 03:57:43.000000000 +0000
@@ -1,4 +1,4 @@
-* test=auto
+* text=auto
 
 # Specify lf for all source files
 *.py text eol=lf
diff -pruN 1.5.0-1/.github/workflows/codeql.yml 1.6.0-1/.github/workflows/codeql.yml
--- 1.5.0-1/.github/workflows/codeql.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.github/workflows/codeql.yml	2025-06-01 03:57:43.000000000 +0000
@@ -27,13 +27,13 @@ jobs:
         uses: actions/checkout@v3
 
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v2
+        uses: github/codeql-action/init@v3
         with:
           languages: ${{ matrix.language }}
           queries: +security-and-quality
 
       - name: Autobuild
-        uses: github/codeql-action/autobuild@v2
+        uses: github/codeql-action/autobuild@v3
         if: ${{ matrix.language == 'python' }}
 
       - name: Build cpp
@@ -45,6 +45,6 @@ jobs:
         if: ${{ matrix.language == 'java' }}
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v2
+        uses: github/codeql-action/analyze@v3
         with:
           category: "/language:${{ matrix.language }}"
diff -pruN 1.5.0-1/.readthedocs.yml 1.6.0-1/.readthedocs.yml
--- 1.5.0-1/.readthedocs.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/.readthedocs.yml	2025-06-01 03:57:43.000000000 +0000
@@ -24,6 +24,6 @@ build:
     python: "3.8"
   os: ubuntu-22.04
   apt_packages:
-    - openjdk-8-jdk
+    - openjdk-11-jdk
 
 
diff -pruN 1.5.0-1/AUTHORS.rst 1.6.0-1/AUTHORS.rst
--- 1.5.0-1/AUTHORS.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/AUTHORS.rst	2025-06-01 03:57:43.000000000 +0000
@@ -2,8 +2,8 @@ Authors
 -------
 
 The original author: Steve Menard
-
-Current Maintainer: Luis Nell
+Current Lead Developer: Karl Einar Nelson
+Current Maintainer: Martin K. Scherer
 
 
 Huge thanks to these CONTRIBUTORS:
diff -pruN 1.5.0-1/LICENSE 1.6.0-1/LICENSE
--- 1.5.0-1/LICENSE	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/LICENSE	2025-06-01 03:57:43.000000000 +0000
@@ -1,187 +1,201 @@
-==============
-Apache License
-==============
-
-:Version: 2.0
-:Date: January 2004
-:URL: http://www.apache.org/licenses/
-
-------------------------------------------------------------
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-------------------------------------------------------------
-
-1. Definitions.
----------------
-
-**"License"** shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-**"Licensor"** shall mean the copyright owner or entity authorized by the
-copyright owner that is granting the License.
-
-**"Legal Entity"** shall mean the union of the acting entity and all other
-entities that control, are controlled by, or are under common control with that
-entity.  For the purposes of this definition, "control" means *(i)* the power,
-direct or indirect, to cause the direction or management of such entity,
-whether by contract or otherwise, or *(ii)* ownership of fifty percent (50%) or
-more of the outstanding shares, or *(iii)* beneficial ownership of such entity.
-
-**"You"** (or **"Your"**) shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-**"Source"** form shall mean the preferred form for making modifications,
-including but not limited to software source code, documentation source, and
-configuration files.
-
-**"Object"** form shall mean any form resulting from mechanical transformation
-or translation of a Source form, including but not limited to compiled object
-code, generated documentation, and conversions to other media types.
-
-**"Work"** shall mean the work of authorship, whether in Source or Object form,
-made available under the License, as indicated by a copyright notice that is
-included in or attached to the work (an example is provided in the Appendix
-below).
-
-**"Derivative Works"** shall mean any work, whether in Source or Object form,
-that is based on (or derived from) the Work and for which the editorial
-revisions, annotations, elaborations, or other modifications represent, as a
-whole, an original work of authorship. For the purposes of this License,
-Derivative Works shall not include works that remain separable from, or merely
-link (or bind by name) to the interfaces of, the Work and Derivative Works
-thereof.
-
-**"Contribution"** shall mean any work of authorship, including the original
-version of the Work and any modifications or additions to that Work or
-Derivative Works thereof, that is intentionally submitted to Licensor for
-inclusion in the Work by the copyright owner or by an individual or Legal
-Entity authorized to submit on behalf of the copyright owner. For the purposes
-of this definition, "submitted" means any form of electronic, verbal, or
-written communication sent to the Licensor or its representatives, including
-but not limited to communication on electronic mailing lists, source code
-control systems, and issue tracking systems that are managed by, or on behalf
-of, the Licensor for the purpose of discussing and improving the Work, but
-excluding communication that is conspicuously marked or otherwise designated in
-writing by the copyright owner as "Not a Contribution."
-
-**"Contributor"** shall mean Licensor and any individual or Legal Entity on
-behalf of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-------------------------------
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and
-such Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
----------------------------
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-------------------
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-- You must give any other recipients of the Work or Derivative Works a copy of
-  this License; and
-
-- You must cause any modified files to carry prominent notices stating that You
-  changed the files; and
-
-- You must retain, in the Source form of any Derivative Works that You
-  distribute, all copyright, patent, trademark, and attribution notices from
-  the Source form of the Work, excluding those notices that do not pertain to
-  any part of the Derivative Works; and
-
-- If the Work includes a ``"NOTICE"`` text file as part of its distribution,
-  then any Derivative Works that You distribute must include a readable copy of
-  the attribution notices contained within such ``NOTICE`` file, excluding
-  those notices that do not pertain to any part of the Derivative Works, in at
-  least one of the following places: within a ``NOTICE`` text file distributed
-  as part of the Derivative Works; within the Source form or documentation, if
-  provided along with the Derivative Works; or, within a display generated by
-  the Derivative Works, if and wherever such third-party notices normally
-  appear. The contents of the ``NOTICE`` file are for informational purposes
-  only and do not modify the License. You may add Your own attribution notices
-  within Derivative Works that You distribute, alongside or as an addendum to
-  the ``NOTICE`` text from the Work, provided that such additional attribution
-  notices cannot be construed as modifying the License. You may add Your own
-  copyright statement to Your modifications and may provide additional or
-  different license terms and conditions for use, reproduction, or distribution
-  of Your modifications, or for any such Derivative Works as a whole, provided
-  Your use, reproduction, and distribution of the Work otherwise complies with
-  the conditions stated in this License.
-
-5. Submission of Contributions.
--------------------------------
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms
-of any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
---------------
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the ``NOTICE`` file.
-
-7. Disclaimer of Warranty.
---------------------------
-
-Unless required by applicable law or agreed to in writing, Licensor provides
-the Work (and each Contributor provides its Contributions) on an **"AS IS"
-BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND**, either express or
-implied, including, without limitation, any warranties or conditions of
-**TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
-PURPOSE**. You are solely responsible for determining the appropriateness of
-using or redistributing the Work and assume any risks associated with Your
-exercise of permissions under this License.
-
-8. Limitation of Liability.
----------------------------
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License
-or out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction,
-or any and all other commercial damages or losses), even if such Contributor
-has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-----------------------------------------------
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License.
-However, in accepting such obligations, You may act only on Your own behalf and
-on Your sole responsibility, not on behalf of any other Contributor, and only
-if You agree to indemnify, defend, and hold each Contributor harmless for any
-liability incurred by, or claims asserted against, such Contributor by reason
-of your accepting any such warranty or additional liability.
-
-**END OF TERMS AND CONDITIONS**
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff -pruN 1.5.0-1/README.rst 1.6.0-1/README.rst
--- 1.5.0-1/README.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/README.rst	2025-06-01 03:57:43.000000000 +0000
@@ -1,5 +1,4 @@
-.. image:: doc/logo.png
-   :scale: 50 %
+.. image:: doc/logo_small.png
    :alt: JPype logo
    :align: center
 
diff -pruN 1.5.0-1/codecov.yml 1.6.0-1/codecov.yml
--- 1.5.0-1/codecov.yml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/codecov.yml	2025-06-01 03:57:43.000000000 +0000
@@ -11,7 +11,8 @@ coverage:
       python:
         target: 85%
         threshold: 1%
-        paths: "jpype/"
+        paths:
+        - "jpype/"
       cpp:
         target: 80%
         threshold: 1%
@@ -21,7 +22,8 @@ coverage:
       java:
         target: 75%
         threshold: 2%
-        paths: "native/java/"
+        paths:
+        - "native/java/"
 
 parsers:
   gcov:
diff -pruN 1.5.0-1/debian/changelog 1.6.0-1/debian/changelog
--- 1.5.0-1/debian/changelog	2024-01-16 21:01:49.000000000 +0000
+++ 1.6.0-1/debian/changelog	2025-09-03 19:49:17.000000000 +0000
@@ -1,3 +1,16 @@
+python-jpype (1.6.0-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release.
+  * Use dh-sequence-python3 and dh-sequence-javahelper.
+  * Use pybuild-plugin-pyproject.
+  * Simplify some debhelper overrides.
+  * Run most tests during build.
+  * Look for system-installed org.jpype.jar (closes: #1017761).
+  * Enable autopkgtest-pkg-pybuild (closes: #1035211).
+
+ -- Colin Watson <cjwatson@debian.org>  Wed, 03 Sep 2025 20:49:17 +0100
+
 python-jpype (1.5.0-1) unstable; urgency=medium
 
   * Team upload.
diff -pruN 1.5.0-1/debian/control 1.6.0-1/debian/control
--- 1.5.0-1/debian/control	2024-01-16 21:01:49.000000000 +0000
+++ 1.6.0-1/debian/control	2025-09-03 19:49:17.000000000 +0000
@@ -8,12 +8,15 @@ Build-Depends: debhelper-compat (= 13),
 	, python3-all-dev
 	, python3-setuptools
 	, python3-numpy
-	, dh-python
-	, javahelper
+	, python3-pytest <!nocheck>
+	, dh-sequence-python3
+	, dh-sequence-javahelper
+	, pybuild-plugin-pyproject
 Standards-Version: 4.6.1
 Homepage: https://github.com/originell/jpype
 Vcs-Git: https://salsa.debian.org/python-team/packages/python-jpype.git
 Vcs-Browser: https://salsa.debian.org/python-team/packages/python-jpype
+Testsuite: autopkgtest-pkg-pybuild
 
 Package: python3-jpype
 Architecture: any
diff -pruN 1.5.0-1/debian/patches/debian-java-version.patch 1.6.0-1/debian/patches/debian-java-version.patch
--- 1.5.0-1/debian/patches/debian-java-version.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/debian/patches/debian-java-version.patch	2025-09-03 19:49:17.000000000 +0000
@@ -0,0 +1,20 @@
+From: Colin Watson <cjwatson@debian.org>
+Date: Wed, 3 Sep 2025 20:18:27 +0100
+Subject: Handle Debian's OpenJDK version numbers
+
+Last-Update: 2025-09-03
+---
+ test/jpypetest/common.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/test/jpypetest/common.py b/test/jpypetest/common.py
+index 8494ff5..6f879b9 100644
+--- a/test/jpypetest/common.py
++++ b/test/jpypetest/common.py
+@@ -162,5 +162,4 @@ def java_version():
+                           "import jpype; jpype.startJVM(); "
+                           "print(jpype.java.lang.System.getProperty('java.version'))"]),
+                        encoding='ascii')
+-    # todo: make this robust for version "numbers" containing strings (e.g.) 22.1-internal
+-    return tuple(map(int, java_version.split(".")))
++    return tuple(map(int, java_version.split("-")[0].split(".")))
diff -pruN 1.5.0-1/debian/patches/series 1.6.0-1/debian/patches/series
--- 1.5.0-1/debian/patches/series	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/debian/patches/series	2025-09-03 19:49:17.000000000 +0000
@@ -0,0 +1,2 @@
+debian-java-version.patch
+system-jar.patch
diff -pruN 1.5.0-1/debian/patches/system-jar.patch 1.6.0-1/debian/patches/system-jar.patch
--- 1.5.0-1/debian/patches/system-jar.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/debian/patches/system-jar.patch	2025-09-03 19:49:17.000000000 +0000
@@ -0,0 +1,37 @@
+From: Colin Watson <cjwatson@debian.org>
+Date: Wed, 3 Sep 2025 20:41:54 +0100
+Subject: Look for system-installed org.jpype.jar
+
+Bug-Debian: https://bugs.debian.org/1017761
+Last-Update: 2025-09-03
+---
+ jpype/_core.py | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/jpype/_core.py b/jpype/_core.py
+index 61b44a9..8afe70e 100644
+--- a/jpype/_core.py
++++ b/jpype/_core.py
+@@ -298,11 +298,18 @@ def startJVM(
+ #        extra_jvm_args += ['--module-path=%s'%mp ]
+ 
+     # Get the support library
+-    support_lib = os.path.join(os.path.dirname(
+-        os.path.dirname(__file__)), "org.jpype.jar")
+-    if not os.path.exists(support_lib):
++    support_lib_paths = [
++        os.path.join(os.path.dirname(
++            os.path.dirname(__file__)), "org.jpype.jar"),
++        "/usr/share/java/org.jpype.jar",
++    ]
++    for support_lib in support_lib_paths:
++        if os.path.exists(support_lib):
++            break
++    else:
+         raise RuntimeError(
+-            "Unable to find org.jpype.jar support library at " + support_lib)
++            "Unable to find org.jpype.jar support library in " +
++            str(support_lib_paths))
+ 
+     system_class_loader = _getOption(
+         jvm_args, "-Djava.system.class.loader", keep=True)
diff -pruN 1.5.0-1/debian/rules 1.6.0-1/debian/rules
--- 1.5.0-1/debian/rules	2024-01-16 21:01:49.000000000 +0000
+++ 1.6.0-1/debian/rules	2025-09-03 19:49:17.000000000 +0000
@@ -2,15 +2,16 @@
 export JAVA_HOME=/usr/lib/jvm/default-java
 # export JAVA_HOME=/usr/lib/jvm/java-6-openjdk
 export PYBUILD_NAME=jpype
+export PYBUILD_TEST_CUSTOM=1
+# TODO: Figure out how to set up Java database access.
+export PYBUILD_TEST_ARGS=\
+	{interpreter} {dir}/setup.py test_java && \
+	{interpreter} -m pytest test/jpypetest --checkjni -k 'not test_sql_h2 and not test_sql_hsqldb and not test_sql_sqlite'
+export PYBUILD_AFTER_TEST=rm -rf {build_dir}/test/classes
 export PYBUILD_AFTER_INSTALL=dh_numpy3
-export PYBUILD_DISABLE=test
 
 %:
-	dh $@ --buildsystem=pybuild --with python3 --with javahelper
+	dh $@ --buildsystem=pybuild
 
-override_dh_python3:
-	dh_python3
-
-override_jh_installlibs:
-	jh_installlibs
+execute_after_jh_installlibs:
 	rm $(CURDIR)/debian/python3-jpype/usr/lib/python3/dist-packages/org.jpype.jar
diff -pruN 1.5.0-1/doc/CHANGELOG.rst 1.6.0-1/doc/CHANGELOG.rst
--- 1.5.0-1/doc/CHANGELOG.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/doc/CHANGELOG.rst	2025-06-01 03:57:43.000000000 +0000
@@ -4,6 +4,70 @@ Changelog
 This changelog *only* contains changes from the *first* pypi release (0.5.4.3) onwards.
 
 Latest Changes:
+- **1.6.0 - 2025-05-31**
+- **1.6.0 - 2025-01-20**
+
+  - Java components have been converted to maven style module.
+  
+  - JArray is now registered as a Sequence.
+
+  - Fixed conversion of float16 for subnormal numbers.
+
+  - Fixed segmentation fault on null String.
+
+  - Fixed bugs with java.util.List concat and repeat methods.
+
+  - Enhancement to JProxy to handle wrapping an existing Python object with a Java
+    interface.  
+
+  - Fixed bug in which using an interface the derived from Map with JProxy failed.
+
+  - Fixed a bug in which JPype did not respect a JConversion between two Java classes.
+
+  - Enhancement in convertToDirectBuffer to support wrapping bytes and readonly
+    memoryviews as readonly java ByteBuffers.
+
+- **1.5.2 - 2025-01-20**
+
+  - Roll back agent change due to misbehaving JVM installs.
+
+  - Correct issues with non-ascii path for jdbc connections and forName.
+
+- **1.5.1 - 2024-11-09**
+
+  - Future proofing for Python 3.14
+
+  - Support for Python 3.13
+
+  - Allow access to default methods implemented in interfaces when using ``@JImplements``.
+
+  - Added support for typing ``JArray`` (Java type only), e.g. ``JArray[java.lang.Object]`` ``"JArray[java.lang.Object]"``
+
+  - Fixed uncaught exception while setting traceback causing issues in Python 3.11/3.12.
+
+  - Use PEP-518 and PEP-660 configuration for the package, allowing editable and
+    configurable builds using modern Python packaging tooling.
+    Where before ``python setup.py --enable-tracing develop``, now can be done with
+    ``pip install --editable ./ --config-setting="--install-option=--enable-tracing"``.
+    The old setup.py usage remains, but is discouraged, and the arguments are now passed
+    after the command (previously they were specified before).
+
+  - Use PEP-518 configuration for the package, allowing
+    configurable builds using more up-to-date Python packaging tooling.
+    For editable installs, ``python setup.py --enable-tracing develop``
+    must now be done with ``python setup.py develop --enable-tracing``.
+
+  - Update for tests for numpy 2.0.
+
+  - Support of np.float16 conversion with arrays.
+
+  - Fixed a problem that caused ``dir(jpype.JPackage("mypackage"))`` to fail if
+    the class path contained non-ascii characters. See issue #1194.
+
+  - Fixed ``BufferOverflowException`` in ``JUnpickler`` when decoding
+    multiple Java objects.
+
+
 - **1.5.0 - 2023-04-03**
 
   - Support for Python 3.12
@@ -17,6 +81,8 @@ Latest Changes:
   - Java exceptions that occur in inequality comparisons now map to Python
     TypeError.
 
+- **1.4.2_dev0 - 2022-10-26**
+
   - Fixed crash when calling subscript on JArray.
 
   - Fixed direct byte buffers not reporting nbytes correctly when cast to
diff -pruN 1.5.0-1/doc/add_labels.py 1.6.0-1/doc/add_labels.py
--- 1.5.0-1/doc/add_labels.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/doc/add_labels.py	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,134 @@
+import re
+
+def sanitize_label(text):
+    """
+    Sanitize the label text by removing or replacing special characters.
+    Only allow alphanumeric characters and underscores.
+    """
+    # Replace spaces with underscores
+    text = text.replace(" ", "_")
+    # Remove any characters that are not alphanumeric or underscores
+    text = re.sub(r"[^\w]", "", text)
+    return text.lower()
+
+
+def sync_labels(input_file, output_file):
+    """
+    Synchronize labels with headers in an RST file. Labels are generated based on the chapter, header, subheader, and 
+    sub-subheader structure. The script ensures anchors appear immediately before the correct header text, preserves 
+    all section content, and avoids duplicating labels.
+
+    Header Hierarchy:
+    - Chapters are identified by lines underlined with `*`.
+    - Headers are identified by lines underlined with `=`.
+    - Subheaders are identified by lines underlined with `-`.
+    - Sub-subheaders are identified by lines underlined with `~`.
+
+    Label Format:
+    - Labels follow the format: `.. _chapter_header_subheader_subsubheader:`
+    - Labels are generated dynamically based on the text of the header and its position in the hierarchy.
+    """
+    # Regular expressions to identify underline patterns for different header levels
+    underline_patterns = {
+        "*": "chapter",
+        "=": "header",
+        "-": "subheader",
+        "~": "subsubheader",
+    }
+
+    # Variables to track the current hierarchy of headers
+    current_chapter = None
+    current_header = None
+    current_subheader = None
+
+    # Read the input file
+    with open(input_file, "r") as infile:
+        lines = infile.readlines()
+
+    # Initialize output lines
+    output_lines = []
+    buffer = None  # Holds the previous line to check for headers
+    last_label = None
+    line_count = 0
+
+    for i, line in enumerate(lines):
+        # Debugging: Print the current line being processed
+        print(f"Processing line {i}: {line.strip()}")
+
+        if line.startswith(".."):
+            last_label = line
+            line_count = 0
+
+        # Check if the line is an underline pattern
+        if re.match(r"^\*+$", line):
+            # Process chapter
+            current_chapter = sanitize_label(buffer.strip().lower().replace(" ", "_")) if buffer else None
+            label = f".. _{current_chapter}:\n\n" if current_chapter else None
+            if label and not line_count == 2:
+                print(f"Detected chapter: {current_chapter}, adding label: {label.strip()}")
+                output_lines.append(label)
+            if buffer:
+                print(f"Detected chapter (skip): {current_chapter}, adding label: {label.strip()}")
+                output_lines.append(buffer)  # Add the header text immediately after the label
+            output_lines.append(line)  # Add the underline itself
+            buffer = None
+        elif re.match(r"^=+$", line):
+            # Process header
+            current_header = sanitize_label(buffer.strip().lower().replace(" ", "_")) if buffer else None
+            if not current_header:  # Fallback for empty buffer
+                current_header = "unknown_header"
+            label = f".. _{current_chapter}_{current_header}:\n\n" if current_header else None
+            if label and not line_count == 2:
+                print(f"Detected header: {current_header}, adding label: {label.strip()}")
+                output_lines.append(label)
+            if buffer:
+                print(f"Detected header (skip): {current_chapter}, adding label: {label.strip()}")
+                output_lines.append(buffer)  # Add the header text immediately after the label
+            output_lines.append(line)  # Add the underline itself
+            buffer = None
+        elif re.match(r"^-+$", line):
+            # Process subheader
+            current_subheader = sanitize_label(buffer.strip().lower().replace(" ", "_")) if buffer else None
+            label = f".. _{current_chapter}_{current_subheader}:\n\n" if current_subheader else None
+            if label and not line_count == 2:
+                print(f"Detected subheader: {current_subheader}, adding label: {label.strip()}")
+                output_lines.append(label)
+            if buffer:
+                output_lines.append(buffer)  # Add the header text immediately after the label
+            output_lines.append(line)  # Add the underline itself
+            buffer = None
+        elif re.match(r"^~+$", line):
+            # Process sub-subheader
+            subsubheader_text = sanitize_label(buffer.strip().lower().replace(" ", "_")) if buffer else None
+            label = f".. _{current_chapter}_{subsubheader_text}:\n\n" if subsubheader_text else None
+            if label and not line_count == 2:
+                print(f"Detected sub-subheader: {subsubheader_text}, adding label: {label.strip()}")
+                output_lines.append(label)
+            if buffer:
+                output_lines.append(buffer)  # Add the header text immediately after the label
+            output_lines.append(line)  # Add the underline itself
+            buffer = None
+        else:
+            # If the line isn't an underline, store it in the buffer
+            if buffer:
+                output_lines.append(buffer)  # Add the previous line to the output
+            buffer = line  # Store the current line for processing
+            if not line.isspace():
+                line_count += 1
+
+    if buffer is not None:
+        output_lines.append(buffer)
+
+    # Write the output to the specified file
+    with open(output_file, "w") as outfile:
+        outfile.writelines(output_lines)
+
+    print(f"Labels synchronized successfully! Output written to {output_file}")
+
+
+# Input and output file paths
+input_file = "userguide.rst"
+output_file = "userguide_with_synced_labels.rst"
+
+# Run the label synchronization
+sync_labels(input_file, output_file)
diff -pruN 1.5.0-1/doc/boilerplate.txt 1.6.0-1/doc/boilerplate.txt
--- 1.5.0-1/doc/boilerplate.txt	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/doc/boilerplate.txt	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,51 @@
+Today, we are working on modifying the JPype documentation. The document I 
+have provided is in reStructuredText (RST) format. All responses must adhere 
+to the following guidelines:
+
+1. **Line Length**:
+   - All responses must maintain an 80-character line length limit.
+
+2. **Formatting**:
+   - Match the existing format in terms of anchors, linkage, and overall 
+     structure.
+   - Header levels must follow the established hierarchy:
+     - ****** Chapter Level
+     - ====== Header Level
+     - ------ Subheader Level
+     - ~~~~~~ Subheader Level
+
+3. **Content Style**:
+   - When revisions are presented, they must match the header levels and 
+     formatting of the existing text.
+   - Use descriptive text instead of bullet lists to ensure the document 
+     reads as a cohesive narrative rather than an outline.
+
+4. **Sphinx Compatibility**:
+   - The resulting RST file must conform to Sphinx's interpretation of RST.
+   - Ensure proper use of directives, roles, and syntax supported by Sphinx 
+     to avoid processing errors.
+   - Test the RST file with Sphinx to verify compatibility and correctness 
+     before finalizing revisions.
+
+5. **Avoid Speculative Material**:
+   - Ensure all revisions are based on verified information, authoritative 
+     sources, or established best practices.
+   - Avoid unverified claims, ambiguous statements, or unsupported examples.
+   - Do not include predictions or assumptions about future behavior unless 
+     explicitly supported by authoritative sources or documentation.
+   - Provide sufficient context and explanation for all claims, examples, 
+     and workflows to ensure clarity and reliability.
+
+By following these guidelines, all revisions will align with the existing 
+style and structure of the JPype documentation.
+
+
+
+
+Typical prompts:
+
+Please review the document for consistency of style between the different
+sections. Identify sections that no longer fit the description of a narrative
+(excluding sections in which a narrative would not be expected such as
+glossary)
+
diff -pruN 1.5.0-1/doc/conf.py 1.6.0-1/doc/conf.py
--- 1.5.0-1/doc/conf.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/doc/conf.py	2025-06-01 03:57:43.000000000 +0000
@@ -207,7 +207,6 @@ html_theme = 'default'
 #html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
 
 # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
 on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
@@ -215,7 +214,6 @@ on_rtd = os.environ.get('READTHEDOCS', N
 if not on_rtd:  # only import and set the theme if we're building docs locally
     import sphinx_rtd_theme
     html_theme = 'sphinx_rtd_theme'
-    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 
 # otherwise, readthedocs.org uses their theme by default, so no need to specify it
 
diff -pruN 1.5.0-1/doc/dbapi2.rst 1.6.0-1/doc/dbapi2.rst
--- 1.5.0-1/doc/dbapi2.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/doc/dbapi2.rst	2025-06-01 03:57:43.000000000 +0000
@@ -5,7 +5,7 @@ JPype DBAPI2 Guide
 `Introduction`
 ==============
 
-One common use of JPype is to provide access to databases used JDBC.  The JDBC
+One common use of JPype is to provide access to databases using JDBC.  The JDBC
 API is well established, very capable, and supports most databases.
 JPype can be used to access JDBC both directly or through the use of the Python
 DBAPI2 as layed (see PEP-0249_).  Unfortunately, the Python API leaves a lot of
@@ -15,7 +15,7 @@ The JPype dbapi2 module provides our imp
 Normally the Python API has to deal with two different type systems, Python
 and SQL.  When using JDBC, we have the added complexity that Java types are
 used to communicate with the driver.  We have introduced concepts appropriate
-to handle this addition complexity.
+to handle this additional complexity.
 
 
 `Module Interface`
@@ -49,7 +49,7 @@ These values are constants.
     The threadsafety level is 2 meaning "Threads may share the module and
     connections".  But the actual threading level depends on the driver
     implementation that JDBC is connected to.  Connections for many databases
-    are synchronized so they can be shared, but threads must execute statement
+    are synchronized so they can be shared, but threads must execute statements
     in series.  Connections in the module are implemented in Python and 
     have per object resources that cannot be shared.  Attempting to use a
     connection with a thread other than the thread that created it will
@@ -91,7 +91,7 @@ exceptions:
 .. autoclass:: jpype.dbapi2.ProgrammingError
 .. autoclass:: jpype.dbapi2.NotSupportedError
 
-Python exceptions are more fine grain than JDBC exceptions.  Whereever possible
+Python exceptions are more fine grain than JDBC exceptions.  Wherever possible
 we have redirected the Java exception to the nearest Python exception.  However,
 there are cases in which the Java exception may appear.  Those exceptions
 inherit from `:py:class:jpype.dbapi2.Error`.  This is the exception inheritance layout::
@@ -127,7 +127,7 @@ Type Access
 
 JPype dbapi2 provides two different maps which serve to convert data
 between Python and SQL types.  When setting parameters and fetching 
-results, Java types are used.  The connection provides to maps for converting
+results, Java types are used.  The connection provides two maps for converting
 the types of parameters.  An `adapter <adapters_>`_ is used to translate from a Python
 type into a Java type when setting a parameter.  Once a result is produced,
 a `converter <converters_>`_ can be used to translate the Java type back into a Python type.
@@ -143,13 +143,13 @@ adapters_
 Whenever a Python type is passed to a statement, it must first be converted
 to the appropriate Java type.  This can be accomplished in a few ways.  The
 user can manually convert to the correct type by constructing a Java object or
-applying the JPype casting operator.  Some Java types have built in implicit
+applying the JPype casting operator.  Some Java types have built-in implicit
 conversions from the corresponding type.  For all other conversions, an adapter.
 An adapter is defined as a type to convert from and a conversion function which 
 takes a single argument that returns a Java object.
 
 The adapter maps are stored in the connection.  The adapter map can be
-supplied when calling `connect`_ , or added to the map later
+supplied when calling `connect`_, or added to the map later
 through the `adapters <connection.adapters_>`_ property. 
 
 
@@ -159,14 +159,14 @@ setters_
 --------
 
 A setter transfers the Java type into a SQL parameter.  There are multiple
-types can an individual parameter may accept.  The type of setter is determined
+types that an individual parameter may accept.  The type of setter is determined
 by the JDBC type.  Each individual JDBC type can have its own setter.  Not
-every database supports the same setter.  There is a default setter may that
-would work for most purposes.  Setters can also be set individually using 
+every database supports the same setter.  There is a default setter that
+should work for most purposes.  Setters can also be set individually using 
 the ``types`` argument to the ``.execute*()`` methods.  The setter is a 
 function which processes the database metadata into a type.
 
-Setters can supplied as a map to `connect`_ or by accessing
+Setters can be supplied as a map to `connect`_ or by accessing
 the `setter <connection.setters_>`_ property on a Connection.
 
 .. autofunction:: jpype.dbapi2.SETTERS_BY_META
@@ -177,7 +177,7 @@ the `setter <connection.setters_>`_ prop
 converters_
 -----------
 
-When a result is fetched the database, it is returned as Jave type.  This Java
+When a result is fetched from the database it is returned as a Java type.  This Java
 type then has a converter applied.  Converters are stored in a map holding the 
 type as key and a converter function that takes one argument and returns the desired type.
 The default converter map will convert all types to Python.  This can be 
@@ -207,7 +207,7 @@ function that uses the metadata to find
 `Connection Objects`_
 =====================
 
-A Connection object can be created using the using `connect`_ function.  Once a
+A Connection object can be created by using the `connect`_ function.  Once a
 connection is established the resulting Connection contains the following.
 
 .. autoclass:: jpype.dbapi2.Connection
@@ -230,7 +230,7 @@ transaction support is implemented (see
   :members:
 
 
-Cursors can act as an iterator.  So to get the contents of table one
+Cursors can act as an iterator.  So to get the contents of a table one
 could use code like:
 
 .. code-block:: python
@@ -253,7 +253,7 @@ the `.execute*()` method are untyped.  W
 a Python string object, it doesn't know if it should be bound as a
 simple `CHAR` column, as a raw `BINARY` item, or as a `DATE`.
 
-This is less of a problem in JPype dbapi2 than in a typically 
+This is less of a problem in JPype dbapi2 than in a typical 
 dbapi driver as we have strong typing backing the connection,
 but we are still required to supply methods to construct individual
 SQL types.  These functions are:
@@ -266,9 +266,9 @@ SQL types.  These functions are:
 .. autofunction::  jpype.dbapi2.TimestampFromTicks
 .. autofunction::  jpype.dbapi2.Binary
 
-For the most part these constructors are largely redundant as 
+For the most part these constructors are largely redundant because 
 adapters can provide the same functionality and Java types
-can directly use to communicate type information.
+can be used directly to communicate type information.
 
 .. `JDBC Types`
 
@@ -276,8 +276,8 @@ can directly use to communicate type inf
 =============
 
 In the Python DBAPI2, the SQL type system is normally reduced to a subset
-of the SQL types by mapping multiple types together for example ``STRING``
-covers the types `STRING`, `CHAR`, `NCHAR` , `NVARCHAR` , `VARCHAR`, 
+of the SQL types by mapping multiple types together. For example, ``STRING``
+covers types `STRING`, `CHAR`, `NCHAR` , `NVARCHAR` , `VARCHAR`, 
 and `OTHER`.  JPype dbapi2 supports both the recommended Python types and
 the fine grain JDBC types.  Each type is represented by an object 
 of type JBDCType.
@@ -288,10 +288,11 @@ of type JBDCType.
 The following types are defined with the correspond Python grouping, the
 default setter, getter, and Python type.  For types that support more than
 one type of getter, the special getter can be applied as the converter for
-the type.  For example, the defaulf configuration has ``getter[BLOB] = BINARY.get``,
+the type.  For example, the default configuration has ``getter[BLOB] = BINARY.get``,
 to get the Blob type use ``getter[BLOB] = BLOB.get`` or specify it when
 calling `use <cursor.use_>`_.
 
+.. The link to cursor.use above appears to be broken. Does cursor.use exist?
 
 ======== ======================== =================== ============== ================= ===============
 Group    JDBC Type                Default Getter      Default Setter PyTypes           Special Getter 
@@ -365,7 +366,7 @@ A user can declare a new type using ``JD
 new type which must match a SQL typename.  Use ``typeinfo`` on the connection to 
 get the list of available types.
 
-It may necessary to define a custom getter function which defining a new type
+It may be necessary to define a custom getter function when defining a new type
 so that the custom return type accurately reflects the column type.
 
 .. code-block:: python
@@ -381,11 +382,11 @@ so that the custom return type accuratel
 Interactions with prepared statements
 -------------------------------------
 
-Certain calls can be problematic for dbapi2 depending on driver.  In
+Certain calls can be problematic for dbapi2 depending on the driver.  In
 particular, SQL calls which invalidate the state of the connection will issue
-an exception when the connection is used.   In HSQLDB the statement
-``cur.execute('shutdown')`` invalidates the connection which when the statement
-is then automatically closed will then produce an exception.
+an exception when the connection is used.  For example, when using HSQLDB, the
+statement ``cur.execute('shutdown')`` will invalidate and close the connection,
+causing an exception to be raised.
 
 This exception is due to a conflict between dbapi2, Java, and HSQLDB
 specifications.  Dbapi2 requires that statements be executed as prepared
@@ -394,7 +395,7 @@ connection is already closed, and HSQLBD
 ``isClosed``.  Thus executing a shutdown through dbapi2 would be expected to
 close the prepared statement on an invalid connection resulting in an error.
 
-We can address these sort of driver specific behaviors by applying a customizer
+We can address these sorts of driver specific behaviors by applying a customizer
 to the Java class to add additional behaviors.
 
 .. code-block:: python
@@ -416,7 +417,7 @@ Conclusion
 ==========
 
 This wraps up the JPype dbapi2 module.  Because JDBC supports many different
-dataase drivers, not every behavior is defined on every driver.  Consult the
+database drivers, not every behavior is defined on every driver.  Consult the
 driver specific information to determine what is available.  
 
 The dbapi2 does not fully cover all of the capabilities of the JDBC driver.  To
diff -pruN 1.5.0-1/doc/develguide.rst 1.6.0-1/doc/develguide.rst
--- 1.5.0-1/doc/develguide.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/doc/develguide.rst	2025-06-01 03:57:43.000000000 +0000
@@ -104,7 +104,7 @@ can be created automatically as a result
 thrown to the user.
 
 Because the classes are created dynamically, the class structure
-uses a lot of Python meta programming. 
+uses a lot of Python meta programming.
 Each class wrapper derives from the class wrappers of each of the
 wrappers corresponding to the Java classes that each class extends
 and implements. The key to this is to hacked ``mro``. The ``mro``
@@ -119,18 +119,18 @@ resource types
 
 JPype largely maps to the same concepts as Python with a few special elements.
 The key concept is that of a Factory which serves to create Java resources
-dynamically as requested.  For example there is no Python notation to 
+dynamically as requested.  For example there is no Python notation to
 create a ``int[][]`` as the concept of dimensions are fluid in Python.
 Thus a factory type creates the actual object instance type with
-``JArray(JInt,2)``  Like Python objects, Java objects derives from a 
-type object which is called ``JClass`` that serves as a meta type for 
-all Java derived resources.  Additional type like object ``JArray`` 
+``JArray(JInt,2)``  Like Python objects, Java objects derives from a
+type object which is called ``JClass`` that serves as a meta type for
+all Java derived resources.  Additional type like object ``JArray``
 and ``JInterface`` serve to probe the relationships between types.
 Java object instances are created by calling the Java class wrapper just
 like a normal Python class.  A number of pseudo classes serve as placeholders
 for Java types so that it is not necessary to create the type instance
-when using.  These aliased classes are ``JObject``, ``JString``, and 
-``JException``.   Underlying all Java instances is the concept of a 
+when using.  These aliased classes are ``JObject``, ``JString``, and
+``JException``.   Underlying all Java instances is the concept of a
 ``jvalue``.
 
 ``jvalue``
@@ -140,9 +140,9 @@ In the earlier design, wrappers, primiti
 concepts. At the JNI layer these are unified by a common element called
 jvalue. A ``jvalue`` is a union of all primitives with the jobject. The jobject
 can represent anything derived from Java object including the pseudo class
-jstring. 
+jstring.
 
-This has been replaced with a Java slot concept which holds an instance of 
+This has been replaced with a Java slot concept which holds an instance of
 ``JPValue`` which holds a pointer to the C++ Java type wrapper and a Java
 jvalue union.  We will discuss this object further in the CPython section.
 
@@ -159,7 +159,7 @@ construct the wrappers for ``java.lang.O
 difficulty is that we need reflection to get methods from Java and those
 are part of ``java.lang.Class``, but class inherits from ``java.lang.Object``.
 Thus Object and the interfaces that Class inherits must all be created
-blindly.  The order of bootstrapping is controlled by specific sequence 
+blindly.  The order of bootstrapping is controlled by specific sequence
 of boot actions after the JVM is started in ``startJVM``.  The class instance
 ``class_`` may not be accessed until after all of the basic class, object,
 and exception types have been loaded.
@@ -268,7 +268,7 @@ an instance which will correspond to a J
 method, or value.
 
 Jpype objects work with the inner layers by inheriting from a set of special
-``_jpype`` classes.  This class hiarachy is mantained by the meta class 
+``_jpype`` classes.  This class hiarachy is mantained by the meta class
 ``_jpype._JClass``.  The meta class does type hacking of the Python API
 to insert a reserved memory slot for the ``JPValue`` structure.  The meta
 class is used to define the Java base classes:
@@ -279,7 +279,7 @@ class is used to define the Java base cl
  * ``_JObject`` - Base type of all Java object instances extending Python object.
  * ``_JNumberLong`` - Base type for integer style types extending Python int.
  * ``_JNumberFloat`` - Base type for float style types extending Python float.
- * ``_JNumberChar`` - Special wrapper type for JChar and java.lang.Character 
+ * ``_JNumberChar`` - Special wrapper type for JChar and java.lang.Character
    types extending Python float.
  * ``_JException`` - Base type for exceptions extending Python Exception.
  * ``_JValue`` - Generic capsule representing any Java type or instance.
@@ -288,7 +288,7 @@ These types are exposed to Python to imp
 to the behavior expected by the Python type.  Under the hood these types are
 largely ignored.  Instead the internal calls for the Java slot to determine
 how to handle the type.  Therefore, internally often Python methods will be
-applied to the "wrong" type as the requirement for the method can be satisfied 
+applied to the "wrong" type as the requirement for the method can be satisfied
 by any object with a Java slot rather than a specific type.
 
 See the section regarding Java slots for details.
@@ -312,7 +312,7 @@ and any missing resource are reported as
 ~~~~~~~~~~~~~~~~~~~
 
 The class wrappers have a metaclass ``_jpyep._JClass`` which serves as
-the guardian to ensure the slot is attached, provide for the inheritance 
+the guardian to ensure the slot is attached, provide for the inheritance
 checks, and control access to static fields and methods.  The slot holds
 a java.lang.Class instance but it does not have any of the methods normally
 associate with a Java class instance exposed.  A java.lang.Class instance
@@ -333,9 +333,9 @@ it tracing down errors. The beans method
 the old properties API.
 
 The naming on this class is a bit deceptive. It does not correspond
-to a single method but rather all the overloads with the same name.  
+to a single method but rather all the overloads with the same name.
 When called it passes to with the arguments to the C++ layer where
-it must be resolved to a specific overload. 
+it must be resolved to a specific overload.
 
 This class is stored directly in the class wrappers.
 
@@ -364,9 +364,9 @@ specialized implementations to allow for
 ~~~~~~~~~~~~~~~~~~~~~
 
 This class provides ``synchronized`` to JPype.  Instances of this
-class are created and held using ``with``.  It has two methods 
+class are created and held using ``with``.  It has two methods
 ``__enter__`` and ``__exit__`` which hook into the Python RAII
-system.  
+system.
 
 
 ``_JValue`` class
@@ -377,7 +377,7 @@ types.  These each have the Python funct
 a Java slot.  The most generic of these is ``_JValue`` which is simply
 a capsule holding the Java C++ type wrapper and a Java jvalue union.
 CPython methods for the ``PyJPValue`` apply to all CPython objects
-that hold a Java slot.  
+that hold a Java slot.
 
 Specific implementation exist for object, numbers, characters, and
 exceptions.  But fundimentally all are treated the same internally
@@ -385,7 +385,7 @@ and thus the CPython type is effectively
 
 Unlike ``jvalue`` we hold the object type in the C++ ``JPValue``
 object.  The class reference is used to determine how to match the arguments
-to methods. The class may not correspond to the actual class of the 
+to methods. The class may not correspond to the actual class of the
 object. Using a class other than the actual class serves to allow
 an object to be cast and thus treated like another type for the purposes
 of overloading. This mechanism is what allows the ``JObject`` factory
@@ -404,11 +404,11 @@ within the type structure and by using a
 The reserved space in order by number and thus avoids the need to access the
 dictionary while the bit flags serve to determine the type without traversing
 the ``__mro__`` structure.  We had to implement the same effect which deriving
-from a wide variety for Python types including type, object, int, long, and 
-Exception.  Adding the slot directly to the type and objects base memory 
+from a wide variety for Python types including type, object, int, long, and
+Exception.  Adding the slot directly to the type and objects base memory
 does not work because these types all have different memory layouts.  We could
 have a table look up based on the type but because we must obey both the CPython
-and the Java object hierarchy at the same time it cannot be done within the 
+and the Java object hierarchy at the same time it cannot be done within the
 memory layout of Python objects.  Instead we have to think outside the box,
 or rather outside the memory footprint of Python objects.
 
@@ -422,9 +422,9 @@ all specialize static members and there
 user defined dynamic slots.
 
 Therefore, instead we will add extra memory outside the view of Python
-objects though the use of a custom allocator. We intercept the call to 
+objects though the use of a custom allocator. We intercept the call to
 create an object allocation and then call the regular Python allocators
-with the extra memory added to the request.  As our extrs slot has 
+with the extra memory added to the request.  As our extrs slot has
 resource in the form of Java global references associated with it, we
 must deallocate those resource regardless of the type that has been
 extended.  We perform this task by creating a custom finalize method to
@@ -443,7 +443,7 @@ effectively a slot as we can test and ac
 Accessing the slot requires testing if the slot exists for the object,
 then computing the sice of the object using the basesize and itemsize
 associate with the type and then offsetting the Python object pointer
-appropriately.  The overall cost is O(1), though is slightly more 
+appropriately.  The overall cost is O(1), though is slightly more
 heavy that directly accesssing an offset.
 
 
@@ -470,7 +470,7 @@ Java must have a ``try`` block around th
 
 We use a routine pattern of code to interact with Java to achieve this:
 
-.. code-block:: cpp 
+.. code-block:: cpp
 
     PyObject* dosomething(PyObject* self, PyObject* args)
     {
@@ -598,26 +598,26 @@ back to Java.
 Memory management
 ~~~~~~~~~~~~~~~~~
 
-Java provides built in memory management for controlling the lifespan of 
+Java provides built in memory management for controlling the lifespan of
 Java objects that are passed through JNI. When a Java object is created
 or returned from the JVM it returns a handle to object with a reference
 counter. To manage the lifespan of this reference counter a local frame
 is created. For the duration of this frame all local references will
 continue to exist. To extend the lifespan either a new global reference
 to the object needs to be created, or the object needs to be kept.  When
-the local frame is destroyed all local references are destroyed with 
-the exception of an optional specified local return reference.  
+the local frame is destroyed all local references are destroyed with
+the exception of an optional specified local return reference.
 
 We have wrapped the Java reference system with the wrapper ``JPLocalFrame``.
 This wrapper has three functions. It acts as a RAII (Resource acquisition
 is initialization) for the local frame. Further, as creating a local
 frame requires creating a Java env reference and all JNI calls require
 access to the env, the local frame acts as the front end to call all
-JNI calls. Finally as getting ahold of the env requires that the 
+JNI calls. Finally as getting ahold of the env requires that the
 thread be attached to Java, it also serves to automatically attach
 threads to the JVM. As accessing an unbound thread will cause a segmentation
 fault in JNI, we are now safe from any threads created from within
-Python even those created outside our knowledge.  (I am looking at 
+Python even those created outside our knowledge.  (I am looking at
 you spyder)
 
 Using this pattern makes the JPype core safe by design.  Forcing JNI
@@ -674,7 +674,7 @@ you always store only the global referen
       // hold the resource longer.
       if (cond)
       {
-        // okay we need to keep this reference, so make a 
+        // okay we need to keep this reference, so make a
         // new global reference to it.
         global = frame.NewGlobalRef(value.l);
       }
@@ -684,7 +684,7 @@ But don't mistake this as an invitation
 Global reference are global, thus will hold the member until the reference is
 destroyed. C++ exceptions can lead to missing the unreference, thus global
 references should only happen when you are placing the Java object into a class
-member variable or a global variable. 
+member variable or a global variable.
 
 To help manage global references, we have ``JPRef<>`` which holds a global
 reference for the duration of the C++ lifespace.  This is the base class for
@@ -709,7 +709,7 @@ than creating a new one.
         // Do the required work
         jobject obj = frame.CallObjectMethodA(globalObj, methodRef, params);
 
-        // We must not call keep here or we will terminate 
+        // We must not call keep here or we will terminate
         // a frame we do not own.
         return obj;
     }
@@ -726,13 +726,13 @@ best case is using the stale reference w
 reference will be a live reference to another object and it will produce an
 error which seems completely irrelevant to anything that was being called.
 Horrible case, the live object does not object to bad call and it all silently
-proceeds down the road another two miles before coming to flaming death. 
+proceeds down the road another two miles before coming to flaming death.
 
-Moral of the story, always create a local frame even if you are handling a global 
-reference. If passed or returned a reference of any kind, it is a borrowed reference 
+Moral of the story, always create a local frame even if you are handling a global
+reference. If passed or returned a reference of any kind, it is a borrowed reference
 belonging to the caller or being held by the current local frame. Thus it must
 be treated accordingly. If you have to hold a global use the appropraite ``JPRef``
-class to ensure it is exception and dtor safe. For further information 
+class to ensure it is exception and dtor safe. For further information
 read ``native/common/jp_javaframe.h``.
 
 
@@ -830,7 +830,7 @@ Currently this is backed by a C++ map of
 
 The typemanager provides a number lookup methods.
 
-.. code-block:: cpp 
+.. code-block:: cpp
 
   // Call from within Python
   JPClass* JPTypeManager::findClass(const string& name)
@@ -855,7 +855,7 @@ to support Java native methods appears i
 Once started the reference queue is mostly transparent. registerRef is used
 to bind a Python object live span to a Java object.
 
-.. code-block:: cpp 
+.. code-block:: cpp
 
   void JPReferenceQueue::registerRef(jobject obj, PyObject* hostRef)
 
@@ -884,7 +884,7 @@ jar.
 The classloader is mostly transparent. It provides one method called findClass
 which loads a class from the internal jar.
 
-.. code-block:: cpp 
+.. code-block:: cpp
 
   jclass JPClassLoader::findClass(string name)
 
@@ -900,9 +900,9 @@ encoding such as UTF8, Java used UTF16 e
 Modified-UTF8. ``JPEncoding`` deals with converting this unusual encoding into
 something that Python can understand.
 
-The key method in this module is transcribe with signature 
+The key method in this module is transcribe with signature
 
-.. code-block:: cpp 
+.. code-block:: cpp
 
   std::string transcribe(const char* in, size_t len,
       const JPEncoding& sourceEncoding,
@@ -943,7 +943,7 @@ must be enabled with a compiler switch t
 one of the cpp files in the native directory to mark the build as dirty, then
 compile the ``jpype`` module with: ::
 
-     python setup.py --enable-tracing develop
+     python setup.py develop --enable-tracing
 
 Once built run a short test program that demonstrates the problem and capture the
 output of the terminal to a file. This should allow the developer to isolate
@@ -959,6 +959,14 @@ To use the Python tracing, start Python
     python -m trace --trace myscript.py
 
 
+Coverage
+--------
+Some of the tests require additional instrumentation to run, this can be enabled
+with the ``enable-coverage`` option::
+
+    python setup.py develop --enable-coverage
+
+
 Debugging issues
 ----------------
 
@@ -967,7 +975,7 @@ to turn to a general purpose tool like g
 easy to debug. Python can be difficult to properly monitor especially with
 tools like valgrind due to its memory handling. Java is also challenging to
 debug. Put them together and you have the mother of all debugging issues. There
-are a number of complicating factors. Let us start with how to debug with gdb. 
+are a number of complicating factors. Let us start with how to debug with gdb.
 
 Gdb runs into two major issues, both tied to the signal handler.
 First, Java installs its own signal handlers that take over the entire process
@@ -979,7 +987,7 @@ catching the segfault before Java catche
 you can't capture a meaningful non-interactively produced core file, and you
 can't get an interactive session to work.
 
-Fortunately there are solutions to the interactive session issue. By disabling 
+Fortunately there are solutions to the interactive session issue. By disabling
 the SIGSEGV handler, we can get past the initial failure and also we can catch
 the stack before it is altered by the JVM. ::
 
@@ -987,7 +995,7 @@ the stack before it is altered by the JV
 
 Thus far I have not found any good solutions to prevent the JVM from altering
 the stack frames when dumping the core. Thus interactive debugging appears
-to be the best option. 
+to be the best option.
 
 There are additional issues that one should be aware of. Open-JDK 1.8 has had a
 number of problems with the debugger. Starting JPype under gdb may trigger, may
@@ -1000,7 +1008,7 @@ Upgrading to Open-JDK 9 appears to fix t
 
 Another complexity with debugging memory problems is that Python tends to
 hide the problem with its allocation pools. Rather than allocating memory
-when a new object is request, it will often recycle and existing object 
+when a new object is request, it will often recycle and existing object
 which was collect earlier. The result is that an object which turns out is
 still live becomes recycled as a new object with a new type. Thus suddenly
 a method which was expected to produce some result instead vectors into
@@ -1016,6 +1024,87 @@ pools. Starting Python 3, it is possible
 through an enviroment variable.  See the ``PYTHONMALLOC`` setting for details.
 
 
+Deliberate Crash for Debugging
+------------------------------
+
+JPype includes deliberate crashes in its exception handling for scenarios where
+multiple failures occur, making it impossible to deliver errors to either Python
+or Java. These crashes are designed to aid debugging in catastrophic situations
+and offer significant advantages over simple program termination (`terminate`).
+
+When debugging JPype, deliberate crashes (segmentation faults) provide the
+following benefits:
+
+1. **Stack Trace Availability**:
+   - Deliberate crashes generate a meaningful stack trace for tools like `gdb`.
+   - Termination does not produce a stack trace, making it harder to identify
+     the root cause of the problem.
+
+2. **Bypassing Signal Handlers**:
+   - Deliberate crashes bypass Java's signal handlers, ensuring the stack trace
+     remains intact.
+   - Termination may still be affected by Java's signal handling, corrupting
+     the debugging process.
+
+3. **Memory State Preservation**:
+   - Deliberate crashes halt execution immediately, preventing Python's memory
+     recycling from altering the program state.
+   - Termination allows Python to continue recycling memory, which can obscure
+     the root cause of memory-related bugs.
+
+4. **Interactive Debugging**:
+   - Deliberate crashes enable interactive debugging with `gdb`, allowing
+     developers to inspect the program state before corruption occurs.
+   - Termination does not provide this opportunity.
+
+For these reasons, deliberate crashes are preferred in catastrophic scenarios
+where debugging is required.
+
+### Implementation and Use Case
+
+In rare and catastrophic situations where all exception handling mechanisms
+fail—such as during startup or when critical resources are unavailable—JPype
+uses a deliberate crash mechanism to produce a meaningful stack trace for
+debugging. This situation most often occurs when JVM resources are not found
+during initialization, resulting in errors that cannot be recovered. Reordering
+the resource loading sequence in `jp_context.cpp` is the most likely source of
+such failures.
+
+The deliberate crash is implemented as follows:
+
+.. code-block:: cpp
+
+   int *i = nullptr;
+   *i = 0;  // Trigger deliberate crash for gdb backtrace
+
+This crash bypasses Java's signal handlers and Python's memory management,
+which can obscure debugging efforts. By triggering a segmentation fault, `gdb`
+can capture the stack trace at the point of failure, providing valuable insight
+into the issue.
+
+### Debugging with `gdb`
+
+To debug using `gdb`, follow these steps:
+
+1. Start Python with `gdb` and disable the SIGSEGV handler:
+
+.. code-block:: bash
+
+      gdb -ex 'handle SIGSEGV nostop noprint pass' python
+
+2. Run the program until the deliberate crash occurs.
+
+3. Use the `bt` command in `gdb` to view the backtrace and identify the source
+   of the problem.
+
+### Important Note
+
+This mechanism is intended exclusively for debugging and should never be
+triggered during normal operation. If you encounter this crash, it indicates a
+critical failure that requires opening an issue on GitHub.
+
+
+
 Future directions
 -----------------
 
@@ -1044,14 +1133,14 @@ is no reason the jpype can't support it.
 these JVMs to be developed if there are differences in getting the JVM
 launched.
 
-There is a project page on github shows what is being developed for the 
+There is a project page on github shows what is being developed for the
 next release. Series 0.6 was usable, but early versions had notable issues
 with threading and internal memory management concepts had to be redone for
-stability.  Series 0.7 is the first verion after rewrite for 
-simplication and hardening.  I consider 0.7 to be at the level of production 
-quality code suitable for most usage though still missing some needed 
+stability.  Series 0.7 is the first verion after rewrite for
+simplication and hardening.  I consider 0.7 to be at the level of production
+quality code suitable for most usage though still missing some needed
 features. Series 0.8 will deal with higher levels of Python/Java integration such as Java
 class extension and pickle support.  Series 0.9 will be dedicated to any
 additional hardening and edge cases in the core code as we should have complete
 integration.  Assuming everything is completed, we will one day become a
-real boy and have a 1.0 release. 
+real boy and have a 1.0 release.
diff -pruN 1.5.0-1/doc/install.rst 1.6.0-1/doc/install.rst
--- 1.5.0-1/doc/install.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/doc/install.rst	2025-06-01 03:57:43.000000000 +0000
@@ -10,7 +10,7 @@ Binary Install
 
 JPype can be installed as pre-compiled binary if you are using the `Anaconda
 <https://anaconda.org>`_ Python stack. Binaries are available for Linux, OSX,
-and windows on conda-forge.
+and Windows on conda-forge.
 
 1. Ensure you have installed Anaconda/Miniconda. Instructions can be found
    `here <http://conda.pydata.org/docs/install/quick.html>`__.
@@ -83,27 +83,32 @@ from `PyPi <http://pypi.python.org/pypi/
 
 **2. Build the source with desired options**
 
-Compile JPype using the included ``setup.py`` script: ::
+Compile JPype using the `build <https://pypi.org/project/build/>` module (this will produce a wheel): ::
 
-  python setup.py build
+  python -m build /path/to/source
 
-The setup script recognizes several arguments.
+A number of additional argument may be provided.
 
 --enable-build-jar   Force setup to recreate the jar from scratch. 
 --enable-tracing     Build a verison of JPype with full logging to the 
                      console. This can be used to diagnose tricky JNI
                      issues.
 
+For example::
+
+    python -m build /path/to/source -C--global-option=build_ext -C--global-option="--enable-tracing"
+
 After building, JPype can be tested using the test bench. The test
 bench requires JDK to build.
 
-**3. Test JPype with (optional):** ::
+**3. Install the built wheel with:** ::
+
+    pip install /path/to/wheel
 
-    python setup.py test
+**4. Test JPype with (optional):** ::
 
-**4. Install JPype with:** ::
+    python -m pytest
 
-    python setup.py install
 
 
 If it fails...
@@ -116,7 +121,7 @@ happens, preform the following steps:
 1. Identify the location of your systems JDK installation and explicitly passing
    it to setup.py. ::
 
-     JAVA_HOME=/usr/lib/java/jdk1.8.0/ python setup.py install
+     JAVA_HOME=/usr/lib/java/jdk1.8.0/ python -m build .
 
 2. If that setup.py still fails please create an Issue `on
    github <https://github.com/jpype-project/jpype/issues?state=open>`__ and
@@ -128,7 +133,7 @@ happens, preform the following steps:
 Platform Specific requirements
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-JPype is known to work on Linx, OSX, and Windows.  To make it easier to those
+JPype is known to work on Linux, OSX, and Windows.  To make it easier to those
 who have not built CPython modules before here are some helpful tips for
 different machines.
 
@@ -138,7 +143,7 @@ Debian/Ubuntu
 Debian/Ubuntu users will have to install ``g++`` and ``python-dev``.
 Use:
 
-    sudo apt-get install g++ python-dev python3-dev
+    sudo apt-get install g++ python3-dev
 
 Windows
 :::::::
Binary files 1.5.0-1/doc/logo_small.png and 1.6.0-1/doc/logo_small.png differ
diff -pruN 1.5.0-1/doc/release.rst 1.6.0-1/doc/release.rst
--- 1.5.0-1/doc/release.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/doc/release.rst	2025-06-01 03:57:43.000000000 +0000
@@ -22,21 +22,24 @@ Full process:
       ``git checkout release``
 - [ ] Make a new branch for the release cycle
       ``git checkout -b releases/{version}``
-- [ ] Check the .azure scripts to see if they are up to date.
-      Look on https://devguide.python.org/versions/ to see what versions can be dropped
-      Check Python versions for Windows
-      Check Python versions for OSX
-      Check the manylinux image for Linux
-- [ ] Merge the current master with the release
-      ``git pull origin master``
-- [ ] Start a release
-      ``bumpversion release``
-- [ ] Edit doc/CHANGELOG.rst
-- [ ] Send the release to be evaluated
-      ``git push``
-- [ ] Verify CI on azure
-- [ ] Manually trigger a ``jpype.release`` on azure
-      If successful, download the artifacts for publication.
+- [ ] Update release process to current
+    - [ ] Check the .azure scripts to see if they are up to date.
+          Look on https://devguide.python.org/versions/ to see what versions can be dropped
+          Check Python versions for Windows
+          Check Python versions for OSX
+          Check the manylinux image for Linux
+    - [ ] Check patterns in .azure/scripts/build-wheels.sh
+    - [ ] Merge the current master with the release
+          ``git pull origin master``
+    - [ ] Edit doc/CHANGELOG.rst
+- [ ] Create a release candidate
+    - [ ] Bump the version to release
+        ``bumpversion release``
+    - [ ] Send the release to be evaluated
+        ``git push``
+    - [ ] Verify CI on Azure  ([Azure](https://dev.azure.com/jpype-project/jpype/_build?definitionId=1))
+    - [ ] Manually trigger a ``jpype.release`` on ([Azure](https://dev.azure.com/jpype-project/jpype/_build?definitionId=2))
+          If successful, download the artifacts for publication.
 - [ ] Advance the release pointer 
       ``git checkout release``
       ``git merge releases/<version>``
diff -pruN 1.5.0-1/doc/userguide.rst 1.6.0-1/doc/userguide.rst
--- 1.5.0-1/doc/userguide.rst	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/doc/userguide.rst	2025-06-01 03:57:43.000000000 +0000
@@ -5,15 +5,205 @@ JPype User Guide
 .. toctree::
    :maxdepth: 2
 
-JPype Introduction
-******************
-JPype is a Python module to provide full access to Java from within Python.
-Unlike Jython, JPype does not achive this by re-implementing Python, but
-instead by interfacing both virtual machines at the native level. This
-shared memory based approach achieves good computing performance, while
-providing the access to the entirety of CPython and Java libraries.
-This approach allows direct memory access between the two machines,
-implementation of Java interfaces in Python, and even use of Java threading.
+.. _introduction:
+
+Introduction
+************
+
+.. _introduction_jpype_the_python_to_java_bridge:
+
+JPype the Python to Java bridge
+===============================
+JPype is a Python module that provides seamless
+access to Java libraries from Python. Unlike Jython, which reimplements Python
+on the Java Virtual Machine (JVM), JPype bridges Python and Java at the native
+level using the Java Native Interface (JNI). This native approach implements CPython
+classes for each Java type and Java support for type management while communicating
+at the process level.  This approach enables:
+
+- Direct interaction between Python and Java objects.
+
+- Access to the full range of Java libraries and APIs.
+
+- No need to serialize objects when communicating between language.
+
+- Unified primitive types.
+
+- High speed transfers through shared memory between Python and Java 
+  for large primitive array types.
+
+JPype is intended for Python developers who need to leverage Java libraries or
+Java developers who want to use Python for scripting, debugging, or
+visualization.
+
+.. _introduction_why_use_jpype?:
+
+Why Use JPype?
+--------------
+JPype makes it easy to integrate Python and Java, enabling developers to:
+
+1. Access Java libraries directly from Python code.
+2. Debug Java data structures interactively using Python tools.
+3. Use Python's flexibility for scientific computing while leveraging Java's
+   robustness for enterprise applications.
+
+.. _introduction_prerequisites:
+
+Prerequisites
+-------------
+Before using JPype, ensure the following:
+
+1. **Python**: JPype requires Python 3.8 or later. Check your Python version by
+   running::
+
+       python --version
+
+2. **Java**: JPype requires a Java Runtime Environment (JRE) or Java Development
+   Kit (JDK) version 11 or later. Check your Java version by running::
+
+       java -version
+
+3. **Architecture Compatibility**: Ensure the Python interpreter and JVM have
+   matching architectures (e.g., both 64-bit or both 32-bit).
+
+.. _introduction_installation:
+
+Installation
+------------
+JPype can be installed using either `pip` or `conda`.
+
+.. _introduction_using_pip:
+
+Using pip
+~~~~~~~~~
+To install JPype via `pip`, run::
+
+    pip install JPype1
+
+
+.. _introduction_using_conda:
+
+Using conda
+~~~~~~~~~~~
+To install JPype via `conda`, use::
+
+    conda install -c conda-forge jpype1
+
+
+.. _introduction_verifying_installation:
+
+Verifying Installation
+~~~~~~~~~~~~~~~~~~~~~~
+After installation, verify that JPype is installed correctly by running::
+
+    import jpype
+    print("JPype installed successfully!")
+
+
+.. _introduction_your_first_jpype_program:
+
+Your First JPype Program
+------------------------
+Follow these steps to write and run your first JPype program:
+
+
+.. _introduction_step_1_start_the_jvm:
+
+Step 1: Start the JVM
+~~~~~~~~~~~~~~~~~~~~~
+JPype requires the JVM to be started before interacting with Java. All calls
+to the Java prior to the start of the JVM will fail. Use the
+`jpype.startJVM()` function to start the JVM::
+
+    import jpype
+
+    # Start the JVM
+    jpype.startJVM(classpath=[])
+
+.. _introduction_step_2_access_java_classes:
+
+Step 2: Access Java Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+JPype allows you to import and use Java classes directly. For example, import
+Java's `java.lang.String` class::
+
+    from java.lang import String
+
+.. _introduction_step_3_use_java_objects:
+
+Step 3: Use Java Objects
+~~~~~~~~~~~~~~~~~~~~~~~~
+Create and manipulate Java objects just like Python objects::
+
+    java_string = String("Hello from Java!")
+    print(java_string.toUpperCase())  # Output: HELLO FROM JAVA!
+
+.. _introduction_step_4_shut_down_the_jvm:
+
+Step 4: Shut Down the JVM
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Once the program is complete the JVM will exit when Python does.  All termination
+code is handled automatically.
+
+
+.. _introduction_complete_example:
+
+Complete Example
+~~~~~~~~~~~~~~~~
+Save the following code in a file named `hello_jpype.py`:
+
+.. code-block:: python
+
+    import jpype
+    import jpype.imports
+
+    # Start the JVM
+    jpype.startJVM(classpath=["../jar/*","../classes","com.amce-1.0.jar"])
+
+    # Import Java classes
+    from java.lang import String
+
+    # Use the Java String class
+    java_string = String("Hello from Java!")
+    print(java_string.toUpperCase())  # Output: HELLO FROM JAVA!
+
+
+Run the script using Python::
+
+    python hello_jpype.py
+
+You should see the output::
+
+    HELLO FROM JAVA!
+
+
+.. _introduction_next_steps:
+
+Next Steps
+----------
+Once you've successfully set up JPype, explore the following topics:
+
+1. **Accessing Java Libraries**: Learn how to use JPype to interact with third-
+   party Java libraries.
+2. **Working with Java Collections**: Discover how JPype integrates Java
+   collections with Python's `collections` module.
+3. **Implementing Java Interfaces in Python**: Use JPype's proxy functionality
+   to implement Java interfaces in Python.
+4. **Debugging Java Code**: Use JPype as an interactive shell for debugging
+   Java programs.
+
+
+.. _introduction_summary_of_jpype:
+
+Summary of JPype
+----------------
+JPype bridges Python and Java, enabling seamless integration between the two
+languages. With JPype, you can access Java libraries, implement Java
+interfaces, and debug Java code—all from the comfort of Python. Happy coding!
+
+
+
+.. _introduction_jpype_use_cases:
 
 JPype Use Cases
 ===============
@@ -21,12 +211,15 @@ JPype Use Cases
 Here are three typical reasons to use JPype.
 
 - Access to a Java library from a Python program (Python oriented)
-- Visualization of Java data structures (Java oriented)
+- Visualization of Java data structures via Matplotlib (Java oriented)
 - Interactive Java and Python development including scientific and mathematical
-  programming.
+  programming using Python as a Java shell with Spyder or Jupyter notebooks.
 
 Let's explore each of these options.
 
+
+.. _introduction_case_1_access_to_a_java_library:
+
 Case 1: Access to a Java library
 --------------------------------
 
@@ -102,6 +295,10 @@ Launch it in the interactive window.  Yo
 once you get a good night sleep.
 
 
+
+
+.. _introduction_case_2_visualization_of_java_structures:
+
 Case 2: Visualization of Java structures
 ----------------------------------------
 
@@ -111,7 +308,7 @@ project has suffered a nasty data struct
 managed to capture the data structure in a serialized form but if you could just
 make graph and call a few functions this would be so much easier.  But  the
 interactive Java shell that you are using doesn't really have much in the way of
-visualization and your don't have time to write a whole graphing applet just to
+visualization and you don't have time to write a whole graphing applet just to
 display this dataset.
 
 So poking around on the internet you find that Python has exactly the
@@ -166,7 +363,7 @@ you can use the casting operators.
    print(type(d))  # prints double[]
 
 Great. Now you just need to figure out how to convert from a Java array into
-our something our visualization code can deal with.  As nothing indicates that
+something our visualization code can deal with.  As nothing indicates that
 you need to convert the array, you just copy out of the visualization tool
 example and watch what happens.
 
@@ -181,6 +378,9 @@ Java arrays.  It looks like ever 4th ele
 It must be the PR the new guy put in.  And off you go back to the wonderful
 world of Java back to the safety of curly braces and semicolons.
 
+
+.. _introduction_case_3_interactive_java:
+
 Case 3: Interactive Java
 ------------------------
 
@@ -189,7 +389,7 @@ Laboratory.  (For the purpose of this ex
 Hawkins was shut down in 1984 and Java was created in 1995).  You have the test
 subject strapped in and you just need to start the experiment.  So you pull up
 Jupyter notebook your boss gave you and run through the cells.  You need to
-added some heart wave monitor to the list of graphed results.
+add some heart wave monitor to the list of graphed results.
 
 The relevant section of the API for the Experiment appears to be
 
@@ -268,229 +468,469 @@ in a window.  Job well done, so you set
 like you still have time to make the intern woodlands hike and forest picnic.
 Though you wonder if maybe next year you should sign up for another laboratory.
 Maybe next year, you will try to sign up for those orbital lasers the President
-was talking about in the March.  That sounds like real fun.
+was talking about back in March.  That sounds like real fun.
 
 (This advanced demonstration utilized the concept of Proxies_ and
 `Code completion`_)
 
 
+.. _introduction_the_jpype_philosophy:
+
 The JPype Philosophy
-====================
+=====================
+
+JPype is designed to provide seamless integration between Python and Java,
+allowing developers to use Java libraries and features as naturally as possible
+within Python. To achieve this, JPype adheres to several core design
+principles:
+
+1. **Make Java appear Pythonic**:
+
+   - JPype strives to make Java concepts feel familiar to Python programmers.
+     This involves adapting Java syntax and behaviors to align with Python's
+     conventions wherever possible.
+   - For example, Java methods are mapped to Python methods, and Java
+     collections are customized to behave like Python collections.
+
+2. **Make Python appear like Java**:
+
+   - JPype ensures that Java developers can work with Python without a steep
+     learning curve. This includes presenting Python constructs in a way that
+     resembles Java syntax and behavior.
+   - For instance, Python classes can implement Java interfaces, and Java
+     objects can be manipulated using Python's object-oriented features.
+
+3. **Expose all of Java to Python**:
+
+   - JPype aims to provide access to the entirety of the Java ecosystem,
+     including libraries, packages, and features. The goal is to act as a
+     bridge, enabling unrestricted interaction between the two languages.
+   - Whether it's Java threading, reflection, or advanced APIs, JPype ensures
+     that Python developers can leverage Java's full capabilities.
+
+4. **Keep the design simple**:
+
+   - Mixing two languages is inherently complex, so JPype minimizes additional
+     complexity by maintaining a simple and consistent design.
+   - For example, all Java array types originate from the `JArray` factory,
+     ensuring a unified approach to handling arrays.
+
+5. **Favor clarity over performance**:
+
+   - While JPype optimizes critical paths for performance, clarity is
+     prioritized to ensure long-term maintainability and usability.
+   - For example, JPype avoids premature optimization that could complicate the
+     codebase or introduce unnecessary constraints.
+
+6. **Introduce familiar methods**:
+
+   - When new methods are added, JPype ensures they align with established
+     conventions in both Python and Java.
+   - For example, Python's ``memoryview`` is used to access Java-backed memory,
+     while Java's ``Stream.of`` inspired the ``JArray.of`` method for converting
+     NumPy arrays to Java arrays.
+
+7. **Provide obvious solutions for both Python and Java programmers**:
+
+   - JPype recognizes that "obviousness" varies between Python and Java
+     developers. Therefore, it provides solutions that feel natural to both
+     audiences.
+
+   - For example, Python programmers can use list comprehensions with Java
+     collections, while Java programmers can use familiar methods like
+     ``contains`` or ``hashCode``.
+
+
+**Balancing Two Worlds**
+
+JPype bridges two distinct programming paradigms: Python's dynamic and flexible
+nature versus Java's strongly-typed and structured approach. This balance
+requires careful mapping of concepts between the two languages:
+
+- **Types**:
 
-JPype is designed to allow the user to exercise Java as fluidly as
-possible from within Python.  We can break this down into a few specific
-design goals.
-
-- Make Java appear Pythonic.  Make it so a Python programmer feels
-  comfortable making use of Java concepts.  This means making use of Python
-  concepts to create very Python looking code and at times bending Python
-  concepts to conform to Java's expectations.
-
-- Make Python appear like Java.  Present concepts from Java with a syntax
-  that resembles Java so that Java users can work with Python without a huge
-  learning curve.
-
-- Present everything that Java has to offer to Python.  Every
-  library, package, and Java feature if possible should be accessible.
-  The goal of bridge is to open up places and not to restrict flow.
-
-- Keep the design as simple as possible. Mixing languages is already complex
-  enough so don't required the user to learn a huge arsenal of unique methods.
-  Instead keep it simple with well defined rules and reuse
-  these concepts.  For example, all array types originate from JArray, and
-  thus using one can also use isinstance to check if a class is an array
-  type.  Rather than introducing factory that does a similar job to an
-  existing one, instead use a keyword argument on the current factory.
-
-- Favor clarity over performance.  This doesn't mean not trying to optimize
-  paths, but just as premature optimization is the bane of programmers,
-  requiring writing to maximize speed is a poor long term choice, especially
-  in a language such as Python where weak typing can promote bit rot.
-
-- If a new method has to be introduced, make it look familiar.
-  Java programmers look to a method named "of" to convert to a type on
-  factories such as a Stream, thus ``JArray.of`` converts a Python NumPy array
-  to Java.  Python programmers expect that memory backed objects can be converted
-  into bytes for rapid transfer using a memory view, thus
-  ``memoryview(array)`` will perform that task.
-
-- Provide an obvious way for both Python and Java programmers to perform tasks.
-  On this front JPype and Python disagree.  In Python's philosophy there should
-  be one -- and preferably only one -- obvious way to do things.  But we
-  are bridging two worlds and thus obviousness is in the eye of the beholder.
+  - Python's weak typing allows variables to change types dynamically, while
+    Java's strong typing enforces strict type declarations. JPype accommodates
+    this difference by providing type factories (``JClass``, ``JArray``) and casting
+    operators (``@``).
 
-The end result is that JPype has a small footprint while providing
-access to Java (and other JVM based languages) with a minimum of effort.
+- **Inheritance**:
 
-Languages other than Java
+  - Java supports single inheritance with interfaces, while Python allows
+    multiple inheritance. JPype maps Java interfaces to Python classes using
+    decorators (``@JImplements``) to ensure compatibility.
+
+- **Collections**:
+
+  - Java collections (``List``, ``Map``, ``Set``) are customized to behave like
+    Python collections, enabling intuitive interaction for Python developers.
+
+- **Error Handling**:
+
+  - Java exceptions are mapped to Python exceptions, allowing developers to
+    handle errors seamlessly across both languages.
+
+
+**Philosophy in Practice**
+
+JPype's design philosophy ensures a small footprint while offering high levels
+of integration between Python and Java. Developers can use JPype to:
+
+- Access Java libraries for tasks that Python lacks native support for (e.g.,
+  advanced threading, enterprise APIs).
+- Use Python's interactive and visualization capabilities to debug or analyze
+  Java data structures.
+- Combine Python's flexibility with Java's robustness for scientific computing,
+  machine learning, and enterprise applications.
+
+By adhering to these principles, JPype provides a powerful yet accessible tool
+for bridging the Python and Java ecosystems.
+
+
+.. _introduction_languages_other_than_java:
+
+Languages Other Than Java
 =========================
 
-JPype is primarily focused on providing the best possible wrapper for Java
-in Python.  However, the Java Virtual Machine (JVM) is used for many popular
-languages such a Kotlin and Scala.  As such JPype can be used for any language
-which used the JVM.
-
-That said each language has its own special properties that tend to be
-represented in different ways.  If you would like JPype fully to operate on your
-particular language the following is required.
-
-- Set up a test bench for your language under the test directory.  Use ivy
-  to pull in the required jar files required to run it and exercise each of
-  the required language features that need to be exercised.
-
-- Write a language specific quick start guide for your language defining
-  how things should appear in both your language of choice and within Python
-  highlighting those things that are different from how Java.
+Although JPype is primarily designed to bridge Python with Java, its
+capabilities extend to other JVM-based languages such as Kotlin, Scala, Groovy,
+and Clojure. These languages share the same underlying Java Virtual Machine
+(JVM) infrastructure, allowing JPype to interact with them seamlessly. However,
+each language introduces unique features and paradigms that may require
+additional considerations when integrating with Python.
 
-- Set up a test harness that exercises your language for each language feature
-  and place a setup script like ``test_java`` that builds the harness.
 
+.. _introduction_supported_jvmbased_languages:
 
-Alternatives
-============
+Supported JVM-Based Languages
+-----------------------------
 
-JPype is not the only Python module of its kind that acts as a bridge to
-Java.  Depending on your programming requirements, one of the alternatives
-may be a better fit.  Specifically JPype is designed for clarity and high
-levels of integration between the Python and Java virtual machine.  As such
-it makes use of JNI and thus inherits all of the benefits and limitations
-that JNI imposes.  With JPype, both virtual machines are running in the
-same process and are sharing the same memory space and threads.  JPype can
-thus intermingle Python and Java threads and exchange memory quickly.  But by
-extension you can't start and stop the JVM machine but instead must keep
-both machines throughout the lifespan of the program.  High integration means
-tightly coupled and thus it embodies the musketeers motto.  If Python crashes,
-so does Java as they only have one process to live in.
-
-A few alternatives with different philosophies and limitations are given in the
-following section.  Please take my review comments with the appropriate grain of
-salt.  When I was tasked with finding a replacement for Matlab Java integration
-for our project test bench, I evaluated a number of alternatives
-Python bridge codes.  I selected JPype primarily because it presented
-the most integrated API and documentation which would be suitable for getting
-physicists up to speed quickly.  Thus your criteria may yield a different
-selection.  JPype's underlying technology was underwhelming so I have had
-the pleasure of many hours reworking stuff under the hood.
+1. **Kotlin**:
 
-For more details on what you can't do with JPype, please see Limitations_.
+   - Kotlin is a modern JVM-based language that emphasizes conciseness and
+     safety. JPype can interact with Kotlin libraries and classes just as it
+     does with Java.
 
-`Jython <https://jython.org/>`_
--------------------------------
+   - Kotlin's null safety and extension functions are fully compatible with
+     JPype, though developers may need to handle Kotlin's nullable types
+     explicitly when working in Python.
+
+   - Example: Using Kotlin's ``List`` class in Python via JPype.
+
+.. code-block:: python
+
+      from kotlin.collections import List
+      my_list = List.of("apple", "orange", "banana")
+      print(my_list.size())  # Access Kotlin methods
+
+
+2. **Scala**:
+
+   - Scala combines object-oriented and functional programming paradigms,
+     making it a popular choice for big data and distributed systems.
+
+   - JPype can interact with Scala libraries, including those built on
+     frameworks like Akka or Spark.
+
+   - Scala's collections and functional constructs (e.g., `map`, `flatMap`) can
+     be accessed directly from Python, though some functional idioms may
+     require adaptation.
+
+.. code-block:: python
+
+      from scala.collection.mutable import ArrayBuffer
+      buffer = ArrayBuffer()
+      buffer.append(1)
+      buffer.append(2)
+      print(buffer.mkString(", "))  # Outputs: "1, 2"
+
+
+3. **Groovy**:
+
+   - Groovy is a dynamic language for the JVM, often used for scripting and
+     lightweight application development.
+
+   - JPype can interact with Groovy scripts and libraries, enabling Python
+     developers to leverage Groovy's concise syntax and dynamic capabilities.
+
+   - Groovy's dynamic typing aligns well with Python, but accessing from
+     within JPype may cause difficulties.
+
+.. code-block:: python
+
+      from groovy.util import Eval
+      result = Eval.me("3 + 5")
+      print(result)  # Outputs: 8
+
+
+4. **Clojure**:
+
+   - Clojure is a functional programming language that runs on the JVM. Its
+     emphasis on immutability and concurrency makes it ideal for certain types
+     of applications.
+
+   - JPype can interact with Clojure libraries, though developers may need to
+     adapt to Clojure's Lisp-like syntax and functional paradigms.
+
+.. code-block:: python
+
+      from clojure.lang import PersistentVector
+      vector = PersistentVector.create([1, 2, 3])
+      print(vector.nth(1))  # Access elements using Clojure methods
+
+
+.. _introduction_using_jpype_with_other_jvm_languages:
+
+Using JPype with Other JVM Languages
+------------------------------------
+
+JPype can be used with JVM-based languages, but the following considerations
+apply:
+
+1. **Language-Specific Features**:
+
+   - Each language introduces unique features (e.g., Kotlin's null safety,
+     Scala's functional constructs, Groovy's dynamic typing). These may require
+     adaptation when working with Python.
+
+2. **Interoperability**:
+
+   - JPype relies on the JVM's native interoperability mechanisms, ensuring
+     seamless interaction with JVM-based languages. However, developers should
+     be aware of differences in naming conventions, type systems, and runtime
+     behavior.
+
+3. **Testing and Integration**:
+
+   - To fully support a JVM-based language, developers should set up a test
+     bench to exercise its features, write language-specific quick-start
+     guides, and ensure compatibility with JPype's existing API.
+
+
+.. _introduction_expanding_jpype_for_other_jvm_languages:
+
+Expanding JPype for Other JVM Languages
+---------------------------------------
+
+If you wish to extend JPype's capabilities for a specific JVM-based language,
+the following steps are recommended:
+
+1. **Create a Test Bench**:
+
+   - Set up a test environment for your language under JPype's test directory.
+     Use Ivy or Maven to pull in the required JAR files and exercise the
+     language's unique features.
+
+2. **Write a Language-Specific Guide**:
+
+   - Document how your language interacts with JPype, highlighting differences
+     from Java and providing examples for common use cases.
+
+3. **Set Up a Test Harness**:
+
+   - Build a test harness to verify compatibility for each language feature.
+     Place the setup script (e.g., `test_kotlin`, `test_scala`) alongside
+     JPype's existing tests.
+
+.. _introduction_conclusion_on_languages:
+
+Conclusion on Languages
+-----------------------
 
-Jython is a reimplementation of Python in Java.  As a result it has much lower
-costs to share data structures between Java and Python and potentially much
-higher level of integration.  Noted downsides of Jython are that it has lagged
-well behind the state of the art in Python; it has a limited selection of
-modules that can be used; and the Python object thrashing is not particularly
-well fit in Java virtual machine leading to some known performance issues.
+JPype's ability to interact with JVM-based languages opens up exciting
+possibilities for Python developers. Whether you're working with Kotlin's
+modern syntax, Scala's functional paradigms, Groovy's dynamic scripting, or
+Clojure's immutability, JPype provides a powerful bridge to leverage the
+strengths of these languages within Python. By following the steps outlined
+above, you can ensure smooth integration and expand JPype's capabilities for
+your specific needs.
 
-`Py4J <https://py4j.org/>`_
+
+.. _introduction_alternatives:
+
+Alternatives
+============
+JPype is not the only Python module of its kind that acts as a bridge to Java.
+Depending on your programming requirements, one of the alternatives may be a
+better fit. Specifically, JPype is designed for clarity and high levels of
+integration between the Python and Java virtual machines. As such, it makes use
+of JNI and inherits all the benefits and limitations that JNI imposes. With
+JPype, both virtual machines run in the same process, sharing the same memory
+space and threads. JPype can intermingle Python and Java threads and exchange
+memory quickly. However, the JVM cannot be restarted within the same process,
+and if Python crashes, Java will also terminate since they share the same
+process.
+
+Below is a comparison of JPype with other Python-to-Java bridging technologies.
+These alternatives may suit different use cases depending on the level of
+integration, performance requirements, or ease of use.
+
+.. _introduction_py4j:
+
+Py4J
 ---------------------------
+`Py4J <https://py4j.org/>`_ is a Python library that enables communication
+with a JVM through a remote tunnel. Unlike JPype, which embeds the JVM directly
+into the Python process using JNI, Py4J operates the JVM as a separate process,
+allowing Python and Java to run independently. This separation introduces
+several unique advantages:
+
+1. **Cross-Architecture Compatibility**: Py4J allows Python and Java to run on
+different architectures or platforms. For example, you can run Python on a
+64-bit architecture while connecting to a 32-bit JVM, or even run Python and
+Java on entirely different machines. This flexibility is particularly useful
+for distributed systems or environments where the Python and Java components
+have different hardware or software requirements.
+
+2. **Restartable Java Sessions**: Because Py4J operates the JVM as a separate
+process, it is possible to stop and restart the JVM without restarting the
+Python process. This is a feature frequently requested by JPype users but is
+not feasible with JPype due to its use of JNI, which tightly couples the Python
+and Java memory spaces. Py4J's ability to restart the JVM makes it suitable for
+applications requiring dynamic lifecycle management of the Java environment.
+
+3. **Memory Isolation**: Since Python and Java run in separate processes, Py4J
+provides complete memory isolation between the two environments. This ensures
+that a crash in the JVM does not affect the Python process and vice versa. Such
+isolation can be critical for applications requiring high reliability and fault
+tolerance.
+
+4. **RPC-Style Communication**: Py4J operates more like a remote procedure call
+(RPC) framework, where Python sends commands to the JVM and receives responses.
+While this approach is less integrated than JPype's direct JNI-based
+interaction, it is intended for applications where tight coupling between
+Python and Java is not required.
+
+Despite these advantages, Py4J has some limitations compared to JPype:
+
+- **Performance**: The remote communication introduces a transfer penalty when
+  moving data between Python and Java, making Py4J less suitable for
+  applications requiring high-performance data exchange.
+
+- **Integration**: Py4J does not provide the seamless integration of Java
+  objects into Python syntax that JPype offers. For example, Java collections
+  and arrays do not behave like native Python objects.
+
+Py4J is a good choice for applications requiring cross-architecture
+compatibility, restartable JVM sessions, or memory isolation between Python and
+Java. However, for applications needing tight integration and high-performance
+data exchange, JPype may be a better fit.
 
-Py4J uses a remote tunnel to operate the JVM.  This has the advantage that
-the remote JVM does not share the same memory space and multiple JVMs can
-be controlled.  It provides a fairly general API, but the overall integration
-to Python is as one would expect when operating a remote channel operating
-more like an RPC front-end.  It seems well documented and capable.  Although
-I haven't done benchmarking, a remote access JVM will have a
-transfer penalty when moving data.
 
-`Jep <https://github.com/ninia/jep>`_
--------------------------------------
+.. _introduction_jep:
 
-Jep stands for Java embedded Python.  It is a mirror image of JPype.  Rather
-that focusing on accessing Java from within Python, this project is geared
-towards allowing Java to access Python as sub-interpreter.  The syntax for
-accessing Java resources from within the embedded Python is quite similar
-with support for imports.  Notable downsides are that although Python supports
-multiple interpreters many Python modules do not, thus some of the advantages
-of the use of Python many be hard to realize.  In addition, the documentation
-is a bit underwhelming thus it is difficult to see how capable it is from the
-limited examples.
-
-`PyJnius <https://github.com/kivy/pyjnius>`_
---------------------------------------------
-
-PyJnius is another Python to Java only bridge.  Syntax is somewhat similar to
-JPype in that classes can be loaded in and then have mostly Java native syntax.
-Like JPype, it provides an ability to customize Java classes so that they
-appear more like native classes.  PyJnius seems to be focused on Android.  It
-is written using Cython .pxi files for speed.  It does not include a method to
-represent primitive arrays, thus Python list must be converted whenever an
-array needs to be passed as an argument or a return.  This seems pretty
-prohibitive for scientific code.  PyJnius appears is still in active development.
-
-`Javabridge <https://github.com/CellProfiler/python-javabridge/>`_
-------------------------------------------------------------------
-
-Javabridge is direct low level JNI control from Python. The integration level
-is quite low on this, but it does serve the purpose of providing the JNI API
-to Python rather than attempting to wrap Java in a Python skin.  The downside
-being of course you would really have to know a lot of JNI to make effective
-use of it.
+Jep
+-------
+`Jep <https://github.com/ninia/jep>`_ stands for Java embedded Python. It is
+designed to allow Java to access Python as a sub-interpreter. The syntax for
+accessing Java resources from within the embedded Python is similar to JPype,
+with support for imports.  However, Jep has limitations due to Python's
+sub-interpreter model, which restricts the use of many Python modules.
+Additionally, Jep's documentation is sparse, making it difficult to assess its
+full capabilities without experimentation. Jep is best suited for applications
+where Java needs to embed Python for scripting purposes.
 
-`jpy <https://github.com/bcdev/jpy>`_
--------------------------------------
+.. _introduction_pyjnius:
 
-This is the most similar package to JPype in terms of project goals.  They have
-achieved more capabilities in terms of a Java from Python than JPype which does
-not support any reverse capabilities.  It is currently unclear if this project
-is still active as the most recent release is dated 2014.  The integration
-level with Python is fairly low currently though what they do provide is a
-similar API to JPype.
+PyJnius
+-------
+PyJnius <https://github.com/kivy/pyjnius>_ is another Python-to-Java bridge.
+Its syntax is somewhat similar to JPype, allowing classes to be loaded and
+accessed with Java-native syntax. PyJnius supports customization of Java
+classes to make them appear more Pythonic. However, PyJnius lacks support for
+primitive arrays, requiring Python lists to be converted manually whenever an
+array is passed as an argument or return value. This limitation makes PyJnius
+less suitable for scientific computing or applications requiring efficient
+array manipulation. PyJnius is actively developed and is particularly focused
+on Android development, making it a strong choice for mobile applications
+requiring Python-Java integration.
 
-`JCC <https://lucene.apache.org/pylucene/jcc/>`_
-------------------------------------------------
-JCC is a C++ code generator that produces a C++ object interface wrapping a Java
-library via Java's Native Interface (JNI). JCC also generates C++ wrappers that
-conform to Python's C type system making the instances of Java classes directly
-available to a Python interpreter.  This may be handy if your goal is not
-to make use of all of Java but rather have a specific library exposed to Python.
-
-`VOC <https://beeware.org/project/projects/bridges/voc/>_`
-----------------------------------------------------------
-A transpiler that converts Python bytecode into Java bytecode part of the
-BeeWare project.  This may be useful if getting a smallish piece of
-Python code hooked into Java.  It currently list itself as early development.
-This is more in the reverse direction as its goals are making Python code
-available in Java rather providing interaction between the two.
+.. _introduction_jython:
 
-`p2j <https://github.com/chrishumphreys/p2j>`_
-----------------------------------------------
+Jython
+------
+Jython <https://www.jython.org/>_ is a reimplementation of Python in Java. It
+allows Python code to run directly on the JVM, providing seamless access to
+Java libraries. Jython, while limited to Python 2, played a significant role in
+bridging Python and Java in earlier development eras. It may still be useful for
+legacy systems or environments where Python 2 compatibility is required. Its
+development has largely stalled, and it lacks support for popular Python
+libraries like NumPy and pandas, making it unsuitable for modern applications.
 
-This lists itself as "A (restricted) python to java source translator".
-Appears to try to convert Python code into Java.  Has not been actively
-maintained since 2013.  Like VOC this is primilarly for code translation rather
-that bridging.
+.. _introduction_javabridge:
 
+Javabridge
+-----------
+`Javabridge <https://github.com/CellProfiler/python-javabridge/>`_  provides
+direct low-level JNI control from Python. Its integration
+level is low, offering only the JNI API to Python rather than attempting to
+wrap Java in a Python-friendly interface. While Javabridge can be useful for
+advanced users familiar with JNI, it requires significant expertise to use
+effectively. Javabridge is best suited for applications needing fine-grained
+control over JNI interactions.
+
+.. _introduction_jcc:
+
+JCC
+---
+`JCC <https://lucene.apache.org/pylucene/jcc/>`_ is a C++ code generator that
+produces a C++ object interface wrapping a Java library via JNI. JCC also
+generates C++ wrappers conforming to Python's C type system, making instances
+of Java classes directly available to a Python interpreter. JCC is actively
+maintained as part of PyLucene and is useful for exposing specific Java
+libraries to Python rather than providing general Java access. It is best
+suited for applications requiring tight integration with libraries like Apache
+Lucene.  It is best suited for applications requiring tight integration with
+specific Java libraries.
+
+.. _introduction_about_this_guide:
 
-About this guide
+About this Guide
 ================
 
-The JPype User Guide is targeted toward programmers who are strong in either
-Python who wish to make use of Java or those who are strong with Java and are
-looking to use Python as a Java development tool.  As such we will compare and
-contrast the differences between the languages and provide examples suitable
-to help illustrate how to translate from one language to the other on the
-assumption that being strong in one language will allow you to easily grasp
-the corresponding relations in the other.  If you don't have a strong
-background in either language an appropriate language tutorial may be
-necessary.
-
-JPype will hide virtually all of the JNI layer such that there is no direct
-access to JNI concepts.  As such attempting to use JNI knowledge will likely
-lead to incorrect assumptions such as incorrectly attempting to use JNI
-naming and method signatures in the JPype API.  Where JNI limitations do
-appear we will discuss the consequences imposed in programming.  No knowledge
-of JNI is required to use this guide or JPype.
-
-JPype only works with Python 3, thus all examples will be using Python
-version 3 syntax and assume the use of the Python 3 new style object model.
-The naming conventions of JPype follow the Java rules rather than those of
-Python.  This is a deliberate choice as it would be dangerous to try to
-mangle Java method and field names into Python conventions and risk
-a name collision.  Thus if method must have Java conventions then the rest
-of the module should follow the same pattern for consistency.
+The JPype User Guide is designed for two primary audiences:
+
+1. **Python Programmers**: Those who are proficient in Python and wish to
+leverage Java libraries or integrate Java functionality into their Python
+projects.
+
+2. **Java Programmers**: Those who are experienced in Java and want
+to use Python as a development tool for Java, particularly for tasks like
+visualization, debugging, or scripting.
+
+
+This guide aims to bridge the gap between these two languages by comparing and
+contrasting their differences, providing examples that illustrate how to
+translate concepts from one language to the other. It assumes that readers are
+proficient in at least one of the two languages. If you lack a strong
+background in either Python or Java, you may need to consult tutorials or
+introductory materials for the respective language before proceeding.
+
+Key Features of the Guide
+-------------------------
+
+- **No JNI Knowledge Required**: JPype abstracts away the complexities of the
+  Java Native Interface (JNI). Users do not need to understand JNI concepts or
+  its naming conventions to use JPype effectively. In fact, relying on JNI
+  knowledge may lead to incorrect assumptions about the JPype API. Where JNI
+  imposes limitations, the guide explains the consequences in practical
+  programming terms.
+
+- **Python 3 Compatibility**: JPype supports only Python 3. All examples in
+  this guide use Python 3 syntax and assume familiarity with Python's new-style
+  object model. If you're using an older version of Python, you will need to
+  upgrade to Python 3 to use JPype.
+
+- **Java Naming Conventions**: JPype adheres to Java's naming conventions for
+  methods and fields to ensure consistency and avoid potential name collisions.
+  While this may differ from Python's conventions, it is a deliberate choice to
+  maintain compatibility with Java libraries and APIs.
+
+By following this guide, you’ll learn how to use JPype to seamlessly integrate
+Python and Java, unlocking the strengths of both languages in your projects.
+
+
+.. _introduction_getting_jpype_started:
 
 Getting JPype started
 ---------------------
@@ -513,7 +953,7 @@ the module is assumed to have been start
   from jpype import JImplements, JOverride, JImplementationFor
 
   # Start JVM with Java types on return
-  jpype.startJVM(convertStrings=False)
+  jpype.startJVM()
 
   # Import default Java packages
   import java.lang
@@ -526,7 +966,7 @@ start with a minimalistic approach.
 .. code-block:: python
 
   import jpype as jp                 # Import the module
-  jp.startJVM(convertStrings=False)  # Start the module
+  jp.startJVM()                      # Start the module
 
 Either style is usable and we do not wish to force any particular style on the
 user.  But as the extra ``jp.`` tends to just clutter up the space and implies
@@ -541,125 +981,250 @@ We will detail the starting process more
 `Starting the JVM`_.
 
 
+.. _introduction_jpype_concepts:
 
 JPype Concepts
-***************
+==============
 
 At its heart, JPype is about providing a bridge to use Java within Python.
-Depending on your perspective that can either be a means of accessing Java
-libraries from within Python or a way to use Java using Python syntax for
-interactivity and visualization.  This mean not only exposing a limited API but
-instead trying to provide the entirety of the Java language with Python.
-
-To do this, JPype maps each of the Java concepts to the nearest concept in
-Python wherever they are similar enough to operate without confusion.  We have
-tried to keep this as Pythonic as possible, though it is never without some
-rough edges.
-
-Python and Java share many of the same concepts.  Types, class, objects,
-function, methods, and members.  But in other places they are rather different.
-Python lacks casting, type declarations, overloading, and many other features of
-a strongly typed language, thus we must expose those concepts into the Python
-syntax as best we can.  Java for instance has class annotation and Python
-have class decorators.  Both serve the purpose of augmenting a class with
-further information, but are very different in execution.
-
-We have broken the mapping down in nine distinct concepts.  Some
-elements serve multiple functions.
-
-Type Factories
-  These are meta classes that allow one to declare a particular
-  Java type in Python.  The result of type factories are wrapper classes.
-  (JClass_ and JArray_)  Factories also exist to implement Java classes
-  from within Python (JProxy_)
-
-Meta Classes
-  These are classes to describe different properties of Java classes such as
-  to check if a class is an Interface. (JInterface_)
-
-Base Classes
-  These are JPype names for Java classes in Python that exist without importing
-  any specific Java class.  Concepts such as Object, String, and Exception are
-  defined and can be used in instance checks.  For example, to catch all Java
-  exceptions regardless of type, we would catch ``JException``.  These are mainly
-  for convenience though they do have some extra functionality.  Most of these
-  functions are being phased out in favor of Java syntax.  For example,
-  catching ``java.lang.Throwable`` will catch everything that ``JException``
-  will catch.  (Jarray_, JObject_, JString_, and JException_)
-
-Wrapper Classes
-  These correspond to each Java class.  Thus can be used to access static
-  variables, static methods, cast, and construct object.  They are used
-  wherever a Java type would be used in the Java syntax such as creating an
-  array or accessing the class instance.  These class wrappers are customized in
-  Python to allow a direct mapping from Java concepts to Python one.  These
-  are all created dynamically corresponding to each Java class.  For most
-  of this document we will refer to these simply as a "class".
-  (`java.lang.Object`_, `java.lang.String`_, etc) Many wrappers
-  are customized to match Python abstract base classes ABC
-  (`java.util.List`_, `java.util.Map`_)
-
-Object Instances
-  These are Java objects.  They operate just like Python objects with
-  Java public fields mapped to Python attributes and Java methods to
-  Python methods.  For this document we will refer to an object instance
-  simply as an "object".  The object instance is split into two halves.  The
-  Python portion is referred to as the "handle" that points the Java
-  "instance".  The lifetime of the "instance" is tied to the handle thus
-  Java objects do not disappear until the Python handle is disposed of.
-  Objects can be cast_ to match the required type and hold methods_ and
-  fields.
-
-`Primitive types`_
-  Each of the 8 Java primitive types are defined.  These are used to cast
-  to a Java type or to construct arrays.  (`JBoolean`_, `JChar`_, `JByte`_,
-  `JShort`_, `JInt`_, `JLong`_, `JFloat`_, and `JDouble`_)
-
-Decorators
-  Java has a number of keywords such as extending a class or implementing an
-  interface.  Those pieces of meta data can't directly be expressed with the
-  Python syntax, but instead have been been expressed as annotations that
-  can be placed on classes or functions to augment them with Java specific
-  information. (`@JImplements`_, `@JOverride`_, `@JImplementationFor`_)
-
-Mapping Java syntax to Python
-  Many Java concepts like try with resources can be mapped into Python
-  directly (as the ``with`` statement), or Java try, throw, catch mapping to
-  Python try, raise, except.  Others such as synchronize do not have an exact
-  Python match.  Those have instead been mapped to special functions
-  that interact with Python syntax..
-  (synchronized_, `with`, `try`, import_)
-
-JVM control functions
-  The JVM requires specific actions corresponding to JNI functions in order
-  to start, shutdown, and define threading behavior.  These top level control
-  functions are held in the ``jpype`` module. (startJVM_, shutdownJVM_)
+Depending on your perspective, this can either be a means of accessing Java
+libraries from within Python or a way to use Java with Python syntax for
+interactivity and visualization. JPype aims to provide access to the entirety
+of the Java language from Python, mapping Java concepts to their closest Python
+equivalents wherever possible.
+
+Python and Java share many common concepts, such as types, classes, objects,
+functions, methods, and members. However, there are significant differences
+between the two languages. For example, Python lacks features like casting,
+type declarations, and method overloading, which are central to Java's strongly
+typed paradigm. JPype introduces these concepts into Python syntax while
+striving to maintain Pythonic usability.
+
+This section breaks down JPype's core concepts into nine distinct categories.
+These categories define how Java elements are mapped into Python and how they
+can be used effectively.
 
-We will detail each of these concepts in greater detail in the later sections.
 
+.. _introduction_core_concepts:
 
-Name mangling
-=============
+Core Concepts
+-------------
 
-When providing Java package, classes, methods, and fields to Python,
-there are occasionally naming conflicts.  For example, if one has a method
-called ``with`` then it would conflict with the Python keyword ``with``.
-Wherever this occurs, JPype renames the offending symbol with a trailing
-under bar.  Java symbols with a leading or trailing under bars are consider to
-be privates and may not appear in the JPype wrapper entirely with the exception
-of package names.
+1. **Type Factories**:
 
-The following Python words will trigger name mangling of a Java name:
+   - Type factories allow you to declare specific Java types in Python. These
+     factories produce wrapper classes for Java types.
 
+   - Examples include `JClass` for Java classes and `JArray` for Java arrays.
 
-=========== =========== ============= =========== ==========
-``False``   ``None``    ``True``      ``and``     ``as``
-``async``   ``await``   ``def``       ``del``     ``elif``
-``except``  ``exec``    ``from``      ``global``  ``in``
-``is``      ``lambda``  ``nonlocal``  ``not``     ``or``
-``pass``    ``print``   ``raise``     ``with``    ``yield``
-=========== =========== ============= =========== ==========
+   - Factories also exist for implementing Java classes from within Python
+     using proxies (e.g., `JProxy`).
 
+2. **Meta Classes**:
+
+   - Meta classes describe properties of Java classes, such as whether a class
+     is an interface.
+
+   - Example: `JInterface` can be used to check if a Java class is an interface.
+
+3. **Base Classes**:
+
+   - JPype provides base classes for common Java types, such as `Object`,
+     `String`, and `Exception`.
+
+   - These classes can be used for convenience, such as catching all Java
+     exceptions with `JException`.
+
+   - Example: `java.lang.Throwable` can be caught using `JException`.
+
+4. **Wrapper Classes**:
+
+   - Wrapper classes correspond to individual Java classes and are dynamically
+     created by JPype. These wrappers encapsulate Java objects and provide a Pythonic
+     interface for interacting with them. Depending on the context, a wrapper may 
+     contain a Java reference, such as a class instance, primitive array, or boxed type
+     or a Java proxy which implements dynamically implements a Java interface.
+
+   - Wrappers are designed to make Java objects behave like native Python objects, 
+     enabling seamless integration between Python and Java. These wrappers provide
+     a Pythonic interface to Java objects, making them behave like native Python
+     objects while retaining their Java functionality.
+
+   - They allow access to static variables, static methods, constructors, and
+     casting.
+
+   - Example: `java.lang.Object`, `java.lang.String`.
+
+5. **Object Instances**:
+
+   - These are Java objects created or accessed within Python. They behave like
+     Python objects, with Java fields mapped to Python attributes and Java
+     methods mapped to Python methods.
+
+   - Example: A Java `String` object can be accessed and manipulated like a
+     Python string.
+
+6. **Primitive Types**:
+
+   - JPype maps Java's primitive types (e.g., `boolean`, `int`, `float`) into
+     Python classes.
+
+   - Example: `JInt`, `JFloat`, `JBoolean`.
+
+7. **Decorators**:
+
+   - JPype provides decorators to augment Python classes and methods with
+     Java-specific functionality.
+
+   - Examples include `@JImplements` for implementing Java interfaces and
+     `@JOverride` for overriding Java methods.
+
+8. **Mapping Java Syntax to Python**:
+
+   - JPype maps Java syntax to Python wherever possible. For example:
+
+     - Java's `try`, `throw`, and `catch` are mapped to Python's `try`, `raise`,
+       and `except`.
+
+     - Java's `synchronized` keyword is mapped to Python's `with` statement
+       using `jpype.synchronized`.
+
+9. **JVM Control Functions**:
+
+   - JPype provides functions for controlling the JVM, such as starting and
+     shutting it down.
+
+   - Examples: `jpype.startJVM()` and `jpype.shutdownJVM()`.
+
+
+.. _introduction_additional_details:
+
+Additional Details
+------------------
+
+- **Name Mangling**:
+
+  - JPype handles naming conflicts between Java and Python by appending an
+    underscore (`_`) to conflicting names.
+
+  - Example: A Java method named `with` will appear as `with_` in Python.
+
+  - For details see `Name Mangling`_.
+
+- **Lifetime Management**:
+
+  - Java objects remain alive as long as their corresponding Python handles
+    exist. Once the Python handle is disposed, the Java object is eligible for
+    garbage collection.
+
+By understanding these core concepts, you can effectively use JPype to
+integrate Python and Java, leveraging the strengths of both languages.
+
+
+.. _introduction_best_practices:
+
+Best Practices on JVM Startup
+-----------------------------
+
+Starting the Java Virtual Machine (JVM) correctly is critical for ensuring the
+smooth operation of JPype-based applications. A well-configured JVM startup
+process minimizes runtime issues, optimizes performance, and ensures
+compatibility with the required Java libraries. This section provides a
+detailed explanation of best practices to guide developers in setting up the
+JVM effectively.
+
+1. Start the JVM Early
+   The JVM should always be started early in the application lifecycle. By
+   initializing the JVM at the beginning of your program, you can avoid issues
+   related to delayed imports or incomplete initialization. This approach
+   ensures that all Java classes and libraries required by your application
+   are properly loaded and accessible throughout the program's execution.
+
+2. Configure the Classpath Explicitly
+   Classpath configuration is another essential consideration. The
+   ``classpath`` specifies the location of Java classes and JAR files that the
+   JVM needs to load. For optimal results, explicitly define the ``classpath``
+   when starting the JVM. This can be done using the ``classpath`` argument in
+   the ``startJVM()`` function or dynamically through the ``addClassPath()``
+   method prior to JVM startup. Explicit configuration prevents errors caused
+   by missing dependencies and ensures that the correct versions of libraries
+   are loaded.
+
+3. Disable Automatic String Conversion
+   When dealing with large-scale data transfers or computationally intensive
+   operations, it is advisable to disable automatic string conversion by
+   setting the ``convertStrings`` argument to ``False``. This prevents
+   unnecessary overhead caused by automatic conversion of Java strings to
+   Python strings, allowing developers to retain control over string handling
+   and improve performance. While enabling automatic string conversion may
+   seem convenient, it is considered a legacy option and should be avoided in
+   modern applications.
+
+4. Avoid Restarting the JVM
+   It is important to note that the JVM cannot be restarted once it has been
+   shut down. Therefore, design your application to start the JVM once and
+   keep it running for the program's lifetime. Attempting to restart the JVM
+   will result in errors due to lingering references and resource conflicts.
+   This limitation underscores the importance of careful planning when
+   initializing the JVM.
+
+.. _optimize_data_transfers:
+
+5. Optimize Data Transfers
+   When Python and Java need to exchange large amounts of data, such as arrays
+   or complex structures, the efficiency of these transfers can significantly
+   impact application performance. Without optimization, frequent back-and-
+   forth calls between Python and Java can create bottlenecks, especially in
+   computationally intensive applications like scientific computing or machine
+   learning.
+
+   To ensure smooth data exchange, consider the following strategies:
+
+   1. **Use NumPy Arrays**: NumPy arrays integrate seamlessly with JPype and
+      allow fast, memory-efficient data transfers to Java. For example, a
+      NumPy array can be mapped directly to a Java primitive array, enabling
+      high-speed operations without unnecessary copying.
+
+   2. **Leverage Java Buffers**: Java's `nio` buffers provide a mechanism for
+      shared memory between Python and Java. These buffers are particularly
+      useful for large datasets or memory-mapped files, as they eliminate the
+      overhead of repeated conversions and allow both languages to operate on
+      the same memory space.
+
+   3. **Cache Java Objects**: If a Java object is used repeatedly in Python,
+      consider caching it to reduce the frequency of cross-language calls.
+      This avoids redundant conversions and improves overall runtime
+      efficiency.
+
+   4. **Validate Data Structures**: Ensure that arrays or collections being
+      transferred are rectangular and compatible with the expected Java types.
+      For example, jagged arrays or incompatible data types can lead to errors
+      or performance degradation.
+
+   By implementing these strategies, you can optimize the interaction between
+   Python and Java, ensuring that your application performs efficiently even
+   when handling large-scale data or computationally intensive tasks.
+
+6. Handle Exceptions Properly
+   Exception handling is another key aspect of JVM startup. Always catch Java
+   exceptions using ``jpype.JException`` or specific Java exception classes to
+   ensure robust error handling. When debugging issues, the ``stacktrace()``
+   method can provide detailed information about Java exceptions, helping
+   developers identify and resolve problems effectively.
+
+7. Document Your Setup
+   Finally, document the JVM startup process and configuration settings
+   clearly within your codebase. This practice not only aids in debugging but
+   also ensures that other developers working on the project can understand
+   and replicate the setup. By adhering to these best practices, you can
+   maximize the reliability, performance, and maintainability of your
+   JPype-based applications.
+
+By adhering to these best practices, you can maximize the performance,
+reliability, and maintainability of your JPype-based applications.
+
+
+.. _jpype_types:
 
 JPype Types
 ***********
@@ -670,6 +1235,9 @@ currently holding and how that variable
 learn how Java and Python types relate to one another, how to create import
 types from Java, and how to use types to create Java objects.
 
+
+.. _jpype_types_stay_strong_in_a_weak_language:
+
 Stay strong in a weak language
 ==============================
 
@@ -682,7 +1250,7 @@ existing type to add new behaviors.  Pyt
 type of object as an argument, however if the interface is limited it will produce
 a TypeError to indicate a particular argument requires a specific type.  Python
 objects and classes are open.  Each class and object is basically a dictionary
-storing a set of key value pairs.  Types implemented in native C are often more
+storing a set of key-value pairs.  Types implemented in native C are often more
 closed and thus can't have their method dictionaries or data members altered
 arbitrarily.  But subject to a few restrictions based implementation, it is
 pretty much the wild west.
@@ -704,6 +1272,9 @@ Java norm and no standard mechanism exis
 Thus we need to introduce a few Java terms to the Python vocabulary.  These are
 "conversion" and "cast".
 
+
+.. _jpype_types_java_conversions:
+
 Java conversions
 ----------------
 
@@ -738,11 +1309,12 @@ in parentheses in front of the object to
 support Java casting syntax. To request an explicit conversion an object must
 be "cast" using a cast operator @.   Overloaded methods with an explicit
 argument will not be matched.  After applying an explicit cast, the match
-quality can improve to exact or derived depending on the cast type. 
+quality can improve to exact or derived depending on the cast type.
 
 Not every conversion is possible between Java types.  Types that cannot be
 converted are considerer to be conversion type "none".
 
+Details on how method overloads are resolved are given in `Method Resolution`_.
 Details on the standard conversions provided by JPype are given in the section
 `Type Matching`_.
 
@@ -751,7 +1323,7 @@ Details on the standard conversions prov
 Java casting
 ------------
 
-To access a casting operation we use the casting ``JObject`` wrapper.  
+To access a casting operation we use the casting ``JObject`` wrapper.
 For example, ``JObject(object, Type)`` would produce a copy with specificed type.
 The first argument is the object to convert and
 the second is the type to cast to.  The second argument should always be a Java
@@ -784,7 +1356,7 @@ changes the resolution type for the obje
 trying to call a specific method overload.   For example, if we have a Java
 ``a=String("hello")`` and there were an overload of the method ``foo`` between
 ``String`` and ``Object`` we would need to select the overload with
-``foo(java.lang.Object@a)``.  
+``foo(java.lang.Object@a)``.
 
 .. _JObject:
 
@@ -823,145 +1395,101 @@ Type enforcement appears in three differ
 whenever a Java method is called, whenever a Java field is set, and whenever
 Python returns a value back to Java.
 
-.. _methods:
 
-Method resolution
-=================
+Primitive Types
+===============
 
-Because Java supports method overloading and Python does not, JPype wraps Java
-methods as a "method dispatch".  The dispatch is a collection of all of the
-methods from class and all of its parents which share the same name.  The job
-of the dispatch is chose the method to call.
-
-Enforcement of the strong typing of Java must be performed at runtime within
-Python.  Each time a method is invoked, JPype must match against the list of
-all possible methods that the class implements and choose the best
-possible overload.  For this reason the methods that appear in a JPype class
-will not be the actual Java methods, but rather a "dispatch" whose job is
-deciding which method should be called based on the type of the provided
-arguments.
-
-If no method is found that matches the provided arguments, the method dispatch
-will produce a ``TypeError``.  This is the exact same outcome that Python uses
-when enforcing type safety within a function.  If a type doesn't match a
-``TypeError`` will be produced.
+Unlike Python, Java makes a distinction between objects and primitive data types.
+Primitives represent the minimum data that can be manipulated by a computer. These
+stand in contrast to objects, which have the ability to contain any combination of
+data types and objects within themselves, and can be inherited from.
 
-Dispatch example
-----------------
+Java primitives come in three categories:
 
-When JPype is unable to decide which overload of a method to call, the user
-must resolve the ambiguity.  This is where casting comes in.
+- **Logical**: `boolean` (true/false values).
+- **Textual**: `char` (single Unicode character).
+- **Numerical**: Fixed-point or floating-point numbers of varying sizes.
 
-Take for example the ``java.io.PrintStream`` class. This class has a variant of
-the print and println methods!
+JPype maps Java primitives to Python classes. To avoid naming conflicts with
+Python, JPype prefixes each primitive type with `J` (e.g., `JBoolean`, `JInt`).
 
-So for the following code:
+.. _jpype_types_primitive_types_jboolean:
+
+JBoolean
+--------
+
+Represents a logical value (`True` or `False`). In JPype, `True` and `False` are
+exact matches for `JBoolean`. Methods returning a `JBoolean` will always return a
+Python `bool`.
 
 .. code-block:: python
 
-  java.lang.System.out.println(1)
+   # Example usage
+   java_boolean = JBoolean(True)
+   print(java_boolean)  # Output: True
 
-JPype will automatically choose the ``println(long)`` method, because the Python
-int matches exactly with the Java long, while all the other numerical types
-are only "implicit" matches. However, if that is not the version you
-wanted to call you must cast it.  In this case we will use a primitive
-type to construct the correct type.
+.. _jpype_types_primitive_types_jchar:
+
+JChar
+-----
 
-Changing the line thus:
+Represents a single character. Java `char` types are 16-bit Unicode characters,
+but some Unicode characters require more than 16 bits. JPype maps `JChar` to
+Python strings of length 1. While `JChar` supports numerical operations, modifying
+characters numerically can corrupt their encoding.
 
 .. code-block:: python
 
-  java.lang.System.out.println(JByte(1)) # <--- wrap the 1 in a JByte
+   # Example usage
+   java_char = JChar('A')
+   print(java_char)  # Output: 'A'
 
-This tells JPype to choose the byte version. When dealing with Java types, JPype follows
-the standard Java matching rules.  Types can implicitly grow to larger types
-but will not shrink without an explicit cast.
+.. _jpype_types_primitive_types_jbyte,_jshort,_jint,_jlong:
 
+JByte, JShort, JInt, JLong
+--------------------------
 
-Primitive Types
-===============
+These types represent signed integers of varying sizes:
 
-Unlike Python, Java makes a distinction between objects and primitive data
-types.  Primitives represent the minimum data that can be manipulated by a
-computer.  These stand in contrast to objects which have the ability to contain any
-combination of data types and object within themselves, and can be inherited
-from.
-
-Java primitives come in three flavors.  The logical primitive ``boolean`` can
-only take the logical value true and false.  The textual primitive ``char``
-represents one character in a string.  Numerical primitives are intended for
-fixed point or floating point calculations.  Numerical primitives come in many
-sizes depending on how much storage is required.  In Java, integer numerical
-primitives are always signed and thus can only reach half their range in terms
-of bits up or down relative to their storage size.
-
-JPype has mapped each of the primitive types into Python classes.  To avoid
-conflicts with Python, JPype has named each primitive with a capital letter
-``J`` followed by the primitive name starting with an upper case letter.
+- **JByte**: 8 bits
+- **JShort**: 16 bits
+- **JInt**: 32 bits
+- **JLong**: 64 bits
 
-.. _JBoolean:
+JPype maps these types to Python's `int`. Methods returning integer primitives will
+return Python `int` values. Methods accepting integer primitives will accept Python
+integers or any object that can be converted into the appropriate range.
 
-JBoolean
-  A boolean is the logical primitive as it can only take values ``True`` and
-  ``False``.  It should properly be an extension of the Python concept ``bool``
-  but that type is not extendable.  Thus instead it must inherit from ``int``.
-  This type is rarely seen in JPype as the values ``True`` and ``False``
-  are considered an exact match to ``JBoolean`` argument.  Methods which
-  return a ``JBoolean`` will always return a Python ``bool`` rather than
-  a Java primitive type.
+.. code-block:: python
 
-.. _JChar:
+   # Example usage
+   java_int = JInt(42)
+   print(java_int)  # Output: 42
 
-JChar
-  A character is the textual primitive that corresponds to exactly one character
-  in a string.  Or at least that was the concept at the time.  Java characters
-  can only represent 16 bits.  But there are currently 143,924
-  defined characters in Unicode.  Thus, there are certain characters that
-  can only be represented as two Unicode characters.  The textual primitives
-  are not intended to perform numerical functions, but are instead encoded.
-  As per the old joke, what does `1` plus `1` equal?  Which of course the
-  correct answer is `b`.  As such characters should not be treated as just
-  another unsigned short.  Python has no concept of a textual only type.
-  Thus when returning a character type, we instead return a string length 1.
-  ``JChar`` supports the Java numerical operations, but just as in Java it will
-  automatically promote to a Python ``int`` when used in a numerical operation.
-  There are of course lots of useful mathematical operations that can be
-  performed on textual primitives, but doing so risks breaking the encoding
-  and can result in uninterpretable data.
-
-.. _JByte:
-.. _JShort:
-.. _JInt:
-.. _JLong:
-
-JByte, Short, Int, Long
-  These types represent fixed point quantities with ranges of 8, 16, 32, and
-  64 bits.  Each of these type inherit from a Python ``int`` type.  A method
-  or field returning an integer primitive will return a type derived from
-  ``int``.  Methods accepting an integer primitive will take either an
-  Java integer primitive or a Python ``int`` or anything that quacks like a
-  ``int`` so long as it can be converted into that primitive range without
-  truncation.
 
-.. _JFloat:
-.. _JDouble:
+.. _jpype_types_jfloat_jdouble:
 
 JFloat, JDouble
-  These two types hold floating point and correspond to either single point
-  (32 bit) or double point (64 bit) precision.  Python does not have a concept
-  of precision and thus both of these derive from the Python type ``float``.
-  As per Java rules numbers greater than the range correspond to the values
-  of positive and negative infinity.  Conversions from Python types are
-  ranged check and will produce a ``OverflowError`` if the value doesn't
-  fit into the request types.  If an overflow error is not desired, first
-  cast the value into the request size prior to calling.  Methods that return
-  a Java floating point primitive will always return a value derived from
-  ``float``.
-
-The classes for Java primitives are closed and should not be extended.
-As with all Java values any information attached to the Python representation
-is lost when passing that value to Java.
+---------------
+
+These types represent floating-point numbers:
+
+- **JFloat**: 32-bit precision
+- **JDouble**: 64-bit precision
+
+JPype maps these types to Python's `float`. Numbers exceeding the range of `JFloat`
+or `JDouble` will result in positive or negative infinity. Range checks are
+performed when converting Python types, and an `OverflowError` will be raised if
+the value is out of bounds.
+
+.. code-block:: python
 
+   # Example usage
+   java_double = JDouble(3.14)
+   print(java_double)  # Output: 3.14
+
+
+.. _jpype_types_objects__classes:
 
 Objects & Classes
 =================
@@ -1005,6 +1533,8 @@ represent this in Python every interface
 even through it does not have ``java.lang.Object`` as a parent.  This ensures
 that anonymous classes and lambdas have full object behavior.
 
+.. _jpype_types_classes:
+
 Classes
 -------
 
@@ -1091,130 +1621,176 @@ the object.
 Now that we have defined the basics of Java objects and classes, we will
 define a few special classes that operate a bit differently.
 
+.. _jpype_types_array_classes:
+
 Array Classes
 -------------
 
-In Java all arrays are also objects, but they cannot define any methods beyond
-a limited set of Java array operations.  These operations have been mapped into
+In Java, all arrays are objects, but they cannot define any methods beyond a
+limited set of Java array operations. These operations have been mapped into
 Python to their closest Python equivalent.
 
-Arrays also have a special type factory to produce them.  In principle
-one can create an array class using ``JClass`` but the signature required
-would need to use the proper name as required for the Java method
-``java.lang.Class.forName``.  Instead we call the factory to create a new
-type to use.
+`JArray` is an abstract base class for all Java array classes. Thus, you can
+test if something is an array class using ``issubclass``, and check if a Java
+object is an array using ``isinstance``.
+
+Creating Array Types
+~~~~~~~~~~~~~~~~~~~~
+
+In principle, you can create an array class using ``JClass``, but the signature
+required would need to use the proper name as required for the Java method
+``java.lang.Class.forName``. Instead, JPype provides two specialized methods to
+create array types: arrays may be produced through the factory ``JArray`` or
+through the index operator ``[]`` on any `JClass` instance.
 
 .. _JArray:
 
-The signature for JArray is ``JArray(type, [dims=1])``.  The type argument
-accepts any Java type including primitives and constructs a new array class.
-This class can be used to create new instances, cast, or as the input to
-the array factory.  The resulting object has a constructor method
-which take either a number, which is the desired size of the array, or a
-sequence which hold the elements of the array.  If the members of the
-initializer sequence are not Java members then each will be converted.  If
-any element cannot be converted a ``TypeError`` will be raised.
-
-As a shortcut the ``[]`` operator can be used to specify an array type or
-an array instance.   For example, ``JInt[5]`` will allocate an array instance
-of Java ints with length 5.  ``JInt[:]`` will create a type instance with 
-an unspecific length which can be used for the casting operator.  To create
-an array instance with multiple dimensions we would use ``JInt[5,10]`` 
-which would create a rectangular array which was 5 by 10.   To create a
-jagged array we would substitute ``:`` for the final dimensions.  So
-``JInt[5,:]`` is a length 5 array of an array of ``int[]``.  Multidimensional
-array types are specificed like ``JInt[:,:,:]`` would be a Java type
-``int[][][]``.  This applied to both primitive and object types.
-
-JArray is an abstract base class for all Java classes that are produced.
-Thus, one can test if something is an array class using ``issubclass``
-and if Java object is an array using ``isinstance``.
-
-Java arrays provide a few additional Python methods:
-
-Get Item
-  Arrays are of course a collection of elements.  As such array elements can
-  be accessed using the Python ``[]`` operator.  For multidimensional arrays
-  JPype uses Java style access with a series of index operations such
-  as ``jarray[4][2]`` rather than NumPy like multidimensional access.
-
-Get Slice
-  Arrays can be accessed using a slice like a Python list.
-  The slice operator is  ``[start:stop:step]``.  It should be noted that array
-  slice are in fact views to the original array so any alteration to
-  the slice will affect the original array.  Array slices are cloned when
-  passed back to Java.  To force a clone immediately, use the ``clone`` method.
-  Please note that applying the slice operator to a slice produces a new
-  slice.  Thus there can sometimes be an ambiguity between multidimensional
-  access and repeated slicing.
+The signature for `JArray` is ``JArray(type, [dims=1])``. The `type` argument
+accepts any Java type, including primitives, and constructs a new array class.
+This class can be used to create new instances, cast, or serve as the input to
+the array factory. The resulting object has a constructor method that takes
+either:
+
+- A number, which specifies the desired size of the array.
+- A sequence, which provides the elements of the array. If the members of the
+  initializer sequence are not Java objects, each will be converted. If any
+  element cannot be converted, a ``TypeError`` will be raised.
+
+As a shortcut, the ``[]`` operator can be used to specify an array type or
+create a new instance of an array with a specified length. You can also create
+multidimensional arrays or arrays with unspecified dimensions after a specific
+point. This applies to both primitive and object types. Because of the number
+of options, we will walk through each use case.
+
+To create a one-dimensional array type, append ``[:]`` to any Java class or
+primitive type. For example:
+
+- ``JInt[:]`` creates a Java array type for integers.
+- ``java.lang.Object[:]`` creates a Java array type for objects.
+- ``java.util.List[:]`` creates a Java array type for lists.
+
+Once the array type is created, you can use it to construct arrays, cast Python
+sequences to Java arrays, or define multidimensional arrays.
+
+.. code-block:: python
+
+   # Example: Creating array types
+   int_array_type = JInt[:]
+   object_array_type = java.lang.Object[:]
+
+   # Creating arrays
+   int_array = int_array_type([1, 2, 3])
+   object_array = object_array_type([None, "Hello", 42])
+
+   print(int_array)  # Output: [1, 2, 3]
+   print(object_array)  # Output: [null, Hello, 42]
+
+Multidimensional Arrays
+~~~~~~~~~~~~~~~~~~~~~~~
+
+JPype supports the creation of multi-dimensional arrays by appending additional
+dimensions using ``[:]``. For example:
+
+- ``JInt[:,:]`` creates a two-dimensional array type for integers.
+- ``java.lang.Object[:,:]`` creates a two-dimensional array type for objects.
+- ``JDouble[:,:,:]`` creates a three-dimensional array type for double-precision
+  floating-point numbers.
+
+When creating multi-dimensional arrays, you can initialize them using nested
+Python lists. JPype automatically converts nested lists into the appropriate
+Java array structure.
+
+.. code-block:: python
+
+   # Example: Creating multidimensional arrays
+   int_2d_array_type = JInt[:, :]
+   int_2d_array = int_2d_array_type([[1, 2], [3, 4]])
+
+   print(int_2d_array[0][1])  # Output: 2
 
-Set Item
+   # Creating a 3D array
+   double_3d_array_type = JDouble[:, :, :]
+   double_3d_array = double_3d_array_type([[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, 8.8]]])
+
+   print(double_3d_array[1][0][1])  # Output: 6.6
+
+Jagged Arrays
+~~~~~~~~~~~~~
+
+Java supports jagged arrays, which are arrays of arrays with varying lengths.
+To create jagged arrays in JPype, replace the final dimension with `[:]`. For
+example:
+
+- `JInt[5, :]` creates a jagged array of integers with 5 rows.
+- `java.lang.Object[3, :]` creates a jagged array of objects with 3 rows.
+
+Jagged arrays can be initialized using nested Python lists with varying lengths.
+
+.. code-block:: python
+
+   # Example: Creating jagged arrays
+   jagged_int_array_type = JInt[3, :]
+   jagged_int_array = jagged_int_array_type([[1, 2], [3, 4, 5], [6]])
+
+   print(jagged_int_array[1][2])  # Output: 5
+
+Use of Java Arrays
+~~~~~~~~~~~~~~~~~~
+
+Java arrays provide several Python methods:
+
+- **Get Item**:
+  Arrays are collections of elements. Array elements can be accessed using the
+  Python ``[]`` operator. For multidimensional arrays, JPype uses Java-style
+  access with a series of index operations, such as ``jarray[4][2]``.
+
+- **Get Slice**:
+  Arrays can be accessed using slices, like Python lists. The slice operator is
+  ``[start:stop:step]``. Note that array slices are views of the original array,
+  so any alteration to the slice will affect the original array. Use the `clone`
+  method to create a copy of the slice if needed.
+
+- **Set Item**:
   Array items can be set using the Python ``[]=`` operator.
 
-Set Slice
-  Multiple array items can be set using a slice assigned with a sequence.
-  The sequence must have the same length as the slice.  If this condition is not
-  met, an exception
-  will be raised.  If the items to be transferred are a buffer,
-  then a faster buffer transfer assignment will be used.  When buffer transfers
-  are used individual elements are not checked for range, but instead cast
-  just like NumPy.  Thus, if we have the elements we wish to assign to the
-  array contained within a NumPy array named ``na`` we can transfer all of them using
-  ``jarray[:] = na``.
+- **Set Slice**:
+  Multiple array items can be set using a slice assigned with a sequence. The
+  sequence must have the same length as the slice. If the items being transferred
+  are a buffer, a faster buffer transfer assignment will be used.
 
-Buffer transfer
-  Buffer transfers from a Java array also work for primitive types.  Thus we
-  can simply call the Python ``memoryview(jarray)`` function to create a buffer
-  that can be used to transfer any portion of a Java array out.  Memory views
-  of Java arrays are not writable.
+- **Buffer Transfer**:
+  Buffer transfers from Java arrays work for primitive types. Use Python's
+  ``memoryview(jarray)`` function to create a buffer for transferring data.
+  Memory views of Java arrays are not writable.
 
-For each
-  Java arrays can be used as the input to a Python for statement.  To iterate
-  each element use ``for elem in jarray:``.  They can also be used in
-  list comprehensions.
-
-Clone
-  Java arrays can be duplicated using the method clone.  To create a copy
-  call ``jarray.clone()``.  This operates both on arrays and slice views.
+- **Iteration (For Each)**:
+  Java arrays can be used in Python `for` loops and lopp comprehensions.
 
-Length
-  Arrays in Java have a defined an immutable length.  As such the
-  Python ``len(array)`` function will produce the array length.  However,
-  as that does not match Java expectations, JPype also adds an attribute
-  for length so that Java idiom  ``jarray.length`` also works as expected.
-
-In addition, the Java class ``JChar[]`` has some addition customizations to help
-work better with string types.
-
-Java arrays are currently missing some of the requirements to act as a
-``collections.abc.Sequence``.  When working with Java arrays it is also useful
-to use the Java array utilities class ``java.util.Arrays`` as it has many
-methods that provide additional functionality.  Java arrays do not support any
-additional mathematical operations at this time.
-
-Creating a Java array is also required when pass by reference syntax is required.
-For example, if a Java function takes an array, modifies it and we want to
-retrieve those values.  In Java, all parameters are pass by value, but the contents
-of a container like an array can be modified which gives the appearance of 
-pass by reference.  For example.
+- **Clone**:
+  Java arrays can be duplicated using the `clone()` method.
 
-.. code-block:: java
+- **Length**:
+  Arrays in Java have a defined, immutable length. Use Python's ``len(array)``
+  function to get the array length.
+
+Character specialization
+~~~~~~~~~~~~~~~~~~~~~~~~
 
-     public void modifies(int[] v) {
-         for (int i=0; i<v.length; ++i)
-              v[i]*=2;
-     }
+- The Java class `JChar[]` has additional customizations to work better with
+  string types.
+- Java arrays do not support additional mathematical operations at this time.
+- Creating a Java array is required for pass-by-reference syntax when using Java
+  methods that modify array contents.
 
 .. code-block:: python
 
-     orig = [1,2,3]
-     obj = jpype.JInt[:](orig)
-     a.modifies(obj)   #  modifies the array by multiply all by 2
-     orig[:] = obj     #  copy all the values back from Java to Python
+   orig = [1, 2, 3]
+   obj = jpype.JInt[:](orig)
+   a.modifies(obj)   # Modifies the array by multiplying all elements by 2
+   orig[:] = obj     # Copies all the values back from Java to Python
 
-If we were to call modifies on the original Python list directly, the temporary copy
-would have been modified so the results would have been lost.
 
+.. _jpype_types_buffer_classes:
 
 Buffer classes
 --------------
@@ -1242,6 +1818,8 @@ Buffer transfer
 Buffers do not currently support element-wise access.
 
 
+.. _jpype_types_boxed_classes:
+
 Boxed Classes
 -------------
 
@@ -1310,6 +1888,8 @@ Comparison
   comparison.
 
 
+.. _jpype_types_number_class:
+
 Number Class
 ------------
 
@@ -1447,6 +2027,8 @@ Java strings will cache the Python conve
 cost once per string.
 
 
+.. _jpype_types_exception_classes:
+
 Exception Classes
 -----------------
 
@@ -1481,6 +2063,8 @@ on dealing with exception, see the `Exce
 Java exception use JClass or any of the other importing methods.
 
 
+.. _jpype_types_anonymous_classes:
+
 Anonymous Classes
 -----------------
 
@@ -1494,6 +2078,8 @@ is somewhat problematic when the parent
 object type.
 
 
+.. _jpype_types_lambdas:
+
 Lambdas
 -------
 
@@ -1503,6 +2089,8 @@ Single Abstract Method (SAM) type interf
 methods in the form of default methods but those are generally not accessible
 within JPype.
 
+.. _jpype_types_inner_classes:
+
 Inner Classes
 -------------
 
@@ -1517,6 +2105,28 @@ following differences:
 - Non-static inner classes cannot be instantiated from Python code.  Instances
   received from Java code can be used without problem.
 
+.. _jpype_types_buffer_transfers:
+
+Buffer Transfers
+----------------
+Java arrays provide efficient buffer transfers for primitive types using Python's
+`memoryview`. This allows seamless integration with libraries like NumPy for
+numerical operations. For strategies to optimize data exchange, 
+see :ref:`Optimize Data Transfers <optimize_data_transfers>`.
+
+.. code-block:: python
+
+   # Example: Buffer transfer
+   import numpy as np
+
+   int_array = JInt[:](5)
+   int_array[:] = [1, 2, 3, 4, 5]  # Transfer data to Java array
+
+   buffer = memoryview(int_array)
+   np_array = np.array(buffer)     # Convert to NumPy array
+
+   print(np_array)  # Output: [1, 2, 3, 4, 5]
+
 
 .. _import:
 
@@ -1572,6 +2182,129 @@ not useful for ordinary users.  It was p
 due to `caller sensitive`_ issues.
 
 
+.. _name_mangling:
+
+Name mangling
+=============
+
+When providing Java package, classes, methods, and fields to Python,
+there are occasionally naming conflicts.  For example, if one has a method
+called ``with`` then it would conflict with the Python keyword ``with``.
+Wherever this occurs, JPype renames the offending symbol with a trailing
+under bar.  Java symbols with a leading or trailing under bars are consider to
+be privates and may not appear in the JPype wrapper entirely with the exception
+of package names.
+
+The following Python words will trigger name mangling of a Java name:
+
+=========== =========== ============= =========== ==========
+``False``   ``None``    ``True``      ``and``     ``as``
+``async``   ``await``   ``def``       ``del``     ``elif``
+``except``  ``exec``    ``from``      ``global``  ``in``
+``is``      ``lambda``  ``nonlocal``  ``not``     ``or``
+``pass``    ``print``   ``raise``     ``with``    ``yield``
+=========== =========== ============= =========== ==========
+
+
+.. _methods:
+.. _jpype_types_method_resolution:
+
+
+Method Resolution
+=================
+
+Because Java supports method overloading and Python does not, JPype wraps Java
+methods as a "method dispatch". The dispatch is a collection of all of the
+methods from the class and all of its parents which share the same name. The
+job of the dispatch is to choose the method to call. Enforcement of the strong
+typing of Java must be performed at runtime within Python. Each time a method
+is invoked, JPype must match against the list of all possible methods that the
+class implements and choose the best possible overload. For this reason, the
+methods that appear in a JPype class will not be the actual Java methods, but
+rather a "dispatch" whose job is deciding which method should be called based
+on the type of the provided arguments. If no method is found that matches the
+provided arguments, the method dispatch will produce a ``TypeError``. This is
+the exact same outcome that Python uses when enforcing type safety within a
+function. If a type doesn't match, a ``TypeError`` will be produced.
+
+Dispatch Example
+----------------
+
+When JPype is unable to decide which overload of a method to call, the user
+must resolve the ambiguity. This is where casting comes in. Take for example
+the ``java.io.PrintStream`` class. This class has a variant of the print and
+println methods! So for the following code:
+
+.. code-block:: python
+
+   java.lang.System.out.println(1)
+
+JPype will automatically choose the ``println(long)`` method, because the
+Python ``int`` matches exactly with the Java ``long``, while all the other
+numerical types are only "implicit" matches. However, if that is not the
+version you wanted to call, you must cast it. In this case, we will use a
+primitive type to construct the correct type. Changing the line thus:
+
+.. code-block:: python
+
+   java.lang.System.out.println(JByte(1))  # <--- wrap the 1 in a JByte
+
+This tells JPype to choose the byte version. When dealing with Java types,
+JPype follows the standard Java matching rules. Types can implicitly grow to
+larger types but will not shrink without an explicit cast.
+
+Caching Optimization for Method Resolution
+------------------------------------------
+
+JPype optimizes method resolution by caching the results of previous matches.
+If the same method is called repeatedly with the same argument types (e.g.,
+inside a loop or list comprehension), JPype reuses the cached resolution,
+avoiding the overhead of re-evaluating all overloads. This greatly improves
+performance for repetitive calls.
+
+For example, consider the following code:
+
+.. code-block:: python
+
+   fruits = ["apple", "orange", "banana"]
+   jlist = java.util.ArrayList()
+   [jlist.add(fruit) for fruit in fruits]  # Cached resolution for each iteration
+
+In this case, JPype caches the resolution for ``add(str)`` to ``add(String)``
+method after the first call, and subsequent calls reuse the cached result. This
+optimization is particularly beneficial in loops and list comprehensions.  A
+call to ``add(int)`` would trigger a new resolution.  The next call to ``add(str)``
+will once again trigger a resolution request.
+
+**Note**: For an in-depth discussion on how this caching mechanism improves
+loop performance, particularly in list comprehensions, see the
+:ref:`Performance <miscellaneous_topics_performance>` section.
+
+Interactions of Custom Converters and Caching
+---------------------------------------------
+
+It is unwise to define very broad conversions as it can interact poorly with 
+caching. Suppose that one defined a convertion from all Python strings to the
+Java class for date under some condition. Or perhaps an even broader convserion
+was defined such as all Python classes that inherit from object.
+
+If such overly broad conversions are applied to a function
+for which both date and string were acceptable it were prefer the date
+conversion when method resolution starts.  As the type for the cache was string
+it would attempt the out of order resolution of date first.  If the 
+condition yield a fail it will fall back to normal method resolution, but
+an overly broad conversion specialization may end up being dispatched to the 
+previously defined conversion.
+
+Under normal operation of JPype the type conversions are narrowly defined such
+that the cache will always yield the proper resolution.  But user defined
+conversions may cause unexpected results.  In such a case, a cast operation to
+the Java type would be required to resolve the ambiguity.
+
+
+
+.. _jpype_types_type_matching:
+
 Type Matching
 =============
 
@@ -1653,70 +2386,137 @@ dictionary
 Exception Handling
 ==================
 
-Error handling is an important part of any non-trivial program.  All Java
-exceptions occurring within Java code raise a ``jpype.JException`` which
-derives from Python Exception. These can be caught either using a specific Java
-exception or generically as a ``jpype.JException`` or ``java.lang.Throwable``.
-You can then use the ``stacktrace()``, ``str()``, and args to access extended
+Error handling is an important part of any non-trivial program. All Java
+exceptions occurring within Java code raise a `jpype.JException`, which derives
+from Python's `Exception`. These can be caught either using a specific Java
+exception or generically as a `jpype.JException` or `java.lang.Throwable`. You
+can then use the `stacktrace()`, `str()`, and `args` to access extended
 information.
 
-Here is an example:
+.. _jpype_types_catching_a_specific_java_exception:
+
+Catching a Specific Java Exception
+----------------------------------
+
+The following example demonstrates catching a specific Java exception:
 
 .. code-block:: python
 
-  try :
-      # Code that throws a java.lang.RuntimeException
-  except java.lang.RuntimeException as ex:
-      print("Caught the runtime exception : ", str(ex))
-      print(ex.stacktrace())
+    try:
+        # Code that throws a java.lang.RuntimeException
+    except java.lang.RuntimeException as ex:
+        print("Caught the runtime exception:", str(ex))
+        print(ex.stacktrace())
 
-Multiple java exceptions can be caught together or separately:
+.. _jpype_types_catching_multiple_java_exceptions:
+
+Catching Multiple Java Exceptions
+---------------------------------
+
+Multiple Java exceptions can be caught together or separately:
 
 .. code-block:: python
 
-  try:
-      # ...
-  except (java.lang.ClassCastException, java.lang.NullPointerException) as ex:
-      print("Caught multiple exceptions : ", str(ex))
-      print(ex.stacktrace())
-  except java.lang.RuntimeException as ex:
-      print("Caught runtime exception : ", str(ex))
-      print(ex.stacktrace())
-  except jpype.JException as ex:
-      print("Caught base exception : ", str(ex))
-      print(ex.stacktrace())
-  except Exception as ex:
-      print("Caught python exception :", str(ex))
+    try:
+        # Code that may throw various exceptions
+    except (java.lang.ClassCastException, java.lang.NullPointerException) as ex:
+        print("Caught multiple exceptions:", str(ex))
+        print(ex.stacktrace())
+    except java.lang.RuntimeException as ex:
+        print("Caught runtime exception:", str(ex))
+        print(ex.stacktrace())
+    except jpype.JException as ex:
+        print("Caught base exception:", str(ex))
+        print(ex.stacktrace())
+    except Exception as ex:
+        print("Caught Python exception:", str(ex))
 
-Exceptions can be raised in proxies to throw an exception back to Java.
+.. _jpype_types_raising_exceptions_from_python_to_java:
 
+Raising Exceptions from Python to Java
+--------------------------------------
+
+Exceptions can be raised in proxies to throw an exception back to Java.
 Exceptions within the JPype core are issued with the most appropriate Python
-exception type such as ``TypeError``, ``ValueError``, ``AttributeError``, or
-``OSError``.
+exception type, such as `TypeError`, `ValueError`, `AttributeError`, or
+`OSError`.
+
+.. _jpype_types_raising_exceptions_in_proxies:
+
+Raising Exceptions in Proxies
+-----------------------------
+
+JPype allows Python proxies to raise exceptions that are propagated back to
+Java. This is particularly useful when implementing Java interfaces in Python
+and handling invalid inputs or unexpected conditions.
+
+When an exception is raised in Python, it is wrapped in a `RuntimeException` in
+Java. If the exception propagates back to Python, it is unpacked to return the
+original Python exception.
+
+.. _jpype_types_example:
+
+Example
+~~~~~~~
+
+The following example demonstrates raising a Python exception from a proxy:
+
+.. code-block:: python
 
-Exception aliasing
+    import jpype
+    import jpype.imports
+
+    jpype.startJVM()
+
+    from java.util.function import Function
+
+    @jpype.JImplements(Function)
+    class MyFunction:
+        @jpype.JOverride
+        def apply(self, value):
+            if value is None:
+                raise ValueError("Invalid input: None is not allowed")
+            return value.upper()
+
+    try:
+        func = MyFunction()
+        result = func.apply(None)  # This will raise a ValueError
+    except ValueError as ex:
+        print("Caught Python exception:", str(ex))
+
+
+.. _jpype_types_exception_aliasing:
+
+Exception Aliasing
 ------------------
 
-Certain exceptions in Java have a direct correspondence with existing
-Python exceptions.  Rather than forcing JPype to translate these exceptions,
-or forcing the user to handle Java exception types throughout the code,
-we have "derived" these exceptions from their Python counter parts.  Thus,
-rather than requiring special error handling for Java you can simple catch
-these exceptions using the standard Python exception types.
-
-`java.lang.IndexOutOfBoundsException`
-  This exception is synonymous with the Python exception ``IndexError``.
-  As many slicing or array operations in Java can produce an
-  IndexOutOfBoundsException but the Python contract for slicing of an array
-  should raise an ``IndexError``, this type has been customized to consider
-  IndexError to be a base type.
-
-
-`java.lang.NullPointerException`
-  This exception is derived from the Python exception ``ValueError``.
-  Numerous Java calls produce a ``NullPointerException`` and in all cases this
-  would match a Python ``ValueError``.
+Certain exceptions in Java have a direct correspondence with existing Python
+exceptions. Rather than forcing JPype to translate these exceptions or
+requiring the user to handle Java exception types throughout the code, these
+exceptions are "derived" from their Python counterparts. This allows the user
+to catch them using standard Python exception types.
+
++---------------------------------------+------------------+
+| Java Exception                        | Python Exception |
++---------------------------------------+------------------+
+| `java.lang.IndexOutOfBoundsException` | `IndexError`     |
+| `java.lang.NullPointerException`      | `ValueError`     |
++---------------------------------------+------------------+
+
+
+.. _jpype_types_aliasing_example:
 
+Aliasing Example
+~~~~~~~~~~~~~~~~
+
+The following example demonstrates catching an aliased exception:
+
+.. code-block:: python
+
+    try:
+        # Code that throws a java.lang.IndexOutOfBoundsException
+    except IndexError as ex:
+        print("Caught IndexError:", str(ex))
 
 By deriving these exceptions from Python, the user is free to catch the
 exception either as a Java exception or as the more general Python exception.
@@ -1724,6 +2524,7 @@ Remember that Python exceptions are eval
 least.
 
 
+.. _controlling_the_jvm:
 
 Controlling the JVM
 *******************
@@ -1737,374 +2538,1122 @@ needed are to start up and shutdown the
 Starting the JVM
 ================
 
-The first task is always to start the JVM.  The settings to the JVM
-are immutable over the lifespan of the JVM.  The user settings are:
-the JVM arguments, the class path used to find jars, and whether to
-convert Java strings to Python strings.
+JPype requires the Java Virtual Machine (JVM) to be started before interacting
+with Java. This section explains how to start the JVM, configure its options,
+and troubleshoot common issues.
 
-Class paths
------------
+.. _controlling_the_jvm_key_requirements:
+
+Key Requirements
+----------------
+Before starting the JVM, ensure the following prerequisites are met:
+
+1. **Java Installation**: A Java Runtime Environment (JRE) or Java Development
+   Kit (JDK) must be installed. JPype supports Java versions 11 and later.
+
+2. **Architecture Match**: The architecture of the Python interpreter (e.g.,
+   64-bit or 32-bit) must match the architecture of the installed JVM.
+
+3. **Classpath Configuration**: Specify the paths to Java classes or JAR files
+   required by your application.
+
+4. **Environment Variable**: Ensure the `JAVA_HOME` environment variable is set
+   to the directory containing the Java installation.
+
+
+How to Start the JVM
+--------------------
+To start the JVM, use the ``jpype.startJVM()`` function. This function
+initializes the JVM with the specified options. The key arguments are:
+
+- **``classpath``**: A list of paths to JAR files or directories containing
+  Java classes.
+- **``convertStrings``**: A boolean flag controlling whether Java strings are
+  automatically converted to Python strings.
+- **``ignoreUnrecognized``**: A flag that suppresses errors for unrecognized
+  JVM options.
+- **Additional JVM options**: Any valid JVM arguments (e.g., ``-Xmx`` for
+  memory allocation).
+
+
+Example: Starting the JVM
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Here is a typical example of starting the JVM:
+
+.. code-block:: python
+
+    import jpype
+
+    # Start the JVM with classpath and options
+    jpype.startJVM(
+        classpath=['lib/*', 'classes'],
+        jvmOptions=["-ea"]  # Enable assertions
+    )
+
+
+
+Classpath Configuration
+-----------------------
+JPype supports two methods for specifying the classpath:
+
+1. **``classpath`` Argument**: Pass a list of paths directly to the
+   ``startJVM()`` function. Wildcards (``*``) are supported for JAR files in a
+   directory.
+
+.. code-block:: python
+
+    jpype.startJVM(classpath=['lib/*', 'classes'])
+
+2. **``addClassPath()`` Function**: Use ``jpype.addClassPath()`` to add paths
+   dynamically before starting the JVM.
+
+.. code-block:: python
+
+    jpype.addClassPath('lib/*')
+    jpype.addClassPath('classes')
+    jpype.startJVM()
+
+To debug classpath issues, print the effective classpath after starting the
+JVM:
+
+.. code-block:: python
+
+    print(java.lang.System.getProperty('java.class.path'))
+
+.. _controlling_the_jvm_handling_jar_files_compiled_for_newer_java_versions:
+
+Handling JAR Files Compiled for Newer Java Versions
+---------------------------------------------------
+If a JAR file is compiled for a newer version of Java than the JVM being used,
+JPype will fail to load the classes from the JAR file, and the JVM will throw
+an ``UnsupportedClassVersionError``. This occurs because the JVM cannot
+interpret class files compiled for a newer version.
+
+
+.. _controlling_the_jvm_behavior:
+
+Behavior
+~~~~~~~~
+When attempting to load a JAR file compiled for a newer version of Java, the
+JVM will throw an error similar to the following::
+
+    java.lang.UnsupportedClassVersionError: <class_name> has been compiled by a
+    more recent version of the Java Runtime (class file version X), this
+    version of the Java Runtime only recognizes class file versions up to Y.
+
+For example:
+
+- Java 11 corresponds to class file version 55.
+- Java 17 corresponds to class file version 61.
+
+If the JAR file contains class files compiled with a newer version than the
+JVM supports, the JVM cannot interpret them.
+
+.. _controlling_the_jvm_starting_the_jvm_handling_jar_files_compiled_for_newer_java_versions_steps_to_resolve:
+
+Steps to Resolve
+~~~~~~~~~~~~~~~~
+1. **Upgrade the JVM**:
+   Ensure the JVM version matches or exceeds the version used to compile the
+   JAR file. Use the following command to check the JVM version::
+
+       java -version
+
+2. **Recompile the JAR**:
+   If you have access to the source code, recompile the JAR with an older
+   version of Java using the ``--release`` flag. For example::
+
+       javac --release 11 -d output_directory source_files
+
+   This ensures compatibility with Java 11.
+
+3. **Check the Class File Version**:
+   Use the ``javap`` command to verify the class file version of the JAR::
+
+       javap -verbose <class_name>
+
+   Look for the ``major version`` field in the output.
+
+
+.. _controlling_the_jvm_best_practices:
+
+Best Practices
+~~~~~~~~~~~~~~
+- Always ensure the JVM version matches the requirements of the JAR files
+  being loaded.
+- If possible, use JAR files compiled for long-term support (LTS) versions of
+  Java, such as Java 11 or Java 17, to maximize compatibility.
+
+.. _controlling_the_jvm_automatic_jvm_path_detection:
+
+Automatic JVM Path Detection
+----------------------------
+JPype automatically detects the path to the JVM shared library using the
+``JAVA_HOME`` environment variable. If ``JAVA_HOME`` is not set, JPype searches
+common directories based on the platform. You can retrieve the detected path
+using:
+
+.. code-block:: python
+
+    print(jpype.getDefaultJVMPath())
+
+If the automatic detection fails, specify the JVM path manually as the first
+argument to ``startJVM()``:
+
+.. code-block:: python
+
+    jpype.startJVM('/path/to/libjvm.so', classpath=['lib/*'])
+
+
+.. _controlling_the_jvm_handling_nonascii_characters_in_the_jvm_path:
+
+Handling Non-ASCII Characters in the JVM Path
+----------------------------------------------
+JPype has been revised to handle JVM paths containing non-ASCII characters. Due
+to restrictions in Java, JPype must make a copy of the JVM shared library when
+the path includes non-ASCII characters. This ensures compatibility with the
+Java Virtual Machine.
+
+**Windows-Specific Behavior**:
+On Windows, the copied JVM shared library cannot be deleted after use due to
+file locking restrictions imposed by the operating system. As a result, the
+temporary file will remain on disk after the JVM is shut down.
+
+**Implications**:
+
+- The copied JVM shared library will occupy disk space until manually removed.
+
+- This behavior is specific to Windows and does not affect Linux or macOS.
+
+**Best Practices**:
+
+- Avoid using non-ASCII characters in the JVM path when running JPype on
+  Windows to prevent unnecessary file duplication.
+
+- If non-ASCII characters are unavoidable, ensure sufficient disk space is
+  available for temporary files.
+
+**Troubleshooting**:
+To locate the copied JVM shared library, check the directory where the JVM path
+is specified. The copied file will have the same name as the original shared
+library but may include additional identifiers.
+
+**Example**:
+If the original JVM path is:
+C:\Program Files\Java\jdk-11.0.7\bin\server\jvm.dll
+
+And it contains non-ASCII characters, JPype will create a copy in a temporary directory.
+
+This behavior is necessary to ensure compatibility with Java's handling of
+non-ASCII paths.
+
+
+.. _controlling_the_jvm_additional_flags_for_startjvm:
+
+Additional Flags for `startJVM()`
+---------------------------------
+JPype provides several optional flags for `startJVM()` to customize the JVM
+startup process:
+
+1. **`jvmOptions`**: A list of JVM options for memory, debugging, or garbage
+   collection tuning.
+   Example: ``jpype.startJVM(jvmOptions=["-Xmx512m", "-XX:+UseG1GC"])``
+
+2. **`ignoreUnrecognized`**: Suppresses errors for unrecognized JVM options.
+   Example: ``jpype.startJVM(ignoreUnrecognized=True)``
+
+3. **`convertStrings`**: Controls automatic conversion of Java strings to
+   Python strings.
+   Example: ``jpype.startJVM(convertStrings=False)``
+
+4. **`classpath`**: Specifies paths to JAR files and Java classes.
+   Example: ``jpype.startJVM(classpath=["lib/*", "classes"])``
+
+5. **`jvmPath`**: Specifies the path to the JVM shared library.
+   Example: ``jpype.startJVM(jvmPath="/path/to/libjvm.so")``
+
+6. **`attachThread`**: Automatically attaches Python threads to the JVM.
+   Example: ``jpype.startJVM(attachThread=True)``
+
+7. **`disableGC`**: Disables JPype's garbage collection hooks.
+   Example: ``jpype.startJVM(disableGC=True)``
 
-JPype supports two styles of classpaths.  The first is modeled after
-Matlab the second argument style uses a list to the ``startJVM`` function.
+8. **`stackTrace`**: Enables detailed stack traces for Java exceptions.
+   Example: ``jpype.startJVM(stackTrace=True)``
 
-The Matlab style uses the functions ``jpype.addClassPath`` and
-``getClassPath``.  The first function adds a directory or jar file to the
-search path.  Wild cards are accepted in the search.  Once all of the paths are
-added to internal class path, they can be retrieved using ``getClassPath``
-which takes a keyword argument ``env`` which defaults to true.  When set to
-false, JPype will ignore the environment variable
-``CLASSPATH`` which is normally included in the default classpath.
-
-To use the argument style, pass all of the class paths in a list as
-the keyword argument ``classpath`` to the ``startJVM``.  This classpath
-method does not include the environment ``CLASSPATH``, but it does provide
-a quick method to pull in a specific set of classes.  Wild cards are accepted
-as the end of the path to include all jars in a given directory.
-
-One should note that the class path can only be set prior starting the JVM.
-Calls to set the class path after the JVM is started are silently ignored.
-If a jar must be loaded after the JVM is started, it may be loaded using
-``java.net.URLClassLoader``.  Classes loaded using a ``URLClassloader`` are
-not visible to JPype imports nor to JPackage.
+9. **`initializers`**: A list of Python functions executed during JVM startup.
+   Example: ``jpype.startJVM(initializers=[setup])``
 
-String conversions
+10. **`modulePath`**: Specifies the module path for Java modular applications.
+    Example: ``jpype.startJVM(modulePath=["modules/*"])``
+
+.. _string_conversions:
+
+String Conversions
 ------------------
+The ``convertStrings`` argument controls whether Java strings are automatically
+converted to Python strings. By default, this behavior is disabled
+(``convertStrings=False``) to preserve Java string methods and avoid
+unnecessary conversions.
 
-The ``convertStrings`` argument defines how strings are returned by JPype.
-Early in the life of this project return types were often converted to Python
-types without regard to preserving the type information.  Thus strings would
-automatically convert to a Python string effectively the data from Java to
-Python on each return.  This was a violation of the Python philosophy that
-explicit is better than implicit. This also prohibited chaining of Java string
-operations as each operation would lose the Java representation and have to be
-transferred back and forth.  The simple operation of trying to create a Java
-string was difficult as directly calling ``java.lang.String`` constructor would
-once again convert the result back to a Python string, hence the need to use
-the ``JString`` factory.  There was an option to turn off the conversion of
-strings, but it was never operable.  Therefore, all code written at the
-time would expect Java strings to convert to Python strings on return.
-
-Recognizing this is both a performance issue and that it made certain types of
-programming prohibitive, JPype switched to having a setting requiring
-applications to chose a policy at the start of operation.  This option
-is a keyword argument ``convertStrings``.  The default for 0.7 is to give
-the older broken behavior.  If specified as False, Java strings will act
-as ordinary classes and return a Java string instance.  This string instance
-can be converted by calling the Python ``str()`` function.
-Failure to specify a policy will issue a warning message.
-
-You are strongly encouraged to set convertStrings false especially when
-are writing reusable Python modules with JPype.  String in JPype 0.8,
-the default will to not convert strings.
 
-Path to the JVM
----------------
+If enabled (``convertStrings=True``), Java strings are returned as Python
+strings, but this can impact performance and chaining of Java string methods.
+This option is consisted a legacy option as it will result in unncessary
+calls to ``str()`` every time a String is passed from Java.
 
-In order the start the JVM, JPype requires the path to the Java shared library
-typically located in the JRE installation.  This can either be specified
-manually as the first argument to ``jpype.startJVM`` or by automatic search.
-
-The automatic search routine uses different mechanisms depending on the
-platform.  Typically the first mechanism is the use the environment variable
-``JAVA_HOME``.  If no suitable JVM is found there, it will then search common
-directories based on the platform.  On windows it will consult the registry.
-
-You can get the JVM found during the automatic search by calling
-``jpype.getDefaultJVMPath()``.
-
-In order to use the JVM, the architecture of the JVM must match the Python
-version.  A 64 bit Python can only use a 64 bit JVM.  If no suitable JVM can be
-found it should raise an error. In some cases so rare, it may lead to a crash
-depending on how the platform handles a failed shared library load.
+Best practice: Set ``convertStrings=False`` unless your application explicitly
+requires automatic conversion.
 
-Launching the JVM
------------------
 
-Now that we have discussed the JVM options, lets show how to put it into
-practice.  Suppose that the Python script at the top level of your working
-director, with a subdirectory holding all your working jars ``./lib``, and a
-second directory with bare classes ``./classes``.  Java has been properly
-installed with the same architecture as Python (both 64 bit in this case).
+.. _controlling_the_jvm_checking_jvm_state:
+
+Checking JVM State
+------------------
+Use the following functions to check the status of the JVM:
+
+- **``jpype.isJVMStarted()``**: Returns ``True`` if the JVM is running.
+- **``jpype.getJVMVersion()``**: Retrieves the version of the running JVM.
 
-To start JPype we would execute the following:
+Example:
 
 .. code-block:: python
 
-  import jpype
-  jpype.startJVM("-ea", classpath=['lib/*', 'classes'], convertStrings=False)
+    if not jpype.isJVMStarted():
+        print("JVM is not running!")
+    else:
+        print("JVM version:", jpype.getJVMVersion())
 
-Arguments that begin with a dash are passed to the JVM.  Any
-unrecognized argument will raise an exception unless the keyword argument
-``ignoreUnrecognized`` is set to ``True``.  Details of available arguments can
-be found in the vendor JVM documentation.
-
-The most frequent problem encountered when starting JPype is the jars failing
-to be loaded.  Java is unforgiving when loading jar files.  To debug
-the failures, we will need to print the loaded classpath.
+.. _controlling_the_jvm_common_issues_and_troubleshooting:
 
-Java has a method to retrieve the classpath that was used during the loading
-process.
+Common Issues and Troubleshooting
+---------------------------------
+1. **Classpath Errors**: Ensure that all required JAR files and directories are
+   included in the classpath. Use ``java.lang.System.getProperty('java.class.path')``
+   to verify the effective classpath.
+
+2. **Architecture Mismatch**: Ensure the Python interpreter and JVM have
+   matching architectures (e.g., both 64-bit or both 32-bit). Running a 64-bit
+   Python interpreter with a 32-bit JVM will cause startup failures.
+
+3. **Environment Variable Issues**: Verify that the ``JAVA_HOME`` environment
+   variable is set correctly. If necessary, set it manually:
+   - **Windows**: ``set JAVA_HOME=C:\Program Files\Java\jdk-<version>``
+   - **Linux/Mac**: ``export JAVA_HOME=/usr/lib/jvm/java-<version>``
+
+4. **Unrecognized JVM Options**: If you encounter errors for unrecognized JVM
+   options, use the ``ignoreUnrecognized=True`` flag to suppress them.
+
+5. **Memory Allocation Errors**: Ensure sufficient memory is allocated to the
+   JVM using the ``-Xmx`` option.
+
+6. **Debugging Startup Failures**: Enable stack traces for additional
+   diagnostics:
 
 .. code-block:: python
 
-   print(java.lang.System.getProperty('java.class.path'))
+    import _jpype
+    _jpype.enableStacktraces(True)
+
 
-This command will print the absolute path to each of the jars that will be used
-by the JVM.  Each of the jars are written out explicitly as
-the JVM does not permit wild-cards. JPype has expanded each of them using
-`glob`.  If an expected jar file is missing the list, then it will not be
-accessable.
-
-There is a flag to determine the current state of the JVM.  Calling
-``jpype.isJVMStarted()`` will return the current state of the JVM.
-
-Once the JVM is started, we can find out the version of the JVM.  The JVM can
-only load jars and classfiles compiled for the JVM version or older.  Newer jar
-files will invariably fail to load.  The JVM version can be determined using
-``jpype.getJVMVersion()``.
+.. _controlling_the_jvm_best_practices_for_jvm_starting:
+
+Best Practices for JVM starting
+-------------------------------
+- **Start Early**: Start the JVM at the beginning of your program to avoid
+  issues with imports and initialization.
+- **Specify Classpath Explicitly**: Use the ``classpath`` argument to ensure
+  all required JAR files and directories are loaded.
+- **Disable String Conversion**: Set ``convertStrings=False`` for better
+  control and performance.
+- **Avoid Restarting the JVM**: JPype does not support restarting the JVM after
+  it has been shut down. Design your application to start the JVM once and keep
+  it running for the program's lifetime.
+- **Monitor Resource Usage**: If your application uses large Java objects,
+  monitor memory usage to avoid out-of-memory errors.
+
+.. _controlling_the_jvm_starting_the_jvm_summary:
+
+Summary of JVM starting
+-----------------------
+Starting the JVM is a critical step in using JPype to integrate Python with
+Java. By following the guidelines in this section, you can ensure a smooth
+startup process, avoid common pitfalls, and configure the JVM to meet your
+application's needs. Proper classpath configuration, architecture matching, and
+memory allocation are key to successful integration. Debugging tools and best
+practices are available to help troubleshoot issues and optimize performance.
 
 
 .. _shutdownJVM:
 
-Shutting down the JVM
-=====================
+Shutting Down the JVM
+======================
 
-At the other end of the process after all work has been performed, we will want
-to shutdown the JVM to terminate the program.  This will happen automatically
-and no user intervention is required.  If however, the user wants to continue
-execution of Python code after the JVM is finished they can explicitly call
-``jpype.shutdownJVM()``.  This can only be called from the main Python thread.
-Any other thread will raise an exception.
-
-The shutdown procedure of JPype and Java is fairly complicated.
-
-1) JPype requests that the JVM shutdown gracefully.
-2) Java waits until all non-daemon thread terminate. Thus if you did not
-   send a termination to each non-daemon threads the shutdown will wait here
-   until those threads complete their work.
-3) Once the all threads have completed except for the main thread, the JVM
-   will begin the shutdown sequence.  From this point on the JVM is in a
-   crippled state limited what can happen to spawning the shutdown threads
-   and completing them.
-4) The shutdown will first spawn the threads of cleanup routine that was
-   attached to the JVM shutdown hook in arbitrary order.  These routines
-   can call back to Python and perform additional tasks.
-5) Once the last of these threads are completed, JPype then shuts down the
-   reference queue which dereferences held all Python resources.
-6) Then JPype shuts down the type manager and frees all internal resources
-   in the JPype module.
-7) Last, it unloads the JVM shared library returning the memory used by the JVM.
-8) Once that is complete, control is returned to Python.
+At the end of your program, you may want to shut down the JVM to terminate the
+Java environment explicitly. While this is possible, it is generally not
+recommended unless absolutely necessary. JPype automatically shuts down the JVM
+when the Python process terminates, ensuring a clean exit without manual
+intervention.
 
-All Java objects are now considered dead and cannot be reactivated. Any attempt
-to access their data field will raise an exception.
+.. _controlling_the_jvm_risks_of_shutting_down_the_jvm:
 
-Attaching a shutdown hook
--------------------------
+Risks of Shutting Down the JVM
+------------------------------
+
+Shutting down the JVM manually can lead to serious risks and instability,
+especially if there are lingering Java references or shared resources. Once the
+JVM is shut down, all Java objects become invalid, and any attempt to access
+them will result in errors. This includes:
+
+- **Lingering Java References**: Any Java objects held by Python will become
+  invalid after the JVM is shut down. Accessing these objects will raise
+  exceptions and could result in undefined behavior.
+
+- **Shared Resources**: Shared resources such as buffers (e.g., memory mapped
+  from Java to NumPy) will become unstable. Accessing these buffers after the
+  JVM is shut down may cause crashes or memory corruption.
+
+- **Proxies and Threads**: If Java threads or proxies are active when the JVM is
+  shut down, they will be terminated abruptly, potentially leaving the system in
+  an inconsistent state.
+
+- **Non-Daemon Threads**: All threads must be attached as daemon threads before
+  shutting down the JVM. Non-daemon threads will block the shutdown process,
+  causing it to hang indefinitely. Python threads that interact with Java are
+  automatically attached as daemon threads by JPype, but any custom threads
+  created in Java must also be marked as daemon.
+
+For most applications, it is safer to allow the JVM to shut down automatically
+when the Python process exits. This ensures that all resources are cleaned up
+properly and avoids the risks associated with manual shutdown.
+
+.. _controlling_the_jvm_how_jpype_shuts_down_the_jvm:
+
+How JPype Shuts Down the JVM
+----------------------------
+
+JPype performs the following steps during JVM shutdown to ensure proper cleanup:
 
-If you have resources that need to be closed when the JVM is shutdown these
-should be attached to the Java Runtime object.  The following pattern is used:
+1. **Request JVM Shutdown**: JPype requests the JVM to shut down gracefully.
+2. **Wait for Non-Daemon Threads**: The JVM waits for all non-daemon threads to
+   terminate. If you have active Java threads, ensure they are properly
+   terminated or marked as daemon before shutting down the JVM.
+3. **Execute Shutdown Hooks**: The JVM executes any registered shutdown hooks.
+   These hooks can be used to clean up resources before the JVM terminates.
+4. **Release JPype Reference Queue**: JPype shuts down its internal reference
+   queue, which is responsible for dereferencing Python resources tied to Java
+   objects.
+5. **Release JPype Type Manager**: JPype releases its type manager, which
+   handles mappings between Python and Java types.
+6. **Unload JVM Shared Library**: The JVM shared library is unloaded, freeing
+   memory used by the JVM.
+7. **Finalize Python Resources**: JPype cleans up any remaining Python handles
+   tied to Java objects, ensuring that no invalid references remain.
+
+Once the JVM is shut down, all Java objects are considered dead and cannot be
+reactivated. Any attempt to access their data field will raise an exception.
+
+.. _controlling_the_jvm_managing_threads_during_jvm_shutdown:
+
+Managing Threads During JVM Shutdown
+------------------------------------
+
+The JVM requires all threads to be attached as daemon threads during shutdown.
+Daemon threads are background threads that do not prevent the JVM from
+terminating. Non-daemon threads, on the other hand, will block the shutdown
+process, causing it to hang indefinitely until those threads terminate.
+
+JPype automatically attaches Python threads that interact with Java as daemon
+threads. However, if you create custom threads in Java, you must explicitly mark
+them as daemon threads to ensure they do not block the JVM shutdown.
+
+To mark a Java thread as a daemon, use the following pattern:
 
 .. code-block:: python
 
-    @JImplements(Runnable)
-    class MyShutdownHook:
-        @JOverride
-        def run(self):
-            # perform any required shutdown activities
+    import java.lang.Thread
 
-    java.lang.Runtime.getRuntime().addShutdownHook(Thread(MyShutdownHook()))
+    # Create a Java thread
+    thread = java.lang.Thread()
 
-This thread will be executed in a new thread once the main thread is
-the only one remaining alive.  Care should always be taken to complete
-work in a timely fashion and be aware the shutdown threads are inherently
-racing with each other to complete their work.  Thus try to avoid expensive
-operations on shutdown..
+    # Mark the thread as daemon
+    thread.setDaemon(True)
 
-Debugging shutdown
--------------------
+    # Start the thread
+    thread.start()
 
-The most common failure during shutdown is the failure of an attached thread
-to terminate.  There are specific patterns in Java that allow you to query
-for all currently attached threads.
+If you need to check whether a thread is a daemon, use the `isDaemon()` method:
 
+.. code-block:: python
 
+    print(f"Thread is daemon: {thread.isDaemon()}")
 
-Customization
-*************
+Ensure that all non-daemon threads are properly terminated or marked as daemon
+before shutting down the JVM. Failure to do so may cause the shutdown process to
+hang indefinitely.
+
+.. _controlling_the_jvm_how_to_shut_down_the_jvm:
+
+How to Shut Down the JVM
+------------------------
+
+If you must shut down the JVM manually, you can use the `jpype.shutdownJVM()`
+function. This should only be called from the main Python thread. Calling it
+from any other thread will raise an exception.
+
+.. code-block:: python
+
+    import jpype
+
+    # Shut down the JVM
+    jpype.shutdownJVM()
+
+
+Numerous examples found on the internet explicity state that `shutdownJVM` is a
+good practice.  These examples are legecy from early developement.  At the time
+shutdownJVM brutally closed the JVM and bypassed all for the JVM shutdown routines
+thus causing the program to skip over errors in the JPype module resulting
+from mishandled race conditions.   While it is still acceptable to shutdown the
+JVM and may be desireable to do so if a module needs a particular order to shutdown
+cleanly, the use of an explicit shutdown is discouraged.
+
+
+.. _controlling_the_jvm_debugging_jvm_shutdown:
+
+Debugging JVM Shutdown
+----------------------
+
+If the JVM shutdown process hangs or fails, it is often due to lingering threads
+or resources that were not properly terminated. Use the following techniques to
+debug shutdown issues:
+
+1. **Check Active Threads**: Before shutting down the JVM, check for active
+   non-daemon threads that may be preventing the shutdown. You can use the
+   following Java code to list all active threads:
+
+   .. code-block:: python
+
+       import java.lang.Thread
+
+       # Get all active threads
+       threads = java.lang.Thread.getAllStackTraces().keySet()
+       for thread in threads:
+           print(f"Thread: {thread.getName()}, Daemon: {thread.isDaemon()}")
+
+   Ensure that all non-daemon threads are terminated or marked as daemon before
+   calling `jpype.shutdownJVM()`.
+
+2. **Inspect Shutdown Hooks**: If you have attached shutdown hooks, verify that
+   they complete quickly and do not hang. Long-running shutdown hooks can delay
+   or block JVM termination.
 
-JPype supports three different types of customizations.
+3. **Monitor Resource Usage**: If shared resources such as buffers are in use,
+   ensure that they are properly released before shutting down the JVM. For
+   example, copy buffer contents to a Python object to preserve data.
 
-The first is to adding a Python base class into a Java tree as was done with
-certain exceptions.  This type of customization required private calls in JPype
-and is not currently exposed to the user.
+4. **Enable Debugging Logs**: JPype can provide additional diagnostics during
+   the shutdown process. Use the following command to enable debugging logs:
 
-Second a Python class can be used as a template when a Java class is first
-constructed to add additional functionality.  This type of customization can
-be used to make a Java class appear as a native Python class. Many
-of the Java collection classes have been customized to match Python
-collections.
+   .. code-block:: python
 
-Last, Python class can be added to the implicit conversion list.  This
-customizer is used to make Python types compatable with Java without
-requiring the user to manually case over and over.
+       import _jpype
+       _jpype.enableStacktraces(True)
 
-All customization available to the users is done through class decorators
-added to Python classes or functions.
+   This will print detailed stack traces for exceptions that occur during the
+   shutdown process.
 
+5. **Handle Hanging Threads**: If the JVM shutdown hangs due to threads that
+   cannot terminate, you can forcefully terminate the Python process using
+   `os._exit()` or `java.lang.Runtime.exit()`. **However, note that calling
+   `exit` will bypass normal `atexit` routines in both Python and Java.** This
+   means that any cleanup tasks, such as writing logs (e.g., Jacoco coverage
+   reports) or flushing buffers, will not be executed. Use this approach only
+   as a last resort when all other debugging techniques fail.
 
-.. _@JImplementationFor:
-.. _@JOverride:
+.. _controlling_the_jvm_best_practices_for_jvm_shutdown:
+
+Best Practices for JVM Shutdown
+-------------------------------
+
+- **Avoid Manual Shutdown**: Whenever possible, allow the JVM to shut down
+  automatically when the Python process exits. This avoids the risks of lingering
+  references and shared resource instability.
+
+- **Terminate Threads Properly**: Ensure all non-daemon Java threads are
+  terminated or marked as daemon before shutting down the JVM. Failure to do so
+  may cause the shutdown process to hang indefinitely.
+
+- **Handle Buffers Carefully**: If you are using shared buffers (e.g., Java
+  direct buffers with NumPy), avoid accessing them after the JVM is shut down.
+  If you need to preserve data, copy the buffer contents to a Python object
+  before shutting down the JVM.
+
+- **Use Shutdown Hooks**: Attach shutdown hooks only when necessary to clean up
+  resources. Ensure that the hooks complete quickly to avoid delaying JVM
+  termination.
+
+- **Avoid Forceful Termination**: Avoid using `os._exit()` or `java.lang.Runtime.exit()`
+  unless absolutely necessary. These methods prevent normal cleanup routines from
+  executing, which can result in missing logs, incomplete resource cleanup, or
+  other unintended consequences.
+
+
+.. _controlling_the_jvm_summary_of_jvm_shutdown:
+
+Summary of JVM Shutdown
+------------------------
+
+JPype's shutdown process is designed to ensure that resources are cleaned up
+properly and the JVM terminates gracefully. While shutting down the JVM manually
+is possible, it introduces risks that can lead to instability and crashes. For
+most applications, the JVM should be allowed to shut down automatically when the
+Python process exits. If manual shutdown is required, take precautions to ensure
+that all Java references and shared resources are properly cleaned up before
+shutting down the JVM. Avoid forceful termination unless absolutely necessary,
+as it bypasses critical cleanup routines in both Python and Java.
+
+
+
+.. _customization:
+
+Customization
+*************
+
+JPype supports customization to enhance the integration between Java and Python.
+This allows users to modify Java classes and type conversions to better suit
+their needs, making Java APIs more Pythonic or enabling seamless interaction
+with Python data structures.
+
+There are two primary types of customizations available:
+
+1. **Class Customizers**: Add Python methods and properties to Java classes to
+   make them behave like native Python classes.
+2. **Type Conversion Customizers**: Define implicit conversions between Python
+   types and Java types for seamless interoperability.
+
+.. _customization_class_customizers:
 
 Class Customizers
 =================
 
-Java wrappers can be customized to better match the expected behavior in
-Python.  Customizers are defined using decorators.  Applying the annotations
+Customizers are applied to JPype wrapper classes to enhance their Pythonic
+interface. By adding Python methods and properties to Java classes, customizers
+make Java objects behave like native Python objects. These customizations are
+applied to wrappers, whether they encapsulate a proxy or a Java reference.
+
+Java wrappers can be customized to better match the expected behavior in Python.
+Customizers are defined using decorators. Applying the annotations
 ``@JImplementationFor`` and ``@JOverride`` to a regular Python class will
-transfer methods and properties to a Java class.  ``@JImplementationFor``
-requires the class name as a string, a Java class wrapper, or Java class
-instance.  Only a string can be used prior to starting the JVM.  ``@JOverride``
-when applied to a Python method will hide the Java implementationallowing the
-Python method to replace the Java implementation.  when a Java method is
-overridden, it is renamed with an proceeding underscore to appear as a private
-method.  Optional arguments to ``@JOverride`` can be used to control the
-renaming and force the method override to apply to all classes that derive
-from a base class ("sticky").
+transfer methods and properties to a Java class.
+
+``@JImplementationFor`` requires the class name as a string, a Java class
+wrapper, or a Java class instance. Only a string can be used prior to starting
+the JVM. ``@JOverride``, when applied to a Python method, will hide the Java
+implementation, allowing the Python method to replace the Java implementation.
+When a Java method is overridden, it is renamed with a preceding underscore to
+appear as a private method. Optional arguments to ``@JOverride`` can be used to
+control the renaming and force the method override to apply to all classes that
+derive from a base class ("sticky").
 
 Generally speaking, a customizer should be defined before the first instance of
 a given class is created so that the class wrapper and all instances will have
 the customization.
 
-Example taken from JPype ``java.util.Map`` customizer:
+.. _customization_example_customizing_javautilmap:
 
-.. code-block:: python
+Example: Customizing ``java.util.Map``
+--------------------------------------
+
+The following example demonstrates how to customize the ``java.util.Map`` class
+to behave like a Python dictionary:
 
-  @_jcustomizer.JImplementationFor('java.util.Map')
-  class _JMap:
-      def __jclass_init__(self):
-          Mapping.register(self)
+.. code-block:: python
 
-      def __len__(self):
-          return self.size()
+   @_jcustomizer.JImplementationFor('java.util.Map')
+   class _JMap:
+       def __jclass_init__(self):
+           Mapping.register(self)
 
-      def __iter__(self):
-          return self.keySet().iterator()
+       def __len__(self):
+           return self.size()
 
-      def __delitem__(self, i):
-          return self.remove(i)
+       def __iter__(self):
+           return self.keySet().iterator()
 
+       def __delitem__(self, i):
+           return self.remove(i)
 
-The name of the class does not matter for the purposes of customizer though it
-should be a private class so that it does not get used accidentally.
+The name of the class does not matter for the purposes of the customizer,
+though it should be a private class so that it does not get used accidentally.
 The customizer code will steal from the prototype class rather than acting as a
-base class, thus, ensuring that the methods will appear on the most derived
-Python class and are not hidden by the java implementations. The customizer
-will copy methods, callable objects, ``__new__``, class member strings, and
-properties.
+base class, ensuring that the methods will appear on the most derived Python
+class and are not hidden by the Java implementations.
 
-.. _@JConversion:
+The customizer copies methods, callable objects, ``__new__``, class member
+strings, and properties.
+
+
+.. _customization_type_conversion_customizers:
 
 Type Conversion Customizers
 ===========================
 
-One can add a custom converter method which is called whenever a specified
-Python type is passed to a particular Java type.  To specify a conversion
-method add ``@JConversion`` to an ordinary Python function with the name of
-Java class to be converted to and one keyword of ``exact`` or ``instanceof``.
-The keyword controls how strictly the conversion will be applied.  ``exact`` is
-restricted to Python objects whose type exactly matches the specified type.
-``instanceof`` accepts anything that matches isinstance to the specified type
-or protocol.  In some cases, the existing protocol definition will be overly
-broad.  Adding the keyword argument ``excludes`` with a type or tuple of types
-can be used to prevent the conversion from being applied.  Exclusions always
-apply first.
+JPype allows users to define custom conversion methods that are called whenever
+a specified Python type is passed to a particular Java type. To specify a
+conversion method, add ``@JConversion`` to an ordinary Python function with the
+name of the Java class to be converted to and one keyword of ``exact`` or
+``instanceof``. The keyword controls how strictly the conversion will be
+applied:
+
+- ``exact``: Restricted to Python objects whose type exactly matches the
+  specified type.
+- ``instanceof``: Accepts anything that matches ``isinstance`` to the specified
+  type or protocol.
+
+In some cases, the existing protocol definition will be overly broad. Adding
+the keyword argument ``excludes`` with a type or tuple of types can be used to
+prevent the conversion from being applied. Exclusions always apply first.
 
-User supplied conversions are tested after all internal conversions have been
+User-supplied conversions are tested after all internal conversions have been
 exhausted and are always considered to be an implicit conversion.
 
+
+.. _customization_example_converting_python_sequences_to_java_collections:
+
+Example: Converting Python Sequences to Java Collections
+--------------------------------------------------------
+
+The following example demonstrates how to convert Python sequences into Java
+collections:
+
+.. code-block:: python
+
+   @JConversion("java.util.Collection", instanceof=Sequence,
+                             excludes=str)
+   def _JSequenceConvert(jcls, obj):
+       return _jclass.JClass('java.util.Arrays').asList(obj)
+
+JPype supplies customizers for certain Python classes by default. These include:
+
+========================= ==============================
+Python class              Implicit Java Class
+========================= ==============================
+pathlib.Path              java.io.File
+pathlib.Path              java.nio.file.Path
+datetime.datetime         java.time.Instant
+collections.abc.Sequence  java.util.Collection
+collections.abc.Mapping   java.util.Map
+========================= ==============================
+
+
+.. _customization_jpype_beans_module:
+
+JPype Beans Module
+==================
+
+.. _customization_overview_of_jpype_beans:
+
+Overview of JPype Beans
+-----------------------
+
+The `jpype.beans` module is an optional feature that converts Java Bean-style
+getter and setter methods into Python properties. This customization is
+particularly useful for interactive programming or when working with Java
+classes that follow the Bean pattern.
+
+However, this behavior is not enabled by default because it can lead to
+confusion about whether a class is exposing a variable or a property added by
+JPype. Additionally, it violates Python's principle of *"There should be one--
+and preferably only one --obvious way to do it."* and the C++ principle of
+*"You only pay for what you use."*
+
+If you find this feature useful, you can enable it explicitly by importing the
+`jpype.beans` module.
+
+.. _customization_enabling_beans_as_properties:
+
+Enabling Beans as Properties
+----------------------------
+
+To enable the `jpype.beans` module, simply import it into your Python program:
+
+.. code-block:: python
+
+  import jpype.beans
+
+Once enabled, the module applies globally to all Java classes that have already
+been loaded, as well as any classes loaded afterward. This behavior cannot be
+undone after the module is imported.
+
+.. _customization_how_it_jpype_beans_works:
+
+How It JPype beans Works
+------------------------
+
+The `jpype.beans` module scans Java classes for methods that follow the Bean
+naming conventions:
+
+- **Getter methods**: Methods prefixed with `get` (e.g., `getName`) are treated
+  as property accessors.
+- **Setter methods**: Methods prefixed with `set` (e.g., `setName`) are treated
+  as property mutators.
+
+For example, a Java class with the following methods:
+
+.. code-block:: java
+
+  public class Person {
+      private String name;
+
+      public String getName() {
+          return name;
+      }
+
+      public void setName(String name) {
+          this.name = name;
+      }
+  }
+
+Will automatically expose the `name` field as a Python property:
+
+.. code-block:: python
+
+  import jpype
+  import jpype.beans
+
+  jpype.startJVM()
+
+  Person = jpype.JClass("Person")
+  person = Person()
+  person.name = "Alice"  # Calls setName("Alice")
+  print(person.name)     # Calls getName(), Output: Alice
+
+.. _customization_implementation_details_of_jpype_beans:
+
+Implementation Details of JPype beans
+-------------------------------------
+
+The module works by:
+
+1. Identifying getter and setter methods in Java classes using the
+   `_isBeanAccessor()` and `_isBeanMutator()` methods.
+2. Creating Python properties for these methods.
+3. Adding the properties to the class dynamically.
+
+The customization applies retroactively to all classes currently loaded and
+globally to all future classes.
+
+.. _customization_limitations_of_jpype_beans:
+
+Limitations of JPype beans
+--------------------------
+
+1. **Global Behavior**: Once enabled, the customization applies to all Java
+   classes globally. It cannot be undone.
+2. **Confusion with Existing Members**: If a Java class already has a Python
+   member with the same name as a property, the property will not be added to
+   avoid conflicts.
+3. **Ambiguity**: This feature can make it unclear whether a field is a true
+   Java variable or a property added by JPype.
+
+.. _customization_best_practices_for_jpype_beans:
+
+Best Practices for JPype beans
+------------------------------
+
+- Use this module only when working with Java classes that heavily rely on the
+  Bean pattern.
+- Avoid enabling this module in large projects unless absolutely necessary, as
+  the global behavior may lead to unintended consequences.
+- Document its usage clearly in your codebase to avoid confusion for other
+  developers.
+
+.. _customization_summary_of_jpype_beans:
+
+Summary of JPype beans
+----------------------
+
+The `jpype.beans` module provides a convenient way to work with Java Bean-style
+classes in Python by exposing getter and setter methods as Python properties.
+While useful in certain scenarios, it is an optional feature that must be
+explicitly enabled and should be used with caution due to its global and
+irreversible behavior.
+
+.. _customization_resolving_method_name_conflicts_with_customizers:
+
+Resolving Method Name Conflicts with Customizers
+================================================
+
+.. _customization_overview_of_conflict_resolution:
+
+Overview of conflict resolution
+-------------------------------
+
+When working with Java classes in Python, conflicts can arise between public
+fields and methods that share the same name. JPype provides tools to resolve
+these conflicts using customizers, allowing you to rename fields or methods
+dynamically and expose them in a Pythonic way.
+
+This section demonstrates how to use a customizer to resolve such conflicts by
+renaming fields or methods and exposing them as Python properties.
+
+.. _customization_example_renaming_conflicting_fields_and_methods:
+
+Example: Renaming Conflicting Fields and Methods
+------------------------------------------------
+
+Consider a Java class with a field and a method that share the same name.
+Without customization, JPype will expose the method, and the field will be
+hidden. To resolve this, you can use a customizer to rename the conflicting
+field or method and expose it as a Python property.
+
+Here’s an example:
+
 .. code-block:: python
 
-        @_jcustomizer.JConversion("java.util.Collection", instanceof=Sequence,
-          excludes=str)
-        def _JSequenceConvert(jcls, obj):
-            return _jclass.JClass('java.util.Arrays').asList(obj)
-
-JPype supplies customizers for certain Python classes.
-
-========================== ==============================
-Python class               Implicit Java Class
-========================== ==============================
-pathlib.Path               java.io.File
-pathlib.Path               java.nio.file.Path
-datetime.datetime          java.time.Instant
-collections.abc.Sequence   java.util.Collection
-collections.abs.Mapping    java.util.Map
-========================== ==============================
+    def asProperty(field):
+        def get(E):
+            return field.get(E)
+        def set(E, V):
+            field.set(E, V)
+        return property(get, set)
+
+    @jpype.JImplementationFor("java.lang.Object")  # Use your base class.
+    class MyCustomizer(object):
 
+        # This is applied to every class that derives from the type
+        def __jclass_init__(cls):
+            # Traverse the fields
+            for field in cls.class_.getDeclaredFields():
+                name = str(field.getName())
+                tp = type(cls.__dict__.get(str(field.getName()), None))
+
+                # Watch for private methods
+                if tp is type(None):
+                    continue
+
+                # Resolve conflicts between public fields and methods
+                if tp is jpype.JMethod:
+                    cls._customize("%s_" % name, asProperty(field))
+
+.. _customization_how_it_conflict_resolution_works:
+
+How It Conflict Resolution Works
+--------------------------------
+
+1. **Field Traversal**: The customizer iterates over all declared fields in the
+   class using `getDeclaredFields()`.
+2. **Conflict Detection**: For each field, it checks whether a public method
+   with the same name exists.
+3. **Renaming**: If a conflict is detected, the field is renamed by appending
+   an underscore (`_`) to its name.
+4. **Property Creation**: The renamed field is exposed as a Python property
+   using the `property()` function.
+
+.. _customization_example_usage_of_conflict_resolution:
+
+Example Usage of Conflict Resolution
+------------------------------------
+
+Suppose you have a Java class `A` with a field `mean` and a method `mean`.
+Without customization, the field would be inaccessible. Using the customizer
+above, you can expose the field as `mean_`:
+
+.. code-block:: python
 
+    A = jpype.JClass("A")
+    a = A()
+    print(a.mean_)  # Access the renamed field
+    a.mean_ = 2      # Modify the field
+    print(a.mean_)   # Verify the updated value
+
+.. _customization_notes_on_global_customizers:
+
+Notes on Global Customizers
+---------------------------
+
+- The customizer is applied globally to all classes that derive from the
+  specified base class (`java.lang.Object` in this example). You can replace
+  the base class with a more specific class to limit the scope of the
+  customization.
+- This approach is particularly useful for resolving conflicts in large Java
+  libraries or frameworks where method and field names overlap frequently.
+
+.. _customization_best_practices_regarding_name_resolution_customizers:
+
+Best Practices Regarding Name Resolution Customizers
+----------------------------------------------------
+
+- Use meaningful naming conventions when renaming fields or methods to avoid
+  confusion.
+- Document customizations clearly in your codebase to help other developers
+  understand the changes.
+- Test the customizer thoroughly to ensure it behaves as expected across all
+  relevant classes.
+
+.. _customization_summary_of_naming_conflict_resolution:
+
+Summary of Naming Conflict Resolution
+-------------------------------------
+
+This example demonstrates how to use JPype customizers to resolve conflicts
+between fields and methods in Java classes. By renaming conflicting fields or
+methods and exposing them as Python properties, you can create a more Pythonic
+interface for interacting with Java classes.
+
+
+.. _customization_best_practices_for_class_customization:
+
+Best Practices For Class Customization
+======================================
+
+To ensure effective use of customizations, follow these best practices:
+
+1. **Define Customizers Early**: Always define customizers before the first
+   instance of the class is created to ensure proper initialization.
+
+2. **Test Customizations Thoroughly**: Verify that the customized behavior
+   works as expected, especially for complex or heavily-used classes.
+
+3. **Avoid Conflicts**: Ensure that customizers do not introduce conflicting
+   methods or properties, especially when customizing multiple interfaces.
+
+4. **Monitor Performance**: Be mindful of performance implications when adding
+   extensive customizations.
+
+5. **Document Customizations**: Clearly document the purpose and behavior of
+   customizations to assist other developers working on the codebase.
+
+By leveraging class and type conversion customizers, JPype users can create
+seamless integrations between Python and Java, making Java APIs feel native to
+Python programmers.
+
+
+.. _collections:
 
 Collections
 ***********
 
-JPype uses customizers to augment Java collection classes to operate like
-Python collections.  Enhanced objects include ``java.util.List``,
-``java.util.Set``, ``java.util.Map``, and ``java.util.Iterator``.  These
-classes generally comply with the Python API except in cases where there is a
-significant name conflict and thus no special treatment is required when
-handling these Java types.  Details of customizing Java classes can be
-found in the previous chapter, Customization_.
+JPype uses customizers to augment Java collection classes to operate like Python
+collections. Enhanced objects include ``java.util.List``, ``java.util.Set``,
+``java.util.Map``, and ``java.util.Iterator``. These classes generally comply
+with the Python API except in cases where there is a significant name conflict.
+This section details the integration of Java collections with Python constructs.
+
+.. _collections_specialized_collection_wrappers:
+
+Specialized Collection Wrappers
+===============================
+
+JPype customizes Java collection classes to behave like Python collections,
+making them intuitive for Python developers. This includes support for iteration,
+indexing, and key-value access. Below are the key behaviors of specific Java
+collection types.
 
-This section will detail the various customization that are to applied the Java
-collection classes.
+.. _collections_iterable:
 
 Iterable
-========
+--------
 
-All Java classes that implement ``java.util.Iterable`` are customized
-to support Python iterator notation and thus can be used in Python for loops
-and in list comprehensions.
+Java classes that implement ``java.util.Iterable`` are customized to support
+Python's iteration constructs. This allows seamless use in Python `for` loops
+and list comprehensions. For example, a Java ``ArrayList`` can be iterated
+directly:
+
+.. code-block:: python
+
+    from java.util import ArrayList
+
+    jlist = ArrayList()
+    jlist.add("apple")
+    jlist.add("orange")
+    jlist.add("banana")
+
+    for item in jlist:
+        print(item)
+
+This integration ensures that Java collections behave like Python sequences,
+providing a natural experience for Python developers.
+
+.. _collections_iterators:
 
 Iterators
-=========
+---------
+
+Java classes that implement ``java.util.Iterator`` act as Python iterators.
+This means they can be used in Python `for` loops and list comprehensions
+without requiring additional conversion. For example:
+
+.. code-block:: python
+
+    from java.util import Vector
+
+    jvector = Vector()
+    jvector.add("apple")
+    jvector.add("orange")
 
-All Java classes that implement ``java.util.Iterator`` act as Python iterators.
+    iterator = jvector.iterator()
+    for item in iterator:
+        print(item)
+
+.. _collections_collection:
 
 Collection
-==========
+----------
+
+Java classes that inherit from ``java.util.Collection`` integrate seamlessly
+with Python's collection constructs. They support operations such as length
+retrieval, iteration, and implicit conversion of Python sequences into Java
+collections. For example:
+
+.. code-block:: python
+
+    from java.util import ArrayList
 
-All Java classes that inherit from ``java.util.Collection`` have a defined
-length determined by the Python ``len(obj)`` function.  As they also inherit
-from Iterable, they have iterator, forech traversal, and list comprehension.
-
-In addition, methods that take a Java collection can convert a Python
-sequence into a collection implicitly if all of the elements have a
-conversion into Java.  Otherwise a ``TypeError`` is raised.
+    pylist = ["apple", "orange", "banana"]
+    jlist = ArrayList(pylist)  # Convert Python list to Java collection
+
+    print(len(jlist))  # Output: 3
+    for item in jlist:
+        print(item)
+
+Methods that accept Java collections can automatically convert Python sequences
+if all elements are compatible with Java types. Otherwise, a ``TypeError`` is
+raised.
 
 .. _java.util.List:
 
 Lists
-=====
+-----
 
-Java List classes such as ArrayList and LinkedList can be used in Python ``for``
-loops and list comprehensions directly.  A Java list can be converted to a
-Python list or the reverse by calling the requested type as a copy
-constructor.
+Java `List` classes, such as ``ArrayList`` and ``LinkedList``, can be used in
+Python `for` loops and list comprehensions. They also support indexing and
+deletion, making them behave like Python lists. For example:
 
 .. code-block:: python
 
-     pylist = ['apple', 'orange', 'pears']
+    from java.util import ArrayList
 
-     # Copy the Python list to Java.
-     jlist = java.util.ArrayList(pylist)
+    jlist = ArrayList()
+    jlist.add("apple")
+    jlist.add("orange")
+    jlist.add("banana")
 
-     # Copy the Java list back to Python.
-     pylist2 = list(jlist)
+    print(jlist[0])  # Output: apple
+    del jlist[1]     # Remove "orange"
+    print(jlist)     # Output: [apple, banana]
 
-Note that the individual list elements are still Java objects when converted
-to Python and thus a list comprehension would be required to force Python
-types if required.  Converting to Java will attempt to convert each argument
+Java lists can also be converted to Python lists and vice versa using the copy
+constructor. For example:
+
+.. code-block:: python
+
+    pylist = ["apple", "orange", "banana"]
+    jlist = ArrayList(pylist)  # Convert Python list to Java list
+    pylist2 = list(jlist)      # Convert Java list back to Python list
+
+Note that individual elements remain Java objects when converted to Python.
+Converting to Java will attempt to convert each argument
 individually to Java.  If there is no conversion it will produce a
 ``TypeError``.  The conversion can be forced by casting to the appropriate
 Java type with a list comprehension or by defining a new conversion
@@ -2114,16 +3663,14 @@ Lists also have iterable, length, item d
 indexing of ``java.util.LinkedList`` is supported but can have a large
 performance penalty for large lists.  Use of iteration is much for efficient.
 
-.. _java.util.Map:
 
-Map
-===
+.. _java.util.Map:
 
-A Java classes that implement ``java.util.Map`` inherit the Python
-collections.abc.Mapping interface.  As such they can be iterated, support
-the indexing operator for value lookups, item deletion, length, and
-support contains.
+Maps
+----
 
+Java classes that implement ``java.util.Map`` behave like Python dictionaries.
+They support key-value access, iteration, and deletion. 
 Here is a summary of their capabilities:
 
 =========================== ================================
@@ -2138,193 +3685,945 @@ Fetch the keys               ``jmap.key(
 Check for a key              ``key in jmap``
 =========================== ================================
 
-In addition, methods that take a Java map can implicitly convert a Python
-``dict`` or a class that implements ``collections.abc.Mapping`` assuming that
-all of the map entries can be converted to Java.  Otherwise a ``TypeError`` is
-raised.
+Example using Java HashMap with Pythonic interface:
 
-MapEntry
-========
+.. code-block:: python
+
+    from java.util import HashMap
+
+    jmap = HashMap()
+    jmap.put("key1", "value1")
+    jmap.put("key2", "value2")
+
+    print(jmap["key1"])  # Output: value1
+    del jmap["key2"]     # Remove "key2"
+    print(len(jmap))     # Output: 1
+
+Maps also support iteration over keys and values:
+
+.. code-block:: python
+
+    for key, value in jmap.items():
+        print(f"{key}: {value}")
+
+Methods that accept Java maps can implicitly convert Python dictionaries if
+all keys and values are compatible with Java types. Otherwise, a ``TypeError``
+is raised.
+
+.. _collections_map_entries:
+
+Map Entries
+-----------
+
+Java map entries unpack into key-value pairs, allowing easy iteration in Python
+loops. For example:
+
+.. code-block:: python
+
+    for key, value in jmap.items():
+        print(f"{key}: {value}")
 
-Java map entries unpack into a two value tuple, thus supporting iterating
-through key value pairs.  Thus is useful when iterating map entries in a
-for loop by pairs.
+.. _collections_sets:
 
-Set
-===
+Sets
+----
 
-All Java classes that implement ``java.util.Set`` implement delitem as well
-as the Java collection customizations.
+Java classes that implement ``java.util.Set`` behave like Python sets. They
+support operations such as item deletion, iteration, and length retrieval. For
+example:
+
+.. code-block:: python
+
+    from java.util import HashSet
+
+    jset = HashSet()
+    jset.add("apple")
+    jset.add("orange")
+
+    print(len(jset))  # Output: 2
+    jset.remove("orange")
+    print(jset)       # Output: [apple]
+
+.. _collections_enumeration:
 
 Enumeration
-===========
+-----------
 
-All Java classes that implement ``java.util.Enumeration`` inherit Python
-iterator behavior and can be used in Python for loops and list comprehensions.
+Java classes that implement ``java.util.Enumeration`` act as Python iterators.
+This allows them to be used in Python `for` loops and list comprehensions. For
+example:
 
+.. code-block:: python
 
+    from java.util import Vector
 
-Working with NumPy
-******************
+    jvector = Vector()
+    jvector.add("apple")
+    jvector.add("orange")
 
-As one of the primary focuses of JPype is working with numerical codes such as
-NumPy, there are a number of NumPy specific enhancements.  NumPy is a large
-binary package and therefore JPype cannot be compiled against NumPy directly
-without force it to be a requirement.  Instead of compiling against NumPy
-directly, JPype implements interfaces that NumPy can recognize and use.  The
-specific enhancements are the following: direct buffer transfers of primitive
-arrays and buffers, direct transfer of multi dimensional arrays, buffer backed
-NumPy arrays, and conversion of NumPy integer types to Java boxed types.
+    enumeration = jvector.elements()
+    for item in enumeration:
+        print(item)
+
+
+.. _collections_integrating_pythonic_constructs_with_java_collections:
+
+Integrating Pythonic Constructs with Java Collections
+======================================================
+
+JPype enables Python developers to interact with Java collections and streams
+while leveraging Python's idiomatic constructs, such as list comprehensions and
+generator expressions. This section explores how Pythonic constructs and Java
+methods can be used interchangeably or combined for efficient manipulation of
+data structures.
+
+
+
+.. _collections_using_pythonic_constructs_with_java_collections:
+
+Using Pythonic Constructs with Java Collections
+------------------------------------------------
+JPype enables Python developers to interact with Java collections while
+leveraging Python's idiomatic constructs, such as list comprehensions and
+generator expressions. For example:
+
+.. code-block:: python
 
-Transfers to Java
+    from java.util import ArrayList
+
+    jlist = ArrayList()
+    jlist.add("apple")
+    jlist.add("orange")
+    jlist.add("banana")
+
+    filtered = [item.upper() for item in jlist if item.startswith("a")]
+    print(filtered)  # Output: ['APPLE']
+
+Combining Pythonic constructs with Java methods allows developers to use the
+best tools for the task, whether they prefer Python's simplicity or Java's
+robustness.
+
+
+.. _collections_using_java_streams_for_functional_operations:
+
+Using Java Streams for Functional Operations
+---------------------------------------------
+
+Java's `Stream` API provides powerful functional programming constructs, such
+as `filter`, `map`, and `reduce`. JPype allows Python developers to use these
+methods with Java collections, enabling them to leverage Java's robust
+libraries.
+
+
+
+.. _collections_example_filtering_and_mapping_with_java_streams:
+
+Example: Filtering and Mapping with Java Streams
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+   from java.util.stream import Collectors
+
+   # Use Java Stream API for filtering and mapping
+   filtered = jlist.stream().filter(lambda s: s.startswith("a")).map(
+       lambda s: s.upper()).collect(Collectors.toList())
+   print(filtered)  # Output: [APPLE]
+
+Advantages of Java Streams:
+- Integration with Java's enterprise libraries.
+- Parallel processing capabilities (e.g., `.parallelStream()`).
+- Type-safe operations with Java's generics.
+
+
+
+.. _collections_comparison_pythonic_constructs_vs_java_methods:
+
+Comparison: Pythonic Constructs vs Java Methods
+------------------------------------------------
+
++---------------------------+---------------------------------------+
+| **Feature**               | **Pythonic Constructs**               |
+|                           | **(List Comprehensions)**             |
++---------------------------+---------------------------------------+
+| Syntax                    | Concise and readable                  |
++---------------------------+---------------------------------------+
+| Performance               | Python interpreter overhead           |
++---------------------------+---------------------------------------+
+| Parallel Processing       | Requires external libraries           |
+|                           | (e.g., `multiprocessing`)             |
++---------------------------+---------------------------------------+
+| Type Safety               | Dynamic typing                        |
++---------------------------+---------------------------------------+
+| Ease of Use               | Familiar to Python developers         |
++---------------------------+---------------------------------------+
+
+
+.. _collections_combining_pythonic_constructs_and_java_methods:
+
+Combining Pythonic Constructs and Java Methods
+----------------------------------------------
+
+JPype allows developers to mix Pythonic constructs and Java methods for maximum
+flexibility. For example, you can use Java streams for complex operations and
+Pythonic constructs for post-processing.
+
+
+.. _collections_example_combining_streams_and_list_comprehensions:
+
+Example: Combining Streams and List Comprehensions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+   # Use Java Stream API for filtering
+   filtered_stream = jlist.stream().filter(lambda s: s.startswith("a")).collect(
+       Collectors.toList())
+
+   # Use Pythonic list comprehension for further processing
+   final_result = [item.lower() for item in filtered_stream]
+   print(final_result)  # Output: ['apple']
+
+
+.. _collections_when_to_use_each_approach:
+
+When to Use Each Approach
+-------------------------
+
++-------------------------------------------+---------------------------+
+| **Scenario**                              | **Recommended Approach**  |
++-------------------------------------------+---------------------------+
+| Simple filtering or mapping               | Pythonic constructs       |
+|                                           | (list comprehensions)     |
++-------------------------------------------+---------------------------+
+| Complex operations (e.g., grouping,       | Java Streams              |
+| reducing)                                 |                           |
++-------------------------------------------+---------------------------+
+| Integration with Java enterprise          | Java Streams              |
+| libraries                                 |                           |
++-------------------------------------------+---------------------------+
+| Quick prototyping or debugging            | Pythonic constructs       |
++-------------------------------------------+---------------------------+
+| Parallel processing                       | Java Streams              |
+|                                           | (`parallelStream`)        |
++-------------------------------------------+---------------------------+
+
+
+
+.. _collections_best_practices_for_collection_processing:
+
+Best Practices for Collection Processing
+----------------------------------------
+
+1. **Choose the Right Tool for the Job**:
+   - Use Pythonic constructs for simplicity and readability.
+   - Use Java streams for performance-critical or enterprise applications.
+
+2. **Leverage JPype's Seamlessness**:
+   - Combine Pythonic constructs and Java methods to get the best of both worlds.
+
+3. **Optimize for Performance**:
+   - Avoid frequent back-and-forth calls between Python and Java. Cache results when possible.
+
+
+
+.. _collections_conclusion_on_collection_processing:
+
+Conclusion on Collection Processing
+-----------------------------------
+
+JPype enables Python developers to work with Java collections using both
+Pythonic constructs and Java methods. Whether you prefer Python's simplicity or
+Java's robustness, JPype provides the flexibility to choose the paradigm that
+best fits your workflow.
+
+
+.. _serialization_with_jpickler:
+
+Serialization with JPickler
+***************************
+
+JPype provides the **JPickler** utility for serializing (`pickling`) Java objects
+into Python-compatible byte streams. This is particularly useful for saving Java
+objects to disk, transferring them between systems, or debugging their state.
+
+
+
+.. _serialization_with_jpickler_why_use_jpickler:
+
+Why Use JPickler?
 =================
 
-Memory from a NumPy array can be transferred to Java in bulk.  The transfer of
-a one dimensional NumPy array to Java can either be done at initialization
-or by use of the Python slice operator.
+When working with Java objects in Python, serialization is often required for:
+
+1. **Persistence**: Saving Java objects to files for later use.
+2. **Data Exchange**: Transferring Java objects between Python applications or
+   systems.
+3. **Debugging**: Capturing the state of Java objects during execution for
+   offline analysis.
+
+However, Python's default `pickle` module does not support Java objects.
+JPickler bridges this gap by encoding Java objects into a format compatible
+with Python's serialization tools.
+
+
+
+.. _serialization_with_jpickler_how_jpickler_works:
+
+How JPickler Works
+------------------
+
+JPickler uses Java's `Serializable` interface to serialize Java objects into a
+byte stream that can be stored or transferred. It also provides a companion
+utility, **JUnpickler**, for deserializing these byte streams back into Java
+objects.
+
+
+
+.. _serialization_with_jpickler_example_1_basic_serialization:
+
+Example 1: Basic Serialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following example demonstrates how to serialize and deserialize Java
+objects using JPickler and JUnpickler.
+
+.. code-block:: python
+
+    import jpype
+    import jpype.imports
+    from jpype.pickle import JPickler, JUnpickler
+
+    # Start the JVM
+    jpype.startJVM()
+
+    # Create a Java object
+    java_list = jpype.java.util.ArrayList()
+    java_list.add("Hello")
+    java_list.add("World")
+
+    # Serialize the Java object to a file
+    with open("serialized_java_list.pkl", "wb") as f:
+        JPickler(f).dump(java_list)
+
+    print("Java object serialized successfully!")
+
+    # Deserialize the Java object from the file
+    with open("serialized_java_list.pkl", "rb") as f:
+        deserialized_list = JUnpickler(f).load()
+
+    print("Deserialized Java object:", deserialized_list)
+    # Output: [Hello, World]
+
+
+
+.. _serialization_with_jpickler_example_2_serializing_complex_java_objects:
 
-Assuming we have a single dimensional NumPy array ``npa``, we can transfer
-it with initialization using
+Example 2: Serializing Complex Java Objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+JPickler can handle any Java object that implements `java.io.Serializable`.
+Here's an example with a custom Java class:
+
+.. code-block:: java
+
+    // Save this as MySerializableClass.java and compile it
+    import java.io.Serializable;
+
+    public class MySerializableClass implements Serializable {
+        private String name;
+        private int value;
+
+        public MySerializableClass(String name, int value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return "MySerializableClass{name='" + name + "', value=" + value + "}";
+        }
+    }
 
 .. code-block:: python
 
-   ja = JArray(JInt)(npa)
+    import jpype
+    import jpype.imports
+    from jpype.pickle import JPickler, JUnpickler
+
+    # Start the JVM
+    jpype.startJVM(classpath=["."])
+
+    # Create an instance of the custom Java class
+    MySerializableClass = jpype.JClass("MySerializableClass")
+    java_object = MySerializableClass("TestObject", 42)
+
+    # Serialize the Java object to a file
+    with open("serialized_java_object.pkl", "wb") as f:
+        JPickler(f).dump(java_object)
+
+    print("Custom Java object serialized successfully!")
+
+    # Deserialize the Java object from the file
+    with open("serialized_java_object.pkl", "rb") as f:
+        deserialized_object = JUnpickler(f).load()
+
+    print("Deserialized Java object:", deserialized_object)
+    # Output: MySerializableClass{name='TestObject', value=42}
+
+
+
+.. _serialization_with_jpickler_best_practices_with_jpicker:
+
+Best Practices with JPicker
+===========================
+
+1. **Ensure Objects Are Serializable**:
+
+   - Only Java objects that implement `java.io.Serializable` can be serialized.
+     Ensure your custom Java classes implement this interface.
+
+2. **Validate Serialization**:
+
+   - Test serialization and deserialization to ensure data integrity.
+
+3. **Handle Non-Serializable Fields**:
+
+   - If a Java object contains non-serializable fields, mark them as `transient`
+     to exclude them during serialization.
+
+4. **Avoid Reference Loops**:
+
+   - Break reference loops between Python and Java objects to prevent memory
+     leaks.
+
+
+.. _serialization_with_jpickler_limitations_of_jpickler:
+
+Limitations of JPickler
+=======================
+
+1. **Non-Serializable Objects**:
+
+   - Java objects that do not implement `java.io.Serializable` cannot be
+     serialized with JPickler.
+
+2. **Cross-Version Compatibility**:
+
+   - Serialized Java objects may not be compatible across different JVM
+     versions.
+
+3. **Performance**:
+
+   - Serialization and deserialization can be resource-intensive for large or
+     complex objects.
+
+
+.. _serialization_with_jpickler_use_cases_of_jpickler:
+
+Use Cases of JPickler
+=====================
+
+1. **Persistence**:
+
+   - Save Java objects to disk for later use.
+
+   - Example: Storing application state or configuration.
+
+2. **Data Exchange**:
 
-Or we can transfer it to Java as a slice assignment.
+   - Transfer Java objects between Python applications or systems.
+
+   - Example: Network communication or distributed systems.
+
+3. **Debugging**:
+
+   - Capture the state of Java objects during execution for offline analysis.
+
+   - Example: Serialize problematic objects for inspection after a crash.
+
+
+.. _serialization_with_jpickler_conclusion_for_jpicker:
+
+Conclusion for JPicker
+======================
+
+JPickler simplifies serialization of Java objects in Python, enabling seamless
+integration between the two ecosystems. By following best practices and
+understanding its limitations, you can use JPickler effectively for
+persistence, data exchange, and debugging tasks.
+
+
+.. _working_with_numpy:
+
+Working with NumPy
+******************
+
+JPype provides seamless integration between Python's NumPy library and Java,
+enabling efficient data exchange and manipulation across both ecosystems. By
+leveraging JPype's ability to transfer arrays bidirectionally, users can combine
+NumPy's powerful numerical computing capabilities with Java's robust libraries
+for machine learning, scientific computing, and enterprise applications. Whether
+transferring data to NumPy for analysis or sending arrays to Java for processing,
+JPype ensures high performance and compatibility with minimal overhead. This
+integration is particularly useful for applications requiring large-scale
+numerical computations or interoperability between Python and Java-based systems.
+
+
+.. _working_with_numpy_transferring_arrays_between_python_and_java:
+
+Transferring Arrays Between Python and Java
+===========================================
+
+JPype supports bidirectional transfers of arrays between Python (NumPy) and Java.
+This allows seamless integration of numerical libraries with Java's ecosystem.
+
+
+
+.. _working_with_numpy_transferring_arrays_to_numpy:
+
+Transferring Arrays to NumPy
+----------------------------
+
+Java arrays can be transferred into NumPy arrays using Python's `memoryview`.
+This enables efficient bulk data transfer for rectangular arrays.
+
+**Example: Transferring a Java Array to NumPy**
 
 .. code-block:: python
 
-   ja[:] = npa
+   import jpype
+   import numpy as np
+
+   # Start the JVM
+   jpype.startJVM()
+
+   # Create a Java array
+   java_array = jpype.JDouble[:]([1.1, 2.2, 3.3])
+
+   # Transfer the Java array to NumPy
+   numpy_array = np.array(memoryview(java_array))
+
+   print(numpy_array)  # Output: [1.1 2.2 3.3]
+
+**Constraints**:
+- The Java array must be rectangular. Jagged arrays are not supported.
+- Only primitive types (e.g., `double`, `int`) are supported for direct transfer.
+
+
 
-The slice operator can transfer the entire array or just a portion of it.
+.. _working_with_numpy_transferring_arrays_to_java:
 
+Transferring Arrays to Java
+---------------------------
 
-Multidimensional transfers to Java
-==================================
+NumPy arrays can be transferred to Java using the `JArray.of` function. This maps
+the structure of a NumPy array to a Java multidimensional array.
 
-Multidimensional arrays can also be transferred at initialization time.
-To transfer a NumPy array to Java use the ``JArray.of`` function
+**Example: Transferring a NumPy Array to Java**
 
 .. code-block:: python
 
-    z = np.zeros((5,10,20))
-    ja = JArray.of(z)
+   import jpype
+   import numpy as np
 
-Transfers to NumPy
-==================
+   # Start the JVM
+   jpype.startJVM()
+
+   # Create a NumPy array
+   numpy_array = np.zeros((5, 10, 20))  # 5x10x20 array filled with zeros
+
+   # Transfer the array to Java
+   java_array = jpype.JArray.of(numpy_array)
+
+   print(java_array[0][0][0])  # Output: 0.0
+
+**Constraints**:
+- The NumPy array must be rectangular. Jagged arrays are not supported.
+- Data types must be compatible with Java primitives (e.g., `np.float64` → `double`).
+
+
+
+.. _working_with_numpy_requirements_and_constraints:
+
+Requirements and Constraints
+----------------------------
 
-Java arrays can be in two forms.  Java multidimensional arrays are not
-contiguous in memory.  If all of the arrays in each dimension are the same,
-then the array is rectangular.  If the size of the arrays within any dimension
-differ, then the array is jagged.  Jagged arrays are an array of arrays rather
-than a rectangular block of memory.
-
-NumPy arrays only hold rectangular arrays as multidimensional arrays of
-primitives.  All other arrangements are a stored as a single dimensional array
-of objects.  JPype can automatically transfer a rectangular array to NumPy as a
-bulk transfer.  To do so JPype supports a ``memoryview`` on rectangular arrays.
-Whenever a memoryview is called on a multidimensional array of primitives,
-JPype verifies that it is rectangular and creates a buffer.  If it is jagged,
-a ``BufferError`` is raised.  When a Java array is used as an argument to
-initialize a NumPy array, it creates a ``memoryview`` so that all of the memory
-can be transferred in bulk.
+1. **Rectangular Arrays**:
 
+   - Both NumPy and Java arrays must be rectangular for direct transfer.
 
-Buffer backed NumPy arrays
+2. **Data Type Compatibility**:
+
+   - NumPy types must map to Java primitives (e.g., `np.int32` → `int`).
+
+3. **Error Handling**:
+
+   - Jagged arrays or incompatible types will raise a `TypeError`.
+
+
+
+.. _working_with_numpy_best_practices_with_numpy:
+
+Best Practices with NumPy
+-------------------------
+
+1. **Validate Array Structure**:
+   - Ensure arrays are rectangular before transferring.
+
+2. **Optimize Data Types**:
+   - Use NumPy types that map directly to Java primitives for efficiency.
+
+3. **Monitor Memory Usage**:
+   - Large arrays can consume significant memory. Monitor resources carefully.
+
+
+
+.. _working_with_numpy_summary_of_numpy:
+
+Summary of NumPy
+----------------
+
+JPype provides efficient bidirectional array transfers between Python and Java.
+By following the outlined constraints and best practices, users can achieve
+seamless integration for numerical and scientific applications.
+
+
+.. _working_with_numpy_buffer_backed_numpy_arrays:
+
+Buffer Backed NumPy Arrays
 ==========================
 
-Java direct buffers provide access between foreign memory and Java.
-This access bypasses the JNI layer entirely, permitting Java and Python to
-operate on a memory space with native speed.  Java direct buffers are not under
-the control of the garbage collector and thus can result in memory leaks and
-memory exhaustion if not used carefully.  This is used with Java libraries that
-support direct buffers.  Direct buffers are part of the Java ``nio`` package
-and thus functionality for buffers is in ``jpype.nio``.
+Java direct buffers provide a mechanism for shared memory between Java and
+Python, enabling high-speed data exchange by bypassing the JNI layer. These
+buffers are particularly useful for applications requiring efficient handling
+of large datasets, such as scientific computing or memory-mapped files.
+
+Direct buffers are part of the Java ``nio`` package and can be accessed using
+the ``jpype.nio`` module. NumPy arrays can be backed by Java direct buffers,
+allowing Python and Java to operate on the same memory space. However, direct
+buffers are not managed by the garbage collector, so improper use may lead to
+memory leaks or crashes.
+
 
-To create a buffer backed NumPy array, the user must either create
-a direct memory buffer using the Java direct buffer API or create a
-Python ``bytearray`` and apply ``jpype.nio.convertToByteBuffer`` to map this
-memory into Java space.  NumPy can then convert the direct buffer into
-an array using ``asarray``.
+.. _working_with_numpy_creating_buffer_backed_arrays:
 
-To originate a direct buffer from Java, use:
+Creating Buffer Backed Arrays
+-----------------------------
+
+To create a buffer-backed NumPy array, you can either originate the buffer in
+Java or Python. The following examples demonstrate both approaches:
+
+**Example 1: Creating a Buffer in Java**
 
 .. code-block:: python
 
-  jb = java.nio.ByteBuffer.allocateDirect(80)
-  db = jb.asDoubleBuffer()
-  a = np.asarray(db)
+   import jpype
+   import numpy as np
+
+   # Start the JVM
+   jpype.startJVM()
+
+   # Allocate a direct buffer in Java
+   jb = java.nio.ByteBuffer.allocateDirect(80)  # Allocates 80 bytes
+   db = jb.asDoubleBuffer()                     # Converts to a double buffer
+
+   # Convert the buffer to a NumPy array
+   np_array = np.asarray(db)                    # NumPy array backed by Java buffer
+   print(np_array)
 
-To origate a direct buffer from Python, use:
+**Example 2: Creating a Buffer in Python**
 
 .. code-block:: python
 
-   bb = bytearray(80)
-   jb = jpype.nio.convertToDirectBuffer(bb)
-   db = jb.asDoubleBuffer()
-   a = np.asarray(db)
+   import jpype
+   import numpy as np
+
+   # Start the JVM
+   jpype.startJVM()
+
+   # Create a Python bytearray
+   py_buffer = bytearray(80)                    # Allocates 80 bytes
+   jb = jpype.nio.convertToDirectBuffer(py_buffer)  # Maps the bytearray to Java
+   db = jb.asDoubleBuffer()                     # Converts to a double buffer
+
+   # Convert the buffer to a NumPy array
+   np_array = np.asarray(db)                    # NumPy array backed by Python buffer
+   print(np_array)
+
+
+.. _working_with_numpy_important_considerations_for_buffer_backed_arrays:
+
+Important Considerations for Buffer Backed Arrays
+-------------------------------------------------
+
+1. **Buffer Lifetime**:
+
+   - Python and NumPy cannot detect when a Java buffer becomes invalid. Once the
+     JVM is shut down, all buffers originating from Java become invalid, and any
+     access to them may result in crashes.
+
+   - To avoid this, create buffers in Python and pass them to Java, ensuring
+     Python retains control over the memory.
+
+2. **JVM Shutdown**:
+
+   - If buffers are created in Java, consider using ``java.lang.Runtime.exit``
+     to terminate both the Java and Python processes simultaneously. This
+     prevents accidental access to dangling buffers.
+
+3. **Applications**:
+
+   - Buffer-backed memory is not limited to NumPy. It can be used for shared
+     memory between processes, memory-mapped files, or any application requiring
+     efficient data exchange.
+
+
+.. _working_with_numpy_summary_of_buffer_backed_arrays:
+
+Summary of Buffer Backed Arrays
+-------------------------------
+
+Buffer-backed NumPy arrays provide a powerful mechanism for high-speed data
+exchange between Python and Java. However, users must carefully manage buffer
+lifetimes and ensure proper handling during JVM shutdown to avoid crashes or
+memory leaks.
 
 
-Buffer backed arrays have one downside.  Python and by extension NumPy have
-no way to tell when a buffer becomes invalid.  Once the JVM is shutdown,
-all buffers become invalid and any access to NumPy arrays backed by Java
-risk crashing.  To avoid this fate, either create the memory for the buffer from
-within Python and pass it to Java.  Or use the Java ``java.lang.Runtime.exit``
-which will terminate both the Java and Python process without leaving any
-opportunity to access a dangling buffer.
+.. _working_with_numpy_numpy_primitives:
+
+Both Python and Java have a notion of readonly memory (bytes vs bytearray in
+Python). ConvertToDirectBuffer will honor the the writability of the passed
+object and return a readonly Java ByteBuffer if the source object is readonly.
 
-Buffer backed memory is not limited to use with NumPy.  Buffer transfers are
-supported to provide shared memory between processes or memory mapped files.
-Anything that can be mapped to an address with as a flat array of primitives
-with machine native byte ordering can be mapped into Java.
 
 NumPy Primitives
 ================
 
-When converting a Python type to a boxed Java type, there is the difficulty
-that Java has no way to know the size of a Python numerical value.  But when
-converting NumPy numerical types, this is not an issue.  The following
-conversions apply to NumPy primitive types.
-
-=========== =======================
-Numpy Type  Java Boxed Type
-=========== =======================
-np.int8     java.lang.Byte
-np.int16    java.lang.Short
-np.int32    java.lang.Integer
-np.int64    java.lang.Long
-np.float32  java.lang.Float
-np.float64  java.lang.Double
-=========== =======================
+JPype provides seamless integration with NumPy, allowing efficient data
+transfers between Python and Java. NumPy arrays can be mapped to Java boxed
+types or primitive arrays. However, certain types, such as `np.float16`, are
+converted to compatible Java types during transfer.
 
-Further, these NumPy types obey Java type conversion rules so that they
-act as the equivalent of the Java primitive type.
+.. _working_with_numpy_supported_numpy_types:
 
+Supported NumPy Types
+---------------------
 
-.. _Proxies:
+The following table summarizes how NumPy types are mapped to Java boxed types
+and primitive arrays:
+
+=================  ============================
+NumPy Type         Java Type (Boxed/Primitive)
+=================  ============================
+np.int8            java.lang.Byte / byte[]
+np.int16           java.lang.Short / short[]
+np.int32           java.lang.Integer / int[]
+np.int64           java.lang.Long / long[]
+np.float16         java.lang.Float / float[] (*)
+np.float32         java.lang.Float / float[]
+np.float64         java.lang.Double / double[]
+=================  ============================
+
+(*) `np.float16` will be automatically converted to `float32` (`java.lang.Float`
+or `float[]`) during the transfer to Java.
+
+.. note::
+   `np.float16` can be transferred to Java, but it will be automatically
+   converted to `float32` (`java.lang.Float` for boxed types or `float[]` for
+   primitive arrays) on the Java side. This is because Java does not natively
+   support `float16`. If precise handling of `float16` is required, consider
+   converting the data to `float32` or `float64` explicitly in Python before
+   transferring it.
+
+.. _working_with_numpy_examples:
+
+Examples
+--------
+
+The following examples demonstrate how to transfer `np.float16` data to Java
+as boxed types or primitive arrays.
+
+.. _working_with_numpy_example_1_transferring_float16_to_a_boxed_type:
+
+Example 1: Transferring `float16` to a Boxed Type
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+   import jpype
+   import numpy as np
+
+   # Start the JVM
+   jpype.startJVM()
+
+   # Create a NumPy array with float16 data
+   float16_array = np.array([1.1, 2.2, 3.3], dtype=np.float16)
+
+   # Transfer the array to a Java boxed type (java.util.ArrayList)
+   java_list = jpype.java.util.ArrayList()
+   for value in float16_array:
+       java_list.add(jpype.JFloat(value))  # Automatically converted to float32
+
+   print(java_list)  # Output: [1.1, 2.2, 3.3] (as float32)
+
+.. _working_with_numpy_example_2_transferring_float16_to_a_primitive_array:
+
+Example 2: Transferring `float16` to a Primitive Array
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
 
-Implementing Java interfaces
-****************************
+   import jpype
+   import numpy as np
+
+   # Start the JVM
+   jpype.startJVM()
+
+   # Create a NumPy array with float16 data
+   float16_array = np.array([1.1, 2.2, 3.3], dtype=np.float16)
+
+   # Transfer the array to a Java primitive array
+   java_primitive_array = jpype.JArray(jpype.JFloat)(float16_array)  # Converted
+                                                                     # to float32[]
+
+   print(java_primitive_array)  # Output: [1.1, 2.2, 3.3] (as float32[])
+
+.. _working_with_numpy_summary_of_numpy_automatic_converstions:
+
+Summary of NumPy Automatic Converstions
+---------------------------------------
+
+JPype supports transferring NumPy arrays to Java, with automatic conversions
+for certain types. While `np.float16` can be transferred, it is converted to
+`float32` on the Java side for compatibility. Users should be aware of this
+behavior and plan accordingly when working with `float16` data.
+
+
+.. _Proxies:
 
-Proxies in Java are foreign elements that pretend to implement a Java
-interface. We use this proxy API to allow Python to implement any Java
-interface.  Of course, a proxy is not the same as subclassing Java classes in
-Python.  However, most Java APIs are built so that sub-classing is not required.
-Good examples of this are AWT and SWING. Except for relatively advanced
-features, it is possible to build complete UIs without creating a single
-subclass.
+Calling Python Code from Java
+*****************************
+Proxies in JPype enable Python objects to implement Java interfaces directly,
+allowing seamless interaction between Python and Java. These proxies are
+specifically designed to implement Java interfaces, acting as wrapper classes
+that disguise the Python nature of the object in a Java type-safe manner.
+
+In Java, proxies are foreign elements that pretend to implement a Java
+interface. JPype leverages this proxy API to allow Python code to implement any
+Java interface. While proxies allow Python objects to fulfill the contract of a
+Java interface, they are not equivalent to subclassing Java classes in Python.
+
+Fortunately, many Java APIs are designed to minimize the need for subclassing.
+For example, frameworks like AWT and SWING allow developers to create complete
+user interfaces without requiring a single subclass. Subclassing is typically
+reserved for more advanced features or specialized use cases.
 
 For those cases where sub-classing is absolutely necessary (i.e. using Java's
-SAXP classes), it is necessaryy to create an interface and a simple
+SAXP classes), it is necessary to create an interface and a simple
 subclass in Java that delegates the calls to that interface.  The interface
 can then be implemented in Python using a proxy.
 
-There are two APIs for supporting of Java proxies.  The new high-level
+There are three APIs for supporting of Java proxies. The direct method is to
+pass a Python function, method, bound method, or lambda to any Java method
+that accepts a FunctionInterface or other SAM.  If more complex behaviors
+need to be exchanged Python can implement a Java interface. Implementation of an
 interface uses decorators which features strong error checking and easy
 notation.  The older low-level interface allows any Python object or dictionary
 to act as a proxy even if it does not provide the required methods for the
 interface.
 
+.. _calling_python_code_from_java_passing_python_callables_to_java_functional_interfaces:
+
+Passing Python Callables to Java Functional Interfaces
+=======================================================
+
+JPype supports passing Python functions, methods, and bound methods directly to
+Java methods or fields that implement `FunctionalInterface`. This allows Python
+code to seamlessly integrate with Java's functional programming constructs,
+such as lambdas and method references, without requiring a proxy or explicit
+implementation of the interface.
+
+### Supported Use Cases
+
+This feature works with any Java method or field that expects a
+`FunctionalInterface`. Common examples include:
+- Java Streams (`java.util.stream`)
+- Java Executors (`java.util.concurrent`)
+- Custom functional interfaces defined in Java code
+
+### Example: Passing a Python Function to a Java Method
+
+Suppose you have a Java method that expects a `java.util.function.Function`:
+
+.. code-block:: java
+
+    import java.util.function.Function;
+
+    public class Example {
+        public static String applyFunction(Function<String, String> func, String input) {
+         return func.apply(input);
+        }
+    }
+
+You can pass a Python function directly to this method:
+
+.. code-block:: python
+
+    import jpype import jpype.imports
+    jpype.startJVM()
+
+    from java.util.function import Function from Example import Example
+
+    # Define a Python function
+    def to_uppercase(s):
+        return s.upper()
+
+    # Pass the Python function to the Java method
+    result = Example.applyFunction(to_uppercase, "hello")
+    print(result)  #Output: HELLO
+
+### Example: Using a Lambda Expression
+
+Python lambdas can also be passed to Java methods:
+
+.. code-block:: python
+
+    # Pass a lambda expression
+    result = Example.applyFunction(lambda s: s[::-1], "hello")
+    print(result)  #Output: olleh
+
+### Example: Using a Bound Method
+
+Bound methods of Python objects can be passed as well:
+
+.. code-block:: python
+
+    class StringManipulator:
+        def reverse(self, s):
+            return s[::-1]
+
+    manipulator = StringManipulator()
+    result = Example.applyFunction(manipulator.reverse, "hello")
+    print(result)  # Output: olleh
+
+### Notes and Best Practices
+
+1. **Performance**: While using Python callables is convenient, it may not be
+as performant as implementing a full Java proxy for high-frequency calls. Use
+proxies for performance-critical applications.
+
+2. **Error Handling**: If an exception occurs within the Python callable, it
+will be wrapped in a `RuntimeException` when passed back to Java.
+
+3. **Type Matching**: Ensure that the Python callable returns a type compatible
+with the expected Java return type. Implicit conversions will be applied where
+possible.
+
+By leveraging this feature, you can simplify integration between Python and
+Java, especially when working with Java's functional programming APIs.
+
+
 .. _@JImplements:
 
 Implements
@@ -2353,6 +4652,8 @@ because the actually proxy is a temporar
 thus rather than returning a useless object, JPype unpacks the proxy
 to its original Python object.
 
+.. _calling_python_code_from_java_proxy_method_overloading:
+
 Proxy Method Overloading
 ------------------------
 
@@ -2378,6 +4679,8 @@ dispatch:
        def callMethod2(self, jstr):
             # ...
 
+.. _calling_python_code_from_java_multiple_interfaces:
+
 Multiple interfaces
 -------------------
 
@@ -2386,6 +4689,8 @@ have conflicting methods.  To implement
 list as the argument to the JImplements decorator.  Each interface must be
 implemented completely.
 
+.. _calling_python_code_from_java_deferred_realization:
+
 Deferred realization
 --------------------
 
@@ -2408,6 +4713,8 @@ Other than the raising of exceptions on
 deferring a proxy class. The implementation is checked once on the first
 usage and cached for the remaining life of the class.
 
+.. _calling_python_code_from_java_proxy_factory:
+
 Proxy Factory
 =============
 
@@ -2417,6 +4724,8 @@ In this API you manually create a JProxy
 be a Python object instance or a Python dictionary.  Low-level proxies use the
 JProxy API.
 
+.. _calling_python_code_from_java_jproxy:
+
 JProxy
 ------
 
@@ -2476,6 +4785,122 @@ or you can use a dictionary.
     d = { 'testMethod': _testMethod, 'testMethod2': _testMethod2, }
     proxy = JProxy("ITestInterface2", dict=d)
 
+.. _calling_python_code_from_java_wrapping_a_python_instance_with_new_behaviors_for_java:
+
+Wrapping a Python instance with new behaviors for Java
+======================================================
+
+JPype allows a JProxy to implement Java interfaces using a combination of a
+dictionary (dict) and an object instance (inst). This feature enables arbitrary
+Python objects to dynamically define methods via a dictionary while also
+providing methods from the object's class. The combined approach is
+particularly useful for cases where some methods are predefined in a Python
+class and others need to be dynamically added or overridden.  This is useful when
+the names and functionality of a Python object need to be made to conform to
+Java's expected behaviors.
+
+.. _calling_python_code_from_java_syntax:
+
+Syntax
+------
+The JProxy factory supports both dict and inst as keyword arguments. When both are provided:
+
+ * Methods in the dictionary take precedence.
+ * The inst object is passed as the self argument to methods defined in the dictionary.
+ * If a method is not found in the dictionary, JPype will fall back to the default method implementation in Java.
+
+.. code-block:: python
+
+    JProxy(interface, dict=my_dict, inst=my_instance)
+
+Example: Combining dict and inst
+Suppose you have a Java interface:
+
+.. code-block:: java
+
+    public interface MyInterface {
+        String method1();
+        String method2();
+    }
+
+You can implement this interface using both a dictionary and an object instance:
+
+.. code-block:: python
+
+    public interface MyInterface {
+        String method1();
+        default String method2() {
+            return "hello";
+        }
+    }
+    You can implement this interface using both a dictionary and an object instance:
+
+    .. code-block:: python
+
+    from jpype import JProxy
+
+    class MyClass:
+        def __init__(self, name):
+            self.name = name
+
+    # Define a dictionary with methods
+    my_dict = {
+        "method1": lambda self: f"Hello, {self.name} from method1"
+    }
+
+    # Create an instance of the class
+    my_instance = MyClass("Alice")
+
+    # Combine the dictionary and instance in a JProxy
+    proxy = JProxy("MyInterface", dict=my_dict, inst=my_instance)
+
+    # Use the proxy in Java
+    print(proxy.method1())  # Output: Hello, Alice from method1
+    print(proxy.method2())  # Falls back to Java's default method implementation
+
+
+.. _calling_python_code_from_java_notes_and_best_practices:
+
+Notes and Best Practices
+------------------------
+Method Resolution:
+
+ * Methods in the dictionary take precedence over methods in the instance.
+
+ * If a method is not found in the dictionary, JPype will attempt to resolve it in the
+   instance.
+
+Error Handling:
+
+ * If neither the dictionary nor the instance provides the required method, a NotImplementedError will be raised.
+
+Flexibility:
+
+This approach allows dynamic addition or overriding of methods via the dictionary while retaining the benefits of object-oriented programming with the instance.
+
+Example: Dynamic Overrides
+You can dynamically override methods in the instance using the dictionary:
+
+.. code-block:: python
+
+    class MyClass:
+        def __init__(self, name):
+            self.name = name
+
+    # Define a dictionary to override methods
+    my_dict = {
+        "method1": lambda self: f"Overridden method1 for {self.name}"
+    }
+
+    my_instance = MyClass("Bob")
+
+    proxy = JProxy("MyInterface", dict=my_dict, inst=my_instance)
+
+    print(proxy.method1())  # Output: Overridden method1 for Bob
+    print(proxy.method2())  # Falls back to Java's default method implementation
+
+
+.. _calling_python_code_from_java_proxying_python_objects:
 
 Proxying Python objects
 =======================
@@ -2505,6 +4930,8 @@ access to Python methods from within Jav
 points to back to Python objects.
 
 
+.. _calling_python_code_from_java_reference_loops:
+
 Reference Loops
 ===============
 
@@ -2535,6 +4962,8 @@ it remains in the Java map, it will main
 it is removed, it is free to switch identities every time it is garbage
 collected.
 
+.. _awtswing:
+
 AWT/Swing
 *********
 
@@ -2574,6 +5003,8 @@ launches it from within Python.
 
 
 
+.. _concurrent_processing:
+
 Concurrent Processing
 *********************
 
@@ -2581,6 +5012,8 @@ This chapter covers the topic of threadi
 Much of this material depends on the use of Proxies_ covered in the prior
 chapter.
 
+.. _concurrent_processing_threading:
+
 Threading
 =========
 
@@ -2598,6 +5031,8 @@ to get a string name from from a class m
 the GIL is released it is another opportunity for Python to switch to a different
 cooperative thread.
 
+.. _concurrent_processing_threading_python_threads:
+
 Python Threads
 --------------
 
@@ -2622,12 +5057,14 @@ way that a thread would not be attached
 The downside of automatic attachment is that each attachment allocates a
 small amount of resources in the JVM.  For applications that spawn frequent
 dynamically allocated threads, these threads will need to be detached prior
-to completing the thread with ``java.lang.Thread.detach()``.  When 
+to completing the thread with ``java.lang.Thread.detach()``.  When
 implementing dynamic threading, one can detach the thread
 whenever Java is no longer needed.  The thread will automatically reattach if
 Java is needed again.  There is a performance penalty each time a thread is
 attached and detached.
 
+.. _concurrent_processing_threading_java_threads:
+
 Java Threads
 ------------
 
@@ -2636,6 +5073,8 @@ To use Java threads, create a Java proxy
 mechanism to be executed.  Each time that Java threads transfer control
 back to Python, the GIL is reacquired.
 
+.. _concurrent_processing_threading_other_threads:
+
 Other Threads
 -------------
 
@@ -2645,6 +5084,133 @@ level threads will work without problem.
 will appear as a single thread to Java, so special care will have to be taken
 for synchronization.
 
+
+.. _concurrent_processing_customizing_javalangthread:
+
+Customizing java.lang.Thread
+============================
+
+.. _concurrent_processing_overview:
+
+Overview
+--------
+
+JPype automatically attaches Python threads to the JVM when they interact with
+Java resources. Threads are attached as daemon threads to ensure that they do
+not block JVM shutdown. While this behavior simplifies integration, it can lead
+to resource leaks in thread-heavy applications if threads are not properly
+detached when they terminate.
+
+To address this, JPype customizes `java.lang.Thread` with additional methods
+for managing thread attachment and detachment. These methods allow developers
+to explicitly detach threads, freeing resources in the JVM and preventing leaks.
+
+.. _concurrent_processing_customized_methods:
+
+Customized Methods
+------------------
+
+The following methods are added to `java.lang.Thread` by JPype:
+
+.. method:: java.lang.Thread.attach()
+
+   Attaches the current thread to the JVM as a user thread.
+
+   User threads prevent the JVM from shutting down until they are terminated or
+   detached. This method can be used to convert a daemon thread to a user
+   thread.
+
+   **Raises**:
+     - `RuntimeError`: If the JVM is not running.
+
+.. method:: java.lang.Thread.attachAsDaemon()
+
+   Attaches the current thread to the JVM as a daemon thread.
+
+   Daemon threads act as background tasks and do not prevent the JVM from
+   shutting down. JPype automatically attaches threads as daemon threads when
+   they interact with Java resources. Use this method to explicitly attach a
+   thread as a daemon.
+
+   **Raises**:
+     - `RuntimeError`: If the JVM is not running.
+
+.. method:: java.lang.Thread.detach()
+
+   Detaches the current thread from the JVM.
+
+   This method frees the associated resources in the JVM for the current thread.
+   It is particularly important for thread-heavy applications to prevent leaks.
+   Detaching a thread does not interfere with its ability to reattach later.
+
+   **Notes**:
+     - This method cannot fail and is safe to call even if the JVM is not
+       running.
+     - There is no harm in calling this method multiple times or detaching
+       threads early.
+
+.. _concurrent_processing_examples_with_java_thread:
+
+Examples with Java Thread
+-------------------------
+
+Here are examples of how to use the customized methods for `java.lang.Thread`:
+
+.. code-block:: python
+
+   import jpype
+   import jpype.imports
+
+   jpype.startJVM()
+
+   # Attach the thread as a user thread
+   java.lang.Thread.attach()
+   print("Thread attached as a user thread.")
+
+   # Perform Java operations here...
+
+   # Detach the thread after completing Java operations
+   java.lang.Thread.detach()
+   print("Thread detached from the JVM.")
+
+   # Attach the thread as a daemon thread
+   java.lang.Thread.attachAsDaemon()
+   print("Thread attached as a daemon thread.")
+
+.. _concurrent_processing_best_practices_for_java_thread:
+
+Best Practices for Java Thread
+------------------------------
+
+- **Detach Threads When They End**: For thread-heavy applications, ensure that
+  Python threads detach themselves from the JVM before they terminate. This
+  prevents resource leaks and ensures efficient memory usage.
+
+- **Avoid Excessive Attachments**: While JPype automatically attaches threads,
+  excessive thread creation without proper detachment can lead to resource
+  exhaustion in the JVM.
+
+- **Detach Early**: Detaching threads early, after completing all Java
+  operations, is safe and does not interfere with reattachment later. This is
+  especially important for applications that spawn many short-lived threads.
+
+- **Monitor Resource Usage**: Regularly monitor JVM memory usage in
+  thread-heavy applications to identify potential leaks caused by lingering
+  thread attachments.
+
+.. _concurrent_processing_summary_of_java_thread:
+
+Summary of Java Thread
+----------------------
+
+JPype customizes `java.lang.Thread` to provide additional methods for managing
+thread attachment and detachment to/from the JVM. While JPype automatically
+attaches threads as daemon threads, it is crucial to detach threads explicitly
+in thread-heavy applications to prevent resource leaks. By following best
+practices, developers can ensure efficient memory usage and smooth integration
+between Python and Java.
+
+
 .. _synchronized:
 
 Synchronization
@@ -2688,6 +5254,9 @@ For synchronization that does not have t
 Python's support directly rather than Java's synchronization to avoid
 unnecessary overhead.
 
+
+.. _concurrent_processing_threading_examples:
+
 Threading examples
 ==================
 
@@ -2696,6 +5265,8 @@ code to extend many of the benefits of J
 has a global lock, the performance of Java threads while using Python is not
 as good as native Java code.
 
+.. _concurrent_processing_limiting_execution_time:
+
 Limiting execution time
 -----------------------
 
@@ -2728,6 +5299,8 @@ results.  For example:
 Here we have limited the execution time of a Java call.
 
 
+.. _concurrent_processing_multiprocessing:
+
 Multiprocessing
 ===============
 
@@ -2761,21 +5334,279 @@ a Java object.  This can cause deadlocks
 wrapping any Queue is required.
 
 
+.. _managing_crossplatform_gui_environments:
+
+Managing Cross-Platform GUI Environments
+****************************************
+
+JPype provides utility functions, `setupGuiEnvironment` and
+`shutdownGuiEnvironment`, to manage GUI environments across platforms,
+ensuring compatibility with macOS, Linux, and Windows. These functions are
+particularly useful for Swing and JavaFX-based applications, where macOS
+imposes specific requirements for GUI event loops. Even on Linux and Windows,
+using `setupGuiEnvironment` ensures consistent behavior and avoids potential
+issues with threading and event loops.
+
+.. _managing_crossplatform_gui_environments_setupguienvironmentcb:
+
+setupGuiEnvironment(cb)
+=======================
+
+**Description**:
+
+`setupGuiEnvironment` ensures that GUI applications can run correctly across
+all platforms. It is specifically designed to address macOS's requirement for
+the main thread to run the event loop, but it is also recommended for Swing
+and JavaFX applications on Linux and Windows to maintain cross-platform
+compatibility and proper threading behavior.
+
+**Parameters**:
+
+- **cb**: A callback function that initializes and launches the GUI application.
+
+**Behavior**:
+
+- **macOS**:
+  - Creates a Java thread using a `Runnable` proxy.
+  - Starts the macOS event loop using `PyObjCTools.AppHelper.runConsoleEventLoop()`.
+
+- **Other Platforms (Linux, Windows)**:
+  - Executes the callback function directly.
+
+**Why Use This Function for Swing and JavaFX Applications?**
+
+Swing and JavaFX applications often rely on proper threading and event loop
+management to function correctly. While macOS has strict requirements for
+running the event loop on the main thread, using `setupGuiEnvironment` on
+Linux and Windows ensures consistent behavior and avoids potential threading
+issues, such as race conditions or improper GUI updates.
+
+**Example**:
+
+.. code-block:: python
+
+    from jpype import setupGuiEnvironment
+    from javafx.application import Platform
+
+    def say_hello_later():
+        """Test function for scheduling a task on the JavaFX Application Thread."""
+        print("Hello from JavaFX!")
+
+    def launch_gui():
+        """Launch the GUI application."""
+        # Example: Schedule a task on the JavaFX Application Thread
+        Platform.runLater(say_hello_later)
+        print("GUI launched")
+
+    # Use setupGuiEnvironment to ensure cross-platform compatibility
+    setupGuiEnvironment(launch_gui)
+
+.. _managing_crossplatform_gui_environments_reestablishing_an_interactive_shell_on_another_thread:
+
+Reestablishing an Interactive Shell on Another Thread
+=====================================================
+
+When using `setupGuiEnvironment`, the main thread may be occupied by the GUI
+event loop (particularly on macOS). To allow interactive debugging in Python,
+you can launch an interactive shell (e.g., IPython) on a separate thread.
+
+**Steps**:
+
+1. Use `setupGuiEnvironment` to start the GUI application.
+2. Launch an interactive shell on a separate thread using Python's `threading`
+   module.
+
+**Example**:
+
+.. code-block:: python
+
+    import threading
+    import IPython
+
+    def launch_interactive_shell():
+        """Launch an interactive shell on a separate thread."""
+        IPython.embed()
+
+    # Start the interactive shell on another thread
+    thread = threading.Thread(target=launch_interactive_shell)
+    thread.start()
+
+By combining this approach with `setupGuiEnvironment`, you can interact with
+the Python environment while the GUI application is running.
+
+.. _managing_crossplatform_gui_environments_shutdownguienvironment:
+
+shutdownGuiEnvironment()
+========================
+
+**Description**:
+
+`shutdownGuiEnvironment` is used to cleanly terminate the macOS event loop. On
+other platforms, it performs no action.
+
+**Behavior**:
+
+- **macOS**:
+  - Stops the macOS event loop using `PyObjCTools.AppHelper.stopEventLoop()`.
+
+- **Other Platforms (Linux, Windows)**:
+  - No action is taken.
+
+**Example**:
+
+.. code-block:: python
+
+    from jpype import shutdownGuiEnvironment
+
+    # Shutdown the GUI environment (macOS-specific)
+    shutdownGuiEnvironment()
+
+.. _managing_crossplatform_gui_environments_best_practices_on_guis:
+
+Best Practices on GUIs
+--------------------------
+
+- **Use `setupGuiEnvironment` for All Platforms**:
+  Even though macOS has specific requirements, using `setupGuiEnvironment`
+  ensures consistent behavior across all platforms, particularly for Swing
+  and JavaFX applications.
+
+- **Thread Safety**:
+  Always schedule GUI updates using JavaFX's `Platform.runLater` or Swing's
+  `SwingUtilities.invokeLater` to ensure they occur on the appropriate thread.
+
+- **Interactive Debugging**:
+  Launch an interactive shell on a separate thread for debugging while the GUI
+  application is running.
+
+- **Exception Handling**:
+  Wrap callback functions in `try-except` blocks to prevent unhandled
+  exceptions from disrupting the GUI.
+
+- **Cross-Platform Testing**:
+  Test the application on macOS, Linux, and Windows to ensure compatibility.
+
+.. _managing_crossplatform_gui_environments_summary_of_guis:
+
+Summary of GUIs
+===============
+
+The `setupGuiEnvironment` function is a critical tool for managing GUI
+environments across platforms, particularly for Swing and JavaFX-based
+applications. It ensures compatibility with macOS's event loop requirements
+while maintaining simplicity on other platforms. Combined with the ability to
+launch an interactive shell on a separate thread, this approach provides a
+robust solution for developing and debugging GUI applications in Python.
+
+
+.. _miscellaneous_topics:
 
 Miscellaneous topics
 ********************
 
 This chapter contains all the stuff that did not fit nicely into the narrative
-about JPype.  Topics include code completion, performance, debugging Java
-within JPype, debugging JNI and other JPype failures, how caller sensitive
+about JPype.  Topics include database interfacing, code completion, performance,
+debugging Java within JPype, debugging JNI and other JPype failures, how caller sensitive
 methods are dealt with, and finally limitations of JPype.
 
+
+.. _miscellaneous_topics_database_access_with_jpypedbapi2:
+
+Database Access with `jpype.dbapi2`
+===================================
+
+JPype provides the `jpype.dbapi2` module, which allows Python applications to
+interact with Java-based database drivers using the Python Database API
+Specification (PEP 249). This module bridges Python and Java, enabling seamless
+access to databases that lack native Python drivers but provide JDBC drivers.
+
+.. _miscellaneous_topics_key_features_of_dbapi2:
+
+Key Features of dbapi2
+----------------------
+- **PEP 249 Compliance**: Implements the Python Database API Specification for
+  standardized database interaction.
+- **JDBC Integration**: Uses Java's JDBC (Java Database Connectivity) to connect
+  to databases.
+- **Cross-Platform**: Supports any database with a JDBC driver, including
+  enterprise databases like Oracle, DB2, and SQL Server.
+
+.. _miscellaneous_topics_prerequisites_for_dbapi2:
+
+Prerequisites for dbapi2
+------------------------
+- Ensure the JVM is started with the appropriate classpath for the JDBC driver.
+- Obtain the JDBC driver for the target database and include its path in the
+  `classpath`.
+
+.. _miscellaneous_topics_example_usage_of_dbapi2:
+
+Example Usage of dbapi2
+-----------------------
+.. code-block:: python
+
+    import jpype
+    import jpype.dbapi2 as dbapi2
+
+    # Start the JVM with the JDBC driver
+    jpype.startJVM(classpath=["path/to/jdbc/driver.jar"])
+
+    # Connect to the database
+    connection = dbapi2.connect(
+        "jdbc:database_url",  # JDBC URL for the database
+        {"user": "username", "password": "password"}  # Connection properties
+    )
+
+    # Create a cursor and execute a query
+    cursor = connection.cursor()
+    cursor.execute("SELECT * FROM my_table")
+
+    # Fetch and process results
+    results = cursor.fetchall()
+    for row in results:
+        print(row)
+
+    # Close the cursor and connection
+    cursor.close()
+    connection.close()
+
+    # Shut down the JVM
+    jpype.shutdownJVM()
+
+
+.. _miscellaneous_topics_benefits_of_dbapi2:
+
+Benefits of dbapi2
+------------------
+- Access databases that lack native Python drivers but provide JDBC drivers.
+- Leverage advanced features of Java-based database drivers.
+- Maintain compatibility with Python's standard database API.
+
+
+.. _miscellaneous_topics_limitations_of_dbapi2:
+
+Limitations of dbapi2
+----------------------
+- Requires a running JVM, which may introduce overhead compared to native Python
+  database drivers.
+- Performance may be slightly impacted due to Python-Java interaction.
+
+.. _miscellaneous_topics_use_cases_of_dbapi2:
+
+Use Cases of dbapi2
+-------------------
+- Connecting to enterprise databases like Oracle, SQL Server, or DB2.
+- Utilizing advanced capabilities of JDBC drivers within Python applications.
+
+
+.. _miscellaneous_topics_javadoc:
+
 Javadoc
 =======
 
 JPype can display javadoc in ReStructured Text as part of the Python
 documentation.  To access the javadoc, the javadoc package must be located on
-the classpath.  This includes the JDK package documentation.  
+the classpath.  This includes the JDK package documentation.
 
 For example to get the documentation for ``java.lang.Class``, we start the JVM
 with the JDK documentation zip file on the classpath.
@@ -2802,6 +5633,8 @@ with custom page layouts will likely not
 If javadoc for a class cannot be located or extracted properly, default
 documentation will be generated using Java reflection.
 
+.. _miscellaneous_topics_autopep8:
+
 Autopep8
 ========
 
@@ -2834,6 +5667,9 @@ Result without ``--ignore E402``
 
         jpype.startJVM()
 
+
+.. _miscellaneous_topics_performance:
+
 Performance
 ===========
 
@@ -2852,8 +5688,8 @@ only those parts that need it in C.** Ex
 that need it in Java.
 
 Everytime an object is passed back and forth, it will incure a conversion
-cost.. In cases where a given object (be it a string, an object, an array, etc ...) is passed often
-into Java, the object should be converted once and cached.
+cost.. In cases where a given object (be it a string, an object, an array, etc
+...) is passed often into Java, the object should be converted once and cached.
 For most situations, this will address speed issues.
 
 To improve speed issues, JPype has converted all of the base classes into
@@ -2868,6 +5704,8 @@ version of it. The JVM is a memory hog,
 code execution speeds.
 
 
+.. _miscellaneous_topics_code_completion:
+
 Code completion
 ===============
 
@@ -2904,6 +5742,9 @@ the letter "s".
 
 JPype has not been tested with other autocompletion engines such as Kite.
 
+
+.. _miscellaneous_topics_garbage_collection:
+
 Garbage collection
 ==================
 
@@ -2941,6 +5782,8 @@ The sizing on this is dynamic so it shou
 a process.
 
 
+.. _miscellaneous_topics_using_jpype_for_debugging_java_code:
+
 Using JPype for debugging Java code
 ===================================
 
@@ -2956,6 +5799,8 @@ probe and plot the Java data structures
 We will briefly discuss each of these methods.
 
 
+.. _miscellaneous_topics_using_jpype_for_debugging_java_code_attaching_a_debugger:
+
 Attaching a Debugger
 --------------------
 
@@ -2995,6 +5840,7 @@ a breakpoint is hit.
 
 .. image:: attach_debugger.png
 
+
 Attach data to an Exception
 ---------------------------
 
@@ -3007,6 +5853,9 @@ attach it to as the cause of the excepti
 call stack until it reaches Python. We can then use ``getCause()`` to retrieve
 the map containing the relevant data.
 
+
+.. _miscellaneous_topics_capturing_the_state:
+
 Capturing the state
 -------------------
 
@@ -3026,154 +5875,128 @@ deserialize the state files to access th
 been recorded.
 
 
-Getting additional diagnostics
+.. _miscellaneous_topics_getting_additional_diagnostics:
+
+Getting Additional Diagnostics
 ==============================
 
-For the most part JPype does what its told, but that does not mean that
-there are no bugs.  With some many different interactions between Python and Java
-there is always some untested edge-cases.
-
-JPype has a few diagnostic tools to help deal with these sorts of problems
-but each of them require accessing a "private" JPype symbol which may be
-altered, removed, folded, spindled, or mutilated in any future release.
-Thus none of the following should be used in production code.
+For the most part, JPype operates as intended, but that does not mean there are
+no bugs or edge cases. Given the complexity of interactions between Python and
+Java, untested scenarios may occasionally arise. JPype provides several
+diagnostic tools to assist in debugging these issues. These tools require
+accessing private JPype symbols, which may change in future releases. As such,
+they should not be used in production code.
+
+.. _checking-type-cast:
 
-Checking the type of a cast
+Checking the Type of a Cast
 ---------------------------
 
-Sometimes it is difficult to understand why a particular method overload is being
-selected by the method dispatch.  To check the match type for a conversion
-call the private method ``Class._canConvertToJava``.  This will produce a string
-naming the type of conversion that will be performed as one of ``none``,
-``explicit``, ``implicit``, or ``exact``..
+Sometimes it is difficult to understand why a particular method overload is
+selected by the method dispatch. To check the match type for a conversion, use
+the private method ``Class._canConvertToJava``. This will return a string
+indicating the type of conversion performed: ``none``, ``explicit``,
+``implicit``, or ``exact``.
 
 To test the result of the conversion process, call ``Class._convertToJava``.
-Unlike an explicit cast, this just attempts to perform the conversion without
-bypassing all of the other logic involved in casting.  It replicates
-the exact process used when a method is called or a field is set.
+Unlike an explicit cast, this method attempts to perform the conversion without
+bypassing the logic involved in casting. It replicates the exact process used
+when a method is called or a field is set.
+
+.. _cpp-exceptions:
 
 C++ Exceptions in JPype
------------------------
+------------------------
+
+Internally, JPype can generate C++ exceptions, which are converted into Python
+exceptions for the user. To trace an error back to its C++ source, you can
+enable stack traces for C++ exceptions. Use the following command:
+
+.. code-block:: python
+
+   import _jpype
+   _jpype.enableStacktraces(True)
+
+Once enabled, all C++ exceptions that fall through a C++ exception handling
+block will produce an augmented stack trace. If the JPype source code is
+available, the stack trace can even include the corresponding lines of code
+where the exceptions occurred. This can help identify the source of errors that
+originate in C++ code but propagate to Python as exceptions.
 
-Internally JPype can generate C++ exception which is converted into Python
-exceptions for the user.  To trace an error back to its C++ source, it
-is necessary to obtain the original C++ exception.  As
-all sensitive block have function names compiled in to the try catch blocks,
-these C++ exception stack frames can be extracted as the "cause" of a Python
-exception.  To enable C++ stack traces use the command
-``_jpype.enableStacktraces(True)``.  Once executed all C++ exceptions that
-fell through a C++ exception handling block will produce an augmented C++
-stack trace.  If the JPype source code is available to Python, it can even
-print out each line where the stack frame was caught.  This is usually at the
-end of each function that was executed.  JPype does not need to be recompiled
-to use this option.
+.. _tracing:
 
 Tracing
 -------
 
-To debug a problem that resulted from a stateful interaction of elements the use
-of the JPype tracing mode may helpful.  To enable tracing
-recompile JPype with the ``--enable-tracing`` mode set.  When code is executed
-with tracing, every JNI call along with the object addresses and exceptions will
-be printed to the console.  This is keyed to macros that appear at the start and end of each
-JPype function.  These macros correspond to a try catch block.
-
-This will often produce very large and verbose tracing logs.  However, tracing
-is often the only way to observe a failure that originated in one JNI call but did
-not fail until many calls later.
+Tracing mode logs every JNI call, along with object addresses and exceptions,
+to the console. To enable tracing, JPype must be recompiled with the
+``--enable-tracing`` option.
+
+Tracing is useful for identifying failures that originate in one JNI call but
+manifest later. However, this mode produces verbose logs and is recommended
+only for advanced debugging.
+
+
+.. _instrumentation:
 
 Instrumentation
 ---------------
 
-In order to support coverage tools, JPype can be compiled with a special
-instrumentation mode in which the private module command ``_jpype.fault``
-can be used to trigger an error.  The argument to the fault must be a function
-name as given in the ``JP_TRACE_IN`` macro at the start of each JPype function
-or a special trigger point defined in the code.  When the fault point is
-encounter it will trigger a ``SystemError``.
-This mode of operation can be used to replicate the path
-that a particular call took and verify that the error handling from that point
-back to Java is safe.
-
-Because instrumentation uses the same control hooks as tracing, only one mode
-can be active at a time.  Enabling instrumentation requires recompiling the
-JPype module with ``--enable-coverage`` option.
+JPype supports an instrumentation mode for testing error-handling paths. This
+mode allows you to simulate faults at designated points in JPype's execution
+flow. To enable instrumentation, recompile JPype with the ``--enable-coverage``
+option.
+
+Once instrumentation is enabled, use the private module command
+``_jpype.fault`` to trigger an error. The argument to the fault command must be
+the name of a function or a predefined fault point. When the fault point is
+encountered, a ``SystemError`` is raised. Instrumentation is primarily useful
+for verifying the robustness of JPype's exception handling mechanisms.
+
 
-Using a debugger
+.. _using-debugger:
+
+Using a Debugger
 ----------------
 
-If there is a crash in the JPype module, it may be necessary to get a backtrace
-using a debugger. Unfortunately Java makes this task a bit complicated.  As
-part of its memory handling routine, Java takes over the segmentation fault
-handler.  Whenever the fault is triggered, Java checks to see if it was the
-result the growth of an internal structure.  If it was simply a need for
-additional space, Java handles the exception by allocating addition memory.  On
-the other hand, if a fault was triggered by some external source, Java
-constructs a JVM fault report and then transfers control back to the usual
-segmentation fault handler.  Java will often corrupt the stack frame.  Any
-debugger attempting to unpack the corrupted core file will instead get random
-function addresses.
-
-The alternative is for the user to start JPype with an interactive debugger and
-execute to the fault point.  But this option also presents challenges.  The
-first action after starting the JVM is a test to see if its segmentation fault
-handler was installed properly.  Thus it will trigger an intentional
-segmentation fault.  The debugger can not recognize the difference between an
-intentional test and an actual fault, so the test will stop the debugger.  To
-avoid this problem debuggers such as gdb must be set to ignore the first
-segmentation fault.  Further details on this can be found in the developer
-guide.
+If JPype crashes, it may be necessary to use a debugger to obtain a backtrace.
+However, debugging JPype can be challenging due to the JVM's handling of
+segmentation faults. The JVM intercepts segmentation faults to allocate memory
+or handle internal operations, which can corrupt stack frames.
+
+To debug JPype using tools such as ``gdb`, you must configure the debugger to
+ignore segmentation faults intentionally triggered by the JVM. For example, use
+the following command to start ``gdb`` and ignore the first fault:
+
+.. code-block:: shell
+
+   gdb --args python script.py
+   (gdb) handle SIGSEGV nostop noprint pass
+
+This configuration allows the debugger to bypass JVM-related faults while
+capturing legitimate errors. Additionally, disable Python's fault handler to
+avoid interference with segmentation fault reporting.
 
 
 .. _caller sensitive:
 
-Caller sensitive methods
-========================
+Caller-Sensitive Methods
+-------------------------
 
-The Java security model tracks what caller requested the method as a means to
-determine the level of access to provide.  Internal callers are provided
-privileged access to perform unsafe operations and external callers are given
-safer and more restricted access.  To perform this task, the JVM seaches the
-call stack to obtain the calling methods module.
-
-This presents a difficulty for method invoked from JNI.  A method called from
-JNI lacks any call stack to unravel.  Rather than relegating the call
-to a safer level of access, the security model would outright deny access to
-certain JPype calls.  This resulted in a number of strange
-behaviors over the years that were forced to be worked around.  This issue was
-finally solved with the release of Java 12 when they outright broken all calls
-to getMethod by throwing a NullPointer exception
-whenever the caller frame was not found.  This inadvertent clued us into
-why Java would act so strangely for certain calls such as constructing a
-SQL database or attempting to call ``Class.forName``.  By creating an actual
-test case to work around we were able to resolve this limitation.
-
-Once we identified the issue, the workaround is only call caller sensitive
-methods from within Java.  But given that we call methods through JNI and the
-JNI interface defines no way to specify an origin for the call, the means we
-needed to develop an alternative calling mechanism.  Instead of calling methods
-directly, we instead pass the method id and the list of desired arguments to
-the internal ``org.jpype`` Java package.  This package unpacks the request and
-executes the desired method from within Java.  The call stack will indicate the
-caller is an external jar and be given the safe and restricted level of access.
-The result is then passed back to through the JNI layer.
-
-This special calling mechanism is slower and more indirect than the normal
-calling procedure, so its use is limited to only those methods that really
-require a caller sensitive procedure.  The mechanism to determine which methods
-are caller sensitive depends on the internals of Java and have changed with
-Java versions.  Older Java versions did not directly mark the caller sensitive
-methods and we must instead blanket bomb all methods belonging to
-``java.lang.Class``, ``java.lang.ClassLoader``, and  ``java.sql.DriverManager``.
-Newer versions specifically annotate the methods requiring caller sensitive
-treatment, but for some reason this annotation is a package private and thus
-we must search through method annotations by name to find the
-caller sensitive annotation.  Fortunately, this process is only performed once
-when the class is created, and very few methods have a large number of
-annotations so this isn't a performance hit.
+Java's security model tracks the caller of certain methods to determine the
+level of access. These methods, known as "caller-sensitive methods," require
+special handling in JPype. Examples of caller-sensitive methods include
+``Class.forName``, ``java.lang.ClassLoader`` methods, and certain methods in
+``java.sql.DriverManager``.
+
+To handle caller-sensitive methods, JPype routes calls through an internal
+Java package, ``org.jpype``, which executes the method within the JVM. This
+ensures proper security context and avoids access errors. Although this
+mechanism introduces slight overhead, it is necessary for compatibility with
+Java's security model.
 
 
-.. _limitations:
+.. _limitations_jvm:
 
 JPype Known limitations
 =======================
@@ -3181,6 +6004,9 @@ JPype Known limitations
 This section lists those limitations that are unlikely to change, as they come
 from external sources.
 
+
+.. _miscellaneous_topics_jpype_known_limitations_annotations:
+
 Annotations
 -----------
 
@@ -3192,7 +6018,7 @@ JProxy method so that a framework like S
 JPype uses the Java supplied ``Proxy`` to implement an interface.  That API
 does not support addition of a runtime annotation to a method or class.  Thus,
 all methods and classes when probed with reflection that are implemented in
-Python will come back with no annotations.   
+Python will come back with no annotations.
 
 Further, the majority of annotation magic within Java is actually performed at
 compile time.  This is accomplished using an annotation processor.  When a
@@ -3200,7 +6026,7 @@ class or method is annotated, the compil
 annotation processor which then can produce new code or modify the class
 annotations.  As this is a compile time process, even if annotations were added
 by Python to a class they would still not be active as the corresponding
-compilation phase would not have been executed.   
+compilation phase would not have been executed.
 
 This is a limitation of the implementation of annotations by the Java virtual
 machine.  It is technically possible though the use of specialized code
@@ -3211,6 +6037,9 @@ annotations are unlikely to be useful. A
 support class or method annotations.
 
 
+
+.. _miscellaneous_topics_restarting_the_jvm:
+
 Restarting the JVM
 -------------------
 
@@ -3221,6 +6050,9 @@ broken state and the new JVM instance wi
 resulting in a memory leak. Thus it is not possible to start the JVM after it
 has been shut down with the current implementation.
 
+
+.. _miscellaneous_topics_running_multiple_jvm:
+
 Running multiple JVM
 --------------------
 
@@ -3255,6 +6087,8 @@ Thus it appears prohibitive to support m
 class model.
 
 
+.. _miscellaneous_topics_jpype_known_limitations_errors_reported_by_python_fault_handler:
+
 Errors reported by Python fault handler
 ---------------------------------------
 
@@ -3311,17 +6145,41 @@ For example, absl installs faulthandlers
 main routine would need to disable faulthandlers to avoid potential crashes.
 
 
+
+.. _miscellaneous_topics_unsupported_java_versions:
+
+Unsupported Java Versions
+-------------------------
+
+JPype now requires the use of the module API, which was introduced in **Java
+9**. As a result, the earliest version of Java supported by JPype is **Java
+11**, which is part of the Long-Term Support (LTS) release.
+
+If you need to use **Java 8**, you must use JPype version **1.5.2 or earlier**,
+as newer versions of JPype no longer support Java 8.
+
+
+
+.. _miscellaneous_topics_unsupported_python_versions:
+
 Unsupported Python versions
 ---------------------------
 
-Python 3.4 and earlier
+
+.. _miscellaneous_topics_python_38_and_earlier:
+
+Python 3.8 and earlier
 ~~~~~~~~~~~~~~~~~~~~~~
 
 The oldest version of Python that we currently support is Python 3.5.  Before
 Python 3.5 there were a number of structural difficulties in the object model
 and the buffering API.  In principle, those features could be excised from
 JPype to extend support to older Python 3 series version, but that is unlikely
-to happen without a significant effort.
+to happen without a significant effort.  Recent changes in memory models
+require Python 3.8 or later.
+
+
+.. _miscellaneous_topics_python_2:
 
 Python 2
 ~~~~~~~~
@@ -3335,6 +6193,9 @@ Python 2 code.  Since that time JPype op
 from 300% to 10000% as we can now implement everything back in CPython rather
 than band-aiding it with interpreted Python code.
 
+
+.. _miscellaneous_topics_pypy:
+
 PyPy
 ~~~~
 
@@ -3351,6 +6212,9 @@ broke some of the features that are diff
 errors make absolutely no sense to me.  So unless a PyPy developer generously
 volunteering time for this project, this one is unlikely to happen.
 
+
+.. _miscellaneous_topics_jython_python:
+
 Jython Python
 ~~~~~~~~~~~~~
 
@@ -3362,18 +6226,27 @@ Python modules often mistake JPype for J
 that differences in the API triggers an error.
 
 
+
+.. _miscellaneous_topics_unsupported_java_virtual_machines:
+
 Unsupported Java virtual machines
 ---------------------------------
 
 The open JVM implementations *Cacao* and *JamVM* are known not to work with
 JPype.
 
+
+.. _miscellaneous_topics_unsupported_platforms:
+
 Unsupported Platforms
 ---------------------
 
 Some platforms are problematic for JPype due to interactions between the
 Python libraries and the JVM implementation.
 
+
+.. _miscellaneous_topics_cygwin:
+
 Cygwin
 ~~~~~~
 
@@ -3394,3 +6267,206 @@ Freezing
 JPype supports freezing and deployment with
 `PyInstaller <https://pyinstaller.readthedocs.io/>`_.  The hook is included
 with JPype installations and no extra configuration should be needed.
+
+
+.. _miscellaneous_topics_glossary:
+
+Useless Trivia
+==============
+
+Thrameos, the primary developer, maintains the correct pronounciation of
+JPype is Jay-Pie-Pee like the word recipe.  His position is that application
+of a silient e is inappropriate for this made up name.
+
+1) Jay-Pie is more emblematic of the project goal which is connection of
+Java and Python.
+
+2) Rules in English regarding a silent e following a p are poor and depend
+mostly on the origin of the word.  Is it like type from the Greek word
+typus or French récipé?  As the y in not being modified as it would otherwise
+be pronounced as Pie like NumPy and SciPy, that means the only logical
+conclusion is that it was intended to be voiced.
+
+3) Jay-pipe or gh-pipe implies that this project is built on pipes (also known
+as series of tubes such as the internet).  JPype is based on JNI (Jay-eN-Ey).
+If you are looking for a pipe based Java connection use Py4J.  If you are looking
+for solution based on JNI use JPypé.
+
+
+
+
+
+Glossary
+========
+
+.. _miscellaneous_topics_glossary_b:
+
+B
+-
+
+**Boxed Types**
+Immutable Java objects that wrap primitive types (e.g., `java.lang.Integer`
+for `int`). Used when primitives need to be treated as objects.
+
+.. _miscellaneous_topics_glossary_c:
+
+C
+-
+
+**Caller Sensitive Methods**
+Java methods that determine the caller's access level based on the call stack.
+JPype uses special mechanisms to handle these methods safely.
+
+**Classpath**
+A parameter specifying the location of Java classes or JAR files required by
+the JVM. It is essential for loading Java libraries.
+
+.. _miscellaneous_topics_glossary_d:
+
+D
+-
+
+**Deferred Proxy**
+A proxy that is created before the JVM is started by specifying the interface
+as a string and using the `deferred=True` argument. The implementation is
+checked only when the proxy is first used.
+
+.. _miscellaneous_topics_glossary_e:
+
+E
+-
+
+**Exact Conversion**
+A type conversion in JPype where the Python type matches the Java type
+exactly. Example: Python `int` to Java `int`.
+
+.. _miscellaneous_topics_glossary_f:
+
+F
+-
+
+**Functional Interface**
+A Java interface with a single abstract method (SAM). JPype allows Python
+callables (e.g., functions, lambdas) to be passed directly to Java methods
+expecting a functional interface.
+
+.. _miscellaneous_topics_glossary_g:
+
+G
+-
+
+**Garbage Collection (GC)**
+The process of automatically reclaiming memory occupied by unused objects.
+JPype links Python's and Java's garbage collectors to avoid memory issues.
+
+.. _miscellaneous_topics_glossary_i:
+
+I
+-
+
+**Implicit Conversion**
+A type conversion in JPype where Python types are automatically converted to
+compatible Java types. Example: Python `int` to Java `long`.
+
+**Interface**
+A Java construct that defines a set of methods without implementations. JPype
+allows Python classes to implement Java interfaces using proxies.
+
+
+.. _miscellaneous_topics_glossary_m:
+
+M
+-
+
+**Mapping**
+A Python concept for key-value pairs. JPype customizes Java `Map` classes to
+behave like Python dictionaries.
+
+**Multidimensional Arrays**
+Java arrays with multiple dimensions. JPype supports creating and working with
+these arrays using nested lists.
+
+.. _miscellaneous_topics_glossary_n:
+
+N
+-
+
+**NumPy Integration**
+JPype's ability to efficiently transfer data between Java arrays and NumPy
+arrays using memory buffers.
+
+.. _miscellaneous_topics_glossary_o:
+
+O
+-
+
+**Object Instance**
+A Java object created from a class. Example: `obj = MyClass()` creates an
+instance of `MyClass`.
+
+**Overloaded Methods**
+Java methods with the same name but different parameter types or counts. JPype
+selects the appropriate overload based on the Python arguments provided.
+
+.. _miscellaneous_topics_glossary_p:
+
+P
+-
+
+**Primitive Types**
+Basic data types in Java (e.g., `int`, `float`, `boolean`). JPype maps these
+types to Python equivalents (e.g., `JInt`, `JFloat`, `JBoolean`).
+
+**Proxy**
+A proxy is an intermediary that allows Python objects to implement Java
+interfaces. In the context of JPype, Proxies focus solely on providing the
+required Java interface functionality.
+
+.. _miscellaneous_topics_glossary_s:
+
+S
+-
+
+**SAM (Single Abstract Method)**
+A Java interface with a single abstract method. Used in functional programming
+and supported by JPype for Python callables.
+
+**Synchronized**
+A Java keyword for thread-safe operations. JPype provides the
+`jpype.synchronized()` method for similar functionality in Python.
+
+.. _miscellaneous_topics_glossary_t:
+
+T
+-
+
+**Type Factory**
+A JPype mechanism for creating Java types in Python. Examples include `JClass`
+for classes and `JArray` for arrays.
+
+.. _miscellaneous_topics_glossary_u:
+
+U
+-
+
+**UnsupportedClassVersionError**
+A JVM error indicating that a JAR file was compiled for a newer Java version
+than the JVM being used.
+
+.. _miscellaneous_topics_glossary_w:
+
+W
+-
+
+**Wrapper**
+A wrapper is an object used to represent an object from one programming language
+in another programming language, providing a native look and feel. In JPype,
+wrappers are used to present Java objects to Python, making them behave like
+native Python objects while retaining their underlying Java functionality.
+Wrappers encapsulate Java references, such as class instances, arrays, or boxed
+types, and may also include proxies when implementing Java interfaces.
+
+Future versions of JPype aim to introduce the ability to manipulate Python
+objects from Java. In this case, wrappers will represent Python objects from the
+perspective of Java, providing a seamless interface for Java code to interact
+with Python objects.
diff -pruN 1.5.0-1/jpype/__init__.py 1.6.0-1/jpype/__init__.py
--- 1.5.0-1/jpype/__init__.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/__init__.py	2025-06-01 03:57:43.000000000 +0000
@@ -52,7 +52,7 @@ __all__.extend(_jclass.__all__)  # type:
 __all__.extend(_jcustomizer.__all__)  # type: ignore[name-defined]
 __all__.extend(_gui.__all__)  # type: ignore[name-defined]
 
-__version__ = "1.5.0"
+__version__ = "1.6.0"
 __version_info__ = __version__.split('.')
 
 
diff -pruN 1.5.0-1/jpype/_classpath.py 1.6.0-1/jpype/_classpath.py
--- 1.5.0-1/jpype/_classpath.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/_classpath.py	2025-06-01 03:57:43.000000000 +0000
@@ -52,7 +52,7 @@ def addClassPath(path1: typing.Union[str
         path1 = path2.joinpath(path1)
 
     # If the JVM is already started then we will have to load the paths
-    # immediately into the DynamicClassLoader
+    # immediately into the JPypeClassLoader
     if _jpype.isStarted():
         Paths = _jpype.JClass('java.nio.file.Paths')
         JContext = _jpype.JClass('org.jpype.JPypeContext')
@@ -62,9 +62,9 @@ def addClassPath(path1: typing.Union[str
             if len(paths) == 0:
                 return
             for path in paths:
-                classLoader.addFile(Paths.get(str(path)))
+                classLoader.addPath(Paths.get(str(path)))
         else:
-            classLoader.addFile(Paths.get(str(path1)))
+            classLoader.addPath(Paths.get(str(path1)))
     _CLASSPATHS.append(path1)
 
 
diff -pruN 1.5.0-1/jpype/_core.py 1.6.0-1/jpype/_core.py
--- 1.5.0-1/jpype/_core.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/_core.py	2025-06-01 03:57:43.000000000 +0000
@@ -19,6 +19,7 @@ from __future__ import annotations
 
 import atexit
 import os
+from pathlib import Path
 import sys
 import typing
 
@@ -108,24 +109,34 @@ def isJVMStarted():
     return _jpype.isStarted()
 
 
-def _hasClassPath(args) -> bool:
-    for i in args:
-        if isinstance(i, str) and i.startswith('-Djava.class.path'):
-            return True
-    return False
-
-
-def _handleClassPath(classpath) -> str:
+def _getOption(args, var, sep=None, keep=False):
+    """ Get an option and remove it from the current jvm arguments list """
+    for i, v in enumerate(args):
+        if not isinstance(v, str):
+            continue
+        _, _, value = v.partition('%s=' % var)
+        if value:
+            if not keep:
+                del args[i]
+            if sep is not None:
+                return value.split(sep)
+            return value
+    return []
+
+
+def _expandClassPath(
+    classpath: typing.Union[typing.Sequence[_PathOrStr],
+                            _PathOrStr, None] = None
+) -> typing.List[str]:
     """
     Return a classpath which represents the given tuple of classpath specifications
     """
-    out = []
-
+    out: list[str] = []
     if isinstance(classpath, (str, os.PathLike)):
         classpath = (classpath,)
     try:
         # Convert anything iterable into a tuple.
-        classpath = tuple(classpath)
+        classpath = tuple(classpath)  # type: ignore[arg-type]
     except TypeError:
         raise TypeError("Unknown class path element")
 
@@ -133,7 +144,8 @@ def _handleClassPath(classpath) -> str:
         try:
             pth = os.fspath(element)
         except TypeError as err:
-            raise TypeError("Classpath elements must be strings or Path-like") from err
+            raise TypeError(
+                "Classpath elements must be strings or Path-like") from err
 
         if isinstance(pth, bytes):
             # In the future we may allow this to support Paths which are undecodable.
@@ -145,7 +157,7 @@ def _handleClassPath(classpath) -> str:
             out.extend(glob.glob(pth + '.jar'))
         else:
             out.append(pth)
-    return _classpath._SEP.join(out)
+    return out
 
 
 _JVM_started = False
@@ -155,10 +167,39 @@ def interactive():
     return bool(getattr(sys, 'ps1', sys.flags.interactive))
 
 
+def _findTemp():
+    dirlist = []
+    # Mirror Python tempfile with a check for ascii
+    for envname in 'TMPDIR', 'TEMP', 'TMP':
+        dirname = os.getenv(envname)
+        if dirname and dirname.isascii():
+            dirlist.append(dirname)
+    if os.name == 'nt':
+        for dirname in [os.path.expanduser(r'~\AppData\Local\Temp'),
+                        os.path.expandvars(r'%SYSTEMROOT%\Temp'),
+                        r'c:\temp', r'c:\tmp', r'\temp', r'\tmp']:
+            if dirname and dirname.isascii():
+                dirlist.append(dirname)
+    else:
+        dirlist.extend(['/tmp', '/var/tmp', '/usr/tmp'])
+
+    name = str(os.getpid())
+    for d in dirlist:
+        p = Path("%s/%s" % (d, name))
+        try:
+            p.touch()
+            p.unlink()
+        except Exception as ex:
+            continue
+        return d
+    raise SystemError("Unable to find non-ansii path")
+
+
 def startJVM(
     *jvmargs: str,
     jvmpath: typing.Optional[_PathOrStr] = None,
-    classpath: typing.Union[typing.Sequence[_PathOrStr], _PathOrStr, None] = None,
+    classpath: typing.Union[typing.Sequence[_PathOrStr],
+                            _PathOrStr, None] = None,
     ignoreUnrecognized: bool = False,
     convertStrings: bool = False,
     interrupt: bool = not interactive(),
@@ -200,20 +241,27 @@ def startJVM(
       TypeError: if a keyword argument conflicts with the positional arguments.
 
      """
+
+# Code for 1.6
+#    modulepath: typing.Union[typing.Sequence[_PathOrStr], _PathOrStr, None] = None,
+
     if _jpype.isStarted():
         raise OSError('JVM is already started')
     global _JVM_started
     if _JVM_started:
         raise OSError('JVM cannot be restarted')
 
+    # Convert to list
+    jvm_args: list[str] = list(jvmargs)
+
     # JVM path
-    if jvmargs:
+    if jvm_args:
         # jvm is the first argument the first argument is a path or None
-        if jvmargs[0] is None or (isinstance(jvmargs[0], str) and not jvmargs[0].startswith('-')):
+        if jvm_args[0] is None or (isinstance(jvm_args[0], str) and not jvm_args[0].startswith('-')):
             if jvmpath:
                 raise TypeError('jvmpath specified twice')
-            jvmpath = jvmargs[0]
-            jvmargs = jvmargs[1:]
+            jvmpath = jvm_args[0]
+            jvm_args = jvm_args[1:]
 
     if not jvmpath:
         jvmpath = getDefaultJVMPath()
@@ -221,31 +269,93 @@ def startJVM(
         # Allow the path to be a PathLike.
         jvmpath = os.fspath(jvmpath)
 
-    extra_jvm_args: typing.Tuple[str, ...] = tuple()
+    # Handle strings and list of strings.
+    extra_jvm_args: list[str] = []
 
     # Classpath handling
-    if _hasClassPath(jvmargs):
+    old_classpath = _getOption(jvm_args, "-Djava.class.path", _classpath._SEP)
+    if old_classpath:
         # Old style, specified in the arguments
         if classpath is not None:
             # Cannot apply both styles, conflict
             raise TypeError('classpath specified twice')
+        classpath = old_classpath
     elif classpath is None:
         # Not specified at all, use the default classpath.
         classpath = _classpath.getClassPath()
 
-    # Handle strings and list of strings.
-    if classpath:
-        extra_jvm_args += (f'-Djava.class.path={_handleClassPath(classpath)}', )
+# Code for 1.6 release when we add module support
+#    # Modulepath handling
+#    old_modulepath = _getOption(jvm_args, "--module-path", _classpath._SEP)
+#    if old_modulepath:
+#        # Old style, specified in the arguments
+#        if modulepath is not None:
+#            # Cannot apply both styles, conflict
+#            raise TypeError('modulepath specified twice')
+#        modulepath = old_modulepath
+#    if modulepath is not None:
+#        mp = _classpath._SEP.join(_expandClassPath(modulepath))
+#        extra_jvm_args += ['--module-path=%s'%mp ]
+
+    # Get the support library
+    support_lib = os.path.join(os.path.dirname(
+        os.path.dirname(__file__)), "org.jpype.jar")
+    if not os.path.exists(support_lib):
+        raise RuntimeError(
+            "Unable to find org.jpype.jar support library at " + support_lib)
+
+    system_class_loader = _getOption(
+        jvm_args, "-Djava.system.class.loader", keep=True)
+
+    java_class_path = _expandClassPath(classpath)
+    java_class_path.append(support_lib)
+    java_class_path = list(filter(len, java_class_path))
+    classpath = _classpath._SEP.join(java_class_path)
+    tmp = None
+
+    # Make sure our module is always on the classpath
+    if not classpath.isascii():
+        if system_class_loader:
+            # https://bugs.openjdk.org/browse/JDK-8079633?jql=text%20~%20%22ParseUtil%22
+            raise ValueError(
+                "system classloader cannot be specified with non ascii characters in the classpath")
+
+        # If we are not installed on an ascii path then we will need to copy the jar to a new location
+        if not support_lib.isascii():
+            import tempfile
+            import shutil
+            fd, path = tempfile.mkstemp(dir=_findTemp())
+            if not path.isascii():
+                raise ValueError("Unable to find ascii temp directory.")
+            shutil.copyfile(support_lib, path)
+            support_lib = path
+            tmp = path
+            os.close(fd)
+            # Don't remove
+
+        # ok, setup the jpype system classloader and add to the path after startup
+        # this guarentees all classes have the same permissions as they did in the past
+        from urllib.parse import quote
+        extra_jvm_args += [
+            '-Djava.system.class.loader=org.jpype.JPypeClassLoader',
+            '-Djava.class.path=%s' % support_lib,
+            '-Djpype.class.path=%s' % quote(classpath),
+            '-Xshare:off'
+        ]
+    else:
+        # no problems
+        extra_jvm_args += ['-Djava.class.path=%s' % classpath]
 
     try:
         import locale
         # Gather a list of locale settings that Java may override (excluding LC_ALL)
-        categories = [getattr(locale, i) for i in dir(locale) if i.startswith('LC_') and i != 'LC_ALL']
+        categories = [getattr(locale, i) for i in dir(
+            locale) if i.startswith('LC_') and i != 'LC_ALL']
         # Keep the current locale settings, else Java will replace them.
         prior = [locale.getlocale(i) for i in categories]
         # Start the JVM
-        _jpype.startup(jvmpath, jvmargs + extra_jvm_args,
-                       ignoreUnrecognized, convertStrings, interrupt)
+        _jpype.startup(jvmpath, tuple(jvm_args + extra_jvm_args),
+                       ignoreUnrecognized, convertStrings, interrupt, tmp)
         # Collect required resources for operation
         initializeResources()
         # Restore locale
@@ -261,7 +371,8 @@ def startJVM(
             match = re.search(r"([0-9]+)\.[0-9]+", source)
             if match:
                 version = int(match.group(1)) - 44
-                raise RuntimeError(f"{jvmpath} is older than required Java version{version}") from ex
+                raise RuntimeError(
+                    f"{jvmpath} is older than required Java version{version}") from ex
         raise
 
 
@@ -339,6 +450,7 @@ def initializeResources():
     _jpype._type_classes[object] = _jpype._java_lang_Object
     _jpype._type_classes[_jpype.JString] = _jpype._java_lang_String
     _jpype._type_classes[_jpype.JObject] = _jpype._java_lang_Object
+    _jpype._type_classes[_jpype.JClass] = _jpype._java_lang_Class
     _jinit.runJVMInitializers()
 
     _jpype.JClass('org.jpype.JPypeKeywords').setKeywords(
@@ -347,7 +459,7 @@ def initializeResources():
     _jpype.JPypeContext = _jpype.JClass('org.jpype.JPypeContext').getInstance()
     _jpype.JPypeClassLoader = _jpype.JPypeContext.getClassLoader()
 
-    # Everything successed so started is now true.
+    # Everything succeeded so started is now true.
     _JVM_started = True
 
 
diff -pruN 1.5.0-1/jpype/_jarray.py 1.6.0-1/jpype/_jarray.py
--- 1.5.0-1/jpype/_jarray.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/_jarray.py	2025-06-01 03:57:43.000000000 +0000
@@ -17,6 +17,7 @@
 # *****************************************************************************
 import _jpype
 from . import _jcustomizer
+from collections.abc import Sequence
 
 
 __all__ = ['JArray']
@@ -92,6 +93,18 @@ class JArray(_jpype._JObject, internal=T
     def of(cls, array, dtype=None):
         return _jpype.arrayFromBuffer(array, dtype)
 
+    def __class_getitem__(cls, key):
+        if key is _jpype.JClass:
+            # explicit check for JClass
+            # _toJavaClass cannot be used
+            # passing int, float, str, etc is not allowed
+            key = _jpype._java_lang_Class
+        if isinstance(key, _jpype._java_lang_Class):
+            key = _jpype.JClass(key)
+        if isinstance(key, _jpype.JClass):
+            return type(key[0])
+        raise TypeError("%s is not a Java class" % key)
+
 
 class _JArrayProto(object):
 
@@ -204,3 +217,4 @@ class _JCharArray(object):
 # Install module hooks
 _jcustomizer._applyCustomizerPost(_jpype._JArray, _JArrayProto)
 _jpype.JArray = JArray
+Sequence.register(_jpype._JArray)
diff -pruN 1.5.0-1/jpype/_jarray.pyi 1.6.0-1/jpype/_jarray.pyi
--- 1.5.0-1/jpype/_jarray.pyi	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/jpype/_jarray.pyi	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,60 @@
+# *****************************************************************************
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+#   See NOTICE file for details.
+#
+# *****************************************************************************
+import typing
+
+
+__all__ = ['JArray']
+
+
+T = typing.TypeVar('T')
+U = typing.TypeVar('U')
+
+
+class JArray(typing.Generic[T]):
+
+    def __new__(cls, tp: typing.Type[T], dims=1) -> JArray[T]:
+        ...
+
+    @classmethod
+    def of(cls, array, dtype: typing.Optional[typing.Type[U]] = None) -> JArray[U]:
+        ...
+
+    def __len__(self) -> int:
+        ...
+
+    def __iter__(self) -> typing.Iterator[T]:
+        ...
+
+    def __reversed__(self) -> typing.Iterator[T]:
+        ...
+
+    @typing.overload
+    def __getitem__(self, key: int) -> T:
+        ...
+
+    @typing.overload
+    def __getitem__(self, key: slice) -> JArray[T]:
+        ...
+
+    @typing.overload
+    def __setitem__(self, index: int, value: T):
+        ...
+
+    @typing.overload
+    def __setitem__(self, index: slice, value: typing.Sequence[T]):
+        ...
diff -pruN 1.5.0-1/jpype/_jclass.py 1.6.0-1/jpype/_jclass.py
--- 1.5.0-1/jpype/_jclass.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/_jclass.py	2025-06-01 03:57:43.000000000 +0000
@@ -98,6 +98,10 @@ class JClass(_jpype._JClass, metaclass=J
         # Pass to class factory to create the type
         return _jpype._getClass(jc)
 
+    def __class_getitem__(cls, index):
+        # enables JClass[1] to get a Class[]
+        return JClass("java.lang.Class")[index]
+
 
 class JInterface(_jpype._JObject, internal=True):  # type: ignore[call-arg]
     """A meta class for all Java Interfaces.
diff -pruN 1.5.0-1/jpype/_jcollection.py 1.6.0-1/jpype/_jcollection.py
--- 1.5.0-1/jpype/_jcollection.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/_jcollection.py	2025-06-01 03:57:43.000000000 +0000
@@ -162,10 +162,16 @@ class _JList(object):
         return self
 
     def __add__(self, obj):
-        new = self.clone()
+        new = _jpype.JClass("java.util.ArrayList")(self)
         new.extend(obj)
         return new
 
+    def __mul__(self, obj):
+        new = _jpype.JClass("java.util.ArrayList")()
+        for i in range(obj):
+            new.extend(self)
+        return new
+
     @JOverride(sticky=True, rename='remove_')
     def remove(self, obj):
         try:
diff -pruN 1.5.0-1/jpype/_jproxy.py 1.6.0-1/jpype/_jproxy.py
--- 1.5.0-1/jpype/_jproxy.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/_jproxy.py	2025-06-01 03:57:43.000000000 +0000
@@ -60,7 +60,8 @@ def _createJProxyDeferred(cls, *intf):
     instantiation.
     """
     if not isinstance(cls, type):
-        raise TypeError("JImplements only applies to types, not %s" % (type(cls)))
+        raise TypeError(
+            "JImplements only applies to types, not %s" % (type(cls)))
 
     def new(tp, *args, **kwargs):
         # Attach a __jpype_interfaces__ attribute to this class if
@@ -69,7 +70,7 @@ def _createJProxyDeferred(cls, *intf):
         if actualIntf is None:
             actualIntf = _prepareInterfaces(cls, intf)
             tp.__jpype_interfaces__ = actualIntf
-        return _jpype._JProxy.__new__(tp, None, actualIntf)
+        return _jpype._JProxy.__new__(tp, None, None, actualIntf)
 
     members = {'__new__': new}
     # Return the augmented class
@@ -81,12 +82,13 @@ def _createJProxy(cls, *intf):
     @JOverride notation on methods evaluated at declaration.
     """
     if not isinstance(cls, type):
-        raise TypeError("JImplements only applies to types, not %s" % (type(cls)))
+        raise TypeError(
+            "JImplements only applies to types, not %s" % (type(cls)))
 
     actualIntf = _prepareInterfaces(cls, intf)
 
     def new(tp, *args, **kwargs):
-        self = _jpype._JProxy.__new__(tp, None, actualIntf)
+        self = _jpype._JProxy.__new__(tp, None, None, actualIntf)
         tp.__init__(self, *args, **kwargs)
         return self
 
@@ -150,7 +152,9 @@ def _convertInterfaces(intf):
     # Flatten the list
     intflist = []
     for item in intf:
-        if isinstance(item, str) or not hasattr(item, '__iter__'):
+        if isinstance(item, _jpype.JClass):
+            intflist.append(item)
+        elif isinstance(item, str) or not hasattr(item, '__iter__'):
             intflist.append(item)
         else:
             intflist.extend(item)
@@ -196,7 +200,7 @@ class JProxy(_jpype._JProxy):
 
     This is an older style JPype proxy interface that uses either a
     dictionary or an object instance to implement methods defined
-    in java.  The python object can be held by java and its lifespan
+    in Java.  The Python object can be held by Java and its lifespan
     will continue as long as java holds a reference to the object
     instance.  New code should use ``@JImplements`` annotation as
     it will support improved type safety and error handling.
@@ -211,23 +215,26 @@ class JProxy(_jpype._JProxy):
             proxy,
         dict (dict[string, callable], optional): specifies a dictionary
             containing the methods to be called when executing the
-            java interface methods.
+            Java interface methods.
         inst (object, optional): specifies an object with methods
-            whose names matches the java interfaces methods.
+            whose names matches the Java interfaces methods.
+        convert (bool, optional): if True the proxy is unwrapped
+            to a Python object.
     """
     def __new__(cls, intf, dict=None, inst=None, convert=False):
         # Convert the interfaces
         actualIntf = _convertInterfaces([intf])
 
-        # Verify that one of the options has been selected
-        if dict is not None and inst is not None:
-            raise TypeError("Specify only one of dict and inst")
-
+        # Create an interface by dictionary.  If instance is given
+        # it will be passed as self.  Its presence in Python when 
+        # returned will be given by convert.
         if dict is not None:
-            return _jpype._JProxy(_JFromDict(dict), actualIntf, convert)
+            return _jpype._JProxy(inst, _JFromDict(dict), actualIntf, convert)
 
+        # (obsolete) Use a Python object with the same methods as the interface.
+        # This form as mostly be replaced by @JImplements form.
         if inst is not None:
-            return _jpype._JProxy.__new__(cls, inst, actualIntf, convert)
+            return _jpype._JProxy.__new__(cls, inst, inst, actualIntf, convert)
 
         raise TypeError("a dict or inst must be specified")
 
diff -pruN 1.5.0-1/jpype/_jvmfinder.py 1.6.0-1/jpype/_jvmfinder.py
--- 1.5.0-1/jpype/_jvmfinder.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/_jvmfinder.py	2025-06-01 03:57:43.000000000 +0000
@@ -21,27 +21,25 @@ import os
 import sys
 
 __all__ = ['getDefaultJVMPath',
-           'JVMNotFoundException', 'JVMNotSupportedException']
+           'get_default_jvm_path',
+           'JVMNotFoundException',
+           'JVMNotSupportedException']
 
-try:
-    import winreg
-except ImportError:
-    winreg = None   # type: ignore[assignment]
+from typing import Sequence, Tuple
 
 
 class JVMNotFoundException(ValueError):
-    """ Exception raised when no JVM was found in the search path.
+    """Exception raised when no JVM was found in the search path.
 
     This exception is raised when the all of the places searched did not
     contain a JVM. The locations searched depend on the machine architecture.
     To avoid this exception specify the JAVA_HOME environment variable as a
     valid jre or jdk root directory.
     """
-    pass
 
 
 class JVMNotSupportedException(ValueError):
-    """ Exception raised when the JVM is not supported.
+    """Exception raised when the JVM is not supported.
 
     This exception is raised after a search found a valid Java home directory
     was found, but the JVM shared library found is not supported. Typically
@@ -49,12 +47,10 @@ class JVMNotSupportedException(ValueErro
     32 vs 64 bit, or the JVM is older than the version used to compile
     JPype.
     """
-    pass
 
 
-def getDefaultJVMPath():
-    """
-    Retrieves the path to the default or first found JVM library
+def getDefaultJVMPath() -> str:
+    """Retrieves the path to the default or first found JVM library.
 
     Returns:
       The path to the JVM shared library file
@@ -74,32 +70,27 @@ def getDefaultJVMPath():
     return finder.get_jvm_path()
 
 
-class JVMFinder(object):
-    """
-    JVM library finder base class
-    """
+get_default_jvm_path = getDefaultJVMPath
 
-    def __init__(self):
-        """
-        Sets up members
-        """
-        # Library file name
-        self._libfile = "libjvm.so"
 
-        # Predefined locations
-        self._locations = ("/usr/lib/jvm", "/usr/java")
+class JVMFinder:
+    """JVM library finder base class."""
+    # Library file name
+    _libfile: str = "libjvm.so"
 
+    # Predefined locations
+    _locations: Tuple[str, ...] = ("/usr/lib/jvm", "/usr/java")
+
+    def __init__(self):
         # Search methods
         self._methods = (self._get_from_java_home,
                          self._get_from_known_locations)
 
     def find_libjvm(self, java_home):
-        """
-        Recursively looks for the given file
+        """Recursively looks for the given file.
 
         Parameters:
             java_home(str): A Java home folder
-            filename(str): filename: Name of the file to find
 
         Returns:
             The first found file path, or None
@@ -108,7 +99,7 @@ class JVMFinder(object):
         found_non_supported_jvm = False
 
         # Look for the file
-        for root, _, names in os.walk(java_home):
+        for root, _, names in sorted(os.walk(java_home), key=lambda t: len(t[0].split(os.sep))):
             if self._libfile in names:
                 # Found it, but check for non supported jvms
                 candidate = os.path.split(root)[1]
@@ -130,7 +121,8 @@ class JVMFinder(object):
                                    "environment variable is pointing "
                                    "to correct installation.")
 
-    def find_possible_homes(self, parents):
+    @staticmethod
+    def find_possible_homes(parents):
         """
         Generator that looks for the first-level children folders that could be
         Java installations, according to their name
@@ -249,25 +241,19 @@ class JVMFinder(object):
 
 
 class LinuxJVMFinder(JVMFinder):
-    """
-    Linux JVM library finder class
-    """
+    """Linux JVM library finder class."""
 
-    def __init__(self):
-        """
-        Sets up members
-        """
-        # Call the parent constructor
-        JVMFinder.__init__(self)
+    # Java bin file
+    _java = "/usr/bin/java"
 
-        # Java bin file
-        self._java = "/usr/bin/java"
+    # Library file name
+    _libfile = "libjvm.so"
 
-        # Library file name
-        self._libfile = "libjvm.so"
+    # Predefined locations
+    _locations = ("/usr/lib/jvm", "/usr/java", "/opt/sun")
 
-        # Predefined locations
-        self._locations = ("/usr/lib/jvm", "/usr/java", "/opt/sun")
+    def __init__(self):
+        super().__init__()
 
         # Search methods
         self._methods = (self._get_from_java_home,
@@ -296,23 +282,20 @@ class DarwinJVMFinder(LinuxJVMFinder):
     """
     Mac OS X JVM library finder class
     """
+    # Library file name
+    _libfile = "libjli.dylib"
+    # Predefined locations
+    _locations = ('/Library/Java/JavaVirtualMachines',)  # type: ignore
 
     def __init__(self):
         """
         Sets up members
         """
-        # Call the parent constructor
-        LinuxJVMFinder.__init__(self)
-
-        # Library file name
-        self._libfile = "libjli.dylib"
+        super().__init__()
 
         self._methods = list(self._methods)
         self._methods.append(self._javahome_binary)
 
-        # Predefined locations
-        self._locations = ('/Library/Java/JavaVirtualMachines',)
-
     def _javahome_binary(self):
         """
         for osx > 10.5 we have the nice util /usr/libexec/java_home available. Invoke it and
@@ -323,7 +306,8 @@ class DarwinJVMFinder(LinuxJVMFinder):
         from packaging.version import Version
 
         current = Version(platform.mac_ver()[0][:4])
-        if current >= Version('10.6') and current < Version('10.9'):
+        # TODO: check if the java_home tool is still available and fix the version boundaries.
+        if Version('10.6') <= current:  # < Version('10.9'):
             return subprocess.check_output(
                 ['/usr/libexec/java_home']).strip()
 
@@ -358,25 +342,18 @@ def _checkJVMArch(jvmPath, maxsize=sys.m
         raise JVMNotSupportedException("Unable to determine JVM Type")
 
 
-reg_keys = [r"SOFTWARE\JavaSoft\Java Runtime Environment",
-            r"SOFTWARE\JavaSoft\JRE",
-            ]
-
-
 class WindowsJVMFinder(JVMFinder):
     """
     Windows JVM library finder class
     """
+    reg_keys = [r"SOFTWARE\JavaSoft\Java Runtime Environment",
+                r"SOFTWARE\JavaSoft\JRE",
+                ]
+    # Library file name
+    _libfile = "jvm.dll"
 
     def __init__(self):
-        """
-        Sets up members
-        """
-        # Call the parent constructor
-        JVMFinder.__init__(self)
-
-        # Library file name
-        self._libfile = "jvm.dll"
+        super().__init__()
 
         # Search methods
         self._methods = (self._get_from_java_home, self._get_from_registry)
@@ -384,16 +361,20 @@ class WindowsJVMFinder(JVMFinder):
     def check(self, jvm):
         _checkJVMArch(jvm)
 
-    def _get_from_registry(self):
+    @staticmethod
+    def _get_from_registry():
         """
         Retrieves the path to the default Java installation stored in the
         Windows registry
 
         :return: The path found in the registry, or None
         """
-        if not winreg:
+        try:
+            import winreg
+        except ImportError:
             return None
-        for location in reg_keys:
+
+        for location in WindowsJVMFinder.reg_keys:
             try:
                 jreKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, location)
                 cv = winreg.QueryValueEx(jreKey, "CurrentVersion")
diff -pruN 1.5.0-1/jpype/dbapi2.py 1.6.0-1/jpype/dbapi2.py
--- 1.5.0-1/jpype/dbapi2.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/dbapi2.py	2025-06-01 03:57:43.000000000 +0000
@@ -69,7 +69,8 @@ class JDBCType(object):
         _types.append(self)
         if _jpype.isStarted():
             java = _jpype._JPackage("java")
-            self._initialize(java.sql.CallableStatement, java.sql.PreparedStatement, java.sql.ResultSet)
+            self._initialize(java.sql.CallableStatement,
+                             java.sql.PreparedStatement, java.sql.ResultSet)
 
     def _initialize(self, cs, ps, rs):
         """ Called after the JVM starts initialize Java resources """
@@ -96,7 +97,8 @@ class JDBCType(object):
                 return self._csget(rs, column)
             return self._rsget(rs, column)
         except _SQLException as ex:
-            raise InterfaceError("Unable to get '%s' using '%s'" % (self._name, self._getter)) from ex
+            raise InterfaceError("Unable to get '%s' using '%s'" %
+                                 (self._name, self._getter)) from ex
 
     def set(self, ps, column, value):
         """ A method used to set a parameter to a query.
@@ -116,9 +118,11 @@ class JDBCType(object):
             # Otherwise, try to set with the generic method
             return ps.setObject(column, value)
         except TypeError as ex:
-            raise InterfaceError("Unable to convert '%s' into '%s'" % (type(value).__name__, self._name)) from ex
+            raise InterfaceError("Unable to convert '%s' into '%s'" % (
+                type(value).__name__, self._name)) from ex
         except OverflowError as ex:
-            raise InterfaceError("Unable to convert '%s' into '%s' calling '%s'" % (type(value).__name__, self._name, self._setter)) from ex
+            raise InterfaceError("Unable to convert '%s' into '%s' calling '%s'" % (
+                type(value).__name__, self._name, self._setter)) from ex
 
     def __repr__(self):
         if self._name is None:
@@ -143,7 +147,8 @@ class _JDBCTypePrimitive(JDBCType):
                 return None
             return rc
         except _SQLException as ex:
-            raise InterfaceError("Unable to get '%s' using '%s'" % (self._name, self._getter)) from ex
+            raise InterfaceError("Unable to get '%s' using '%s'" %
+                                 (self._name, self._getter)) from ex
 
 
 # From https://www.cis.upenn.edu/~bcpierce/courses/629/jdkdocs/guide/jdbc/getstart/mapping.doc.html
@@ -180,7 +185,8 @@ SQLXML = JDBCType('SQLXML', 2009, 'getSQ
 TIME = JDBCType('TIME', 92, 'getTime', 'setTime')
 TIME_WITH_TIMEZONE = JDBCType('TIME_WITH_TIMEZONE', 2013, 'getTime', 'setTime')
 TIMESTAMP = JDBCType('TIMESTAMP', 93, 'getTimestamp', 'setTimestamp')
-TIMESTAMP_WITH_TIMEZONE = JDBCType('TIMESTAMP_WITH_TIMEZONE', 2014, 'getTimestamp', 'setTimestamp')
+TIMESTAMP_WITH_TIMEZONE = JDBCType(
+    'TIMESTAMP_WITH_TIMEZONE', 2014, 'getTimestamp', 'setTimestamp')
 TINYINT = _JDBCTypePrimitive('TINYINT', -6, 'getShort', 'setShort')
 VARBINARY = JDBCType('VARBINARY', -3, 'getBytes', 'setBytes')
 VARCHAR = JDBCType('VARCHAR', 12, 'getString', 'setString')
@@ -203,8 +209,10 @@ DATETIME = TIMESTAMP
 # Special types
 ASCII_STREAM = JDBCType(None, None, 'getAsciiStream', 'setAsciiStream')
 BINARY_STREAM = JDBCType(None, None, 'getBinaryStream', 'setBinaryStream')
-CHARACTER_STREAM = JDBCType(None, None, 'getCharacterStream', 'setCharacterStream')
-NCHARACTER_STREAM = JDBCType(None, None, 'getNCharacterStream', 'setNCharacterStream')
+CHARACTER_STREAM = JDBCType(
+    None, None, 'getCharacterStream', 'setCharacterStream')
+NCHARACTER_STREAM = JDBCType(
+    None, None, 'getNCharacterStream', 'setNCharacterStream')
 URL = JDBCType(None, None, 'getURL', 'setURL')
 
 # The converters are defined in a customizer
@@ -244,8 +252,8 @@ def SETTERS_BY_META(cx, meta, col, ptype
     """ Option for setters to use the metadata of the parameters.
 
     On some databases this option is useless as they do not track parameter
-    types.  This method can be cached for faster performance when lots of
-    parameters.  Usually types can only be determined accurately on inserts
+    types.  This method can be cached for faster performance when the are lots
+    of parameters.  Usually types can only be determined accurately on inserts
     into defined columns.
     """
     return _default_map[_registry[meta.getParameterType(col + 1)]]
@@ -282,12 +290,12 @@ def GETTERS_BY_TYPE(cx, meta, idx):
 
 
 def GETTERS_BY_NAME(cx, meta, idx):
-    """ Option to getters to determine column type by the column name.
+    """ Option for getters to determine column type by the column name.
 
     This option uses the column name to select the type.  It looks up
     the column type name, converts it to uppercase, and then searches
-    for a matchine type.  It falls back to the type code meta information if
-    the typename can not be found in the registery.  New types can be created
+    for a matching type.  It falls back to the type code meta information if
+    the type name cannot be found in the registry.  New types can be created
     using JDBCType for database specific types such as ``JSON``.
     """
     name = str(meta.getColumnTypeName(idx + 1)).upper()
@@ -388,7 +396,7 @@ def connect(dsn, *, driver=None, driver_
        dsn (str): The database connection string for JDBC.
        driver (str, optional): A JDBC driver to load.
        driver_args: Arguments to the driver.  This may either be a dict,
-          java.util.Properties.  If not supplied, kwargs are used as as the
+          java.util.Properties.  If not supplied, kwargs are used as the
           parameters for the JDBC connection.
        *kwargs: Arguments to the driver if not supplied as
           driver_args.
@@ -401,8 +409,10 @@ def connect(dsn, *, driver=None, driver_
     """
     Properties = _jpype.JClass("java.util.Properties")
     if driver:
-        _jpype.JClass('java.lang.Class').forName(driver).newInstance()
+        _jpype.JClass('java.lang.Class').forName(
+            driver, True, _jpype.JPypeClassLoader).newInstance()
     DM = _jpype.JClass('java.sql.DriverManager')
+    # DM.setLogStream(_jpype.JClass("java.lang.System").out)
 
     # User is supplying Java properties
     if isinstance(driver_args, Properties):
@@ -432,7 +442,7 @@ def connect(dsn, *, driver=None, driver_
 class Connection(object):
     """ Connection provides access to a JDBC database.
 
-    Connections are managed and can be as part of a Python with statement.
+    Connections are managed and can be used as part of a Python 'with' statement.
     Connections close automatically when they are garbage collected, at the
     end of a with statement scope, or when manually closed.  Once a connection
     is closed all operations using the database will raise an Error.
@@ -528,7 +538,7 @@ class Connection(object):
 
     @property
     def setters(self):
-        """ Setter are used to set parameters to ``.execute*()`` methods.
+        """ Setters are used to set parameters to ``.execute*()`` methods.
 
         Setters should be a function taking (connection, meta, col, type) -> JDBCTYPE
         """
@@ -578,7 +588,7 @@ class Connection(object):
     def commit(self):
         """Commit any pending transaction to the database.
 
-        Calling commit on a cooonection that does not support the operation
+        Calling commit on a connection that does not support the operation
         will raise NotSupportedError.
         """
         self._validate()
@@ -593,8 +603,8 @@ class Connection(object):
         """Rollback the transaction.
 
         This method is optional since not all databases provide transaction
-        support.  Calling rollback on a cooonection that does not support will
-        raise NotSupportedError.
+        support.  Calling rollback on a connection that does not support it 
+        will raise NotSupportedError.
 
         In case a database does provide transactions this method causes the
         database to roll back to the start of any pending transaction. Closing
@@ -707,7 +717,8 @@ class Cursor(object):
         if types is None:
             types = [None] * count
         if isinstance(params, str):
-            raise _UnsupportedTypeError("parameters must be a sequence of values")
+            raise _UnsupportedTypeError(
+                "parameters must be a sequence of values")
         if isinstance(params, typing.Sequence):
             if count != len(params):
                 raise ProgrammingError("incorrect number of parameters (%d!=%d)"
@@ -725,7 +736,8 @@ class Cursor(object):
                     s = cx._setters(cx, meta, i, type(p))
                     types[i] = s
                 if s is None:
-                    raise _UnsupportedTypeError("no setter found for '%s'" % type(p).__name__)
+                    raise _UnsupportedTypeError(
+                        "no setter found for '%s'" % type(p).__name__)
                 s.set(self._statement, i + 1, p)
             # Cache
             if hasattr(cx._setters, "_cachable"):
@@ -735,7 +747,8 @@ class Cursor(object):
         elif isinstance(params, typing.Iterable):
             for i, p in enumerate(params):
                 if i >= count:
-                    raise ProgrammingError("incorrect number of parameters (%d!=%d)" % (count, i + 1))
+                    raise ProgrammingError(
+                        "incorrect number of parameters (%d!=%d)" % (count, i + 1))
 
                 # Find and apply the adapter
                 a = cx._adapters.get(type(p), None)
@@ -747,15 +760,18 @@ class Cursor(object):
                     s = cx._setters(cx, meta, i, type(p))
                     types[i] = s
                 if s is None:
-                    raise _UnsupportedTypeError("no setter found for '%s'" % type(p).__name__)
+                    raise _UnsupportedTypeError(
+                        "no setter found for '%s'" % type(p).__name__)
                 s.set(self._statement, i + 1, p)
             if count != i + 1:
-                raise ProgrammingError("incorrect number of parameters (%d!=%d)" % (count, i + 1))
+                raise ProgrammingError(
+                    "incorrect number of parameters (%d!=%d)" % (count, i + 1))
             # Cache
             if hasattr(cx._setters, "_cachable"):
                 self._parameterTypes = types
         else:
-            raise _UnsupportedTypeError("'%s' parameters not supported" % (type(params).__name__))  # pragma: no cover
+            raise _UnsupportedTypeError("'%s' parameters not supported" % (
+                type(params).__name__))  # pragma: no cover
 
     def _onResultSet(self, rs):
         meta = rs.getMetaData()
@@ -839,8 +855,8 @@ class Cursor(object):
 
     @property
     def parameters(self):
-        """ (extension) Parameters is read-only attribute is a sequence of
-        6-item sequences.
+        """ (extension) Parameters is a read-only attribute. It is a sequence
+        of 6-item sequences.
 
         Each of these sequences contains information describing one result
         column:
@@ -869,7 +885,7 @@ class Cursor(object):
 
     @property
     def description(self):
-        """ Description is read-only attribute is a sequence of 7-item
+        """ Description is a read-only attribute. It is a sequence of 7-item
         sequences.
 
         Each of these sequences contains information describing one result
@@ -909,7 +925,7 @@ class Cursor(object):
         .execute*() affected (for DML statements like UPDATE or INSERT).
 
         The attribute is -1 in case no .execute*() has been performed on the
-        cursor or the rowcount of the last operation is cannot be determined by
+        cursor or the rowcount of the last operation cannot be determined by
         the interface.  JDBC does not support getting the number of rows
         returned from SELECT, so for most drivers rowcount will be -1 after a
         SELECT statement.
@@ -948,10 +964,13 @@ class Cursor(object):
             self._validate()
             self._finish()
             if not isinstance(procname, str):
-                raise _UnsupportedTypeError("procname must be str, not '%s'" % type(procname).__name__)
+                raise _UnsupportedTypeError(
+                    "procname must be str, not '%s'" % type(procname).__name__)
             if not isinstance(parameters, typing.Sequence):
-                raise _UnsupportedTypeError("parameters must be sequence, not '%s'" % type(procname).__name__)
-            query = "{CALL %s(%s)}" % (procname, ",".join("?" * len(parameters)))
+                raise _UnsupportedTypeError(
+                    "parameters must be sequence, not '%s'" % type(procname).__name__)
+            query = "{CALL %s(%s)}" % (
+                procname, ",".join("?" * len(parameters)))
             try:
                 self._statement = self._jcx.prepareCall(query)
             except _SQLException as ex:
@@ -966,7 +985,8 @@ class Cursor(object):
                 types = [None] * count
             else:
                 if len(types) != count:
-                    raise ProgrammingError("expected '%d' types, got '%d'" % (count, len(types)))
+                    raise ProgrammingError(
+                        "expected '%d' types, got '%d'" % (count, len(types)))
             for i in range(count):
                 # Lookup the JDBC Type
                 p = parameters[i]
@@ -978,7 +998,8 @@ class Cursor(object):
                     types[i] = cx._setters(cx, meta, i, type(p))
                 jdbcType = types[i]
                 if jdbcType is None:
-                    raise _UnsupportedTypeError("no setter found for '%s'" % type(p).__name__)
+                    raise _UnsupportedTypeError(
+                        "no setter found for '%s'" % type(p).__name__)
 
                 mode = meta.getParameterMode(i + 1)
                 if mode == 1:
@@ -1031,7 +1052,7 @@ class Cursor(object):
         Parameters:
            operation (str): A statement to be executed.
            parameters (list, optional): A list of parameters for the statement.
-              The number of parameters much match the number required by the
+              The number of parameters must match the number required by the
               statement or an Error will be raised.
            keys (bool, optional): Specify if the keys should be available to 
               retrieve. (Default False) 
@@ -1046,7 +1067,8 @@ class Cursor(object):
         if parameters is None:
             parameters = ()
         if not isinstance(parameters, (typing.Sequence, typing.Iterable, typing.Iterator)):
-            raise _UnsupportedTypeError("parameters are of unsupported type '%s'" % type(parameters).__name__)
+            raise _UnsupportedTypeError(
+                "parameters are of unsupported type '%s'" % type(parameters).__name__)
         # complete the previous operation
         try:
             if keys:
@@ -1088,11 +1110,11 @@ class Cursor(object):
         Args:
            operation (str): A statement to be executed.
            seq_of_parameters (list, optional): A list of lists of parameters
-               for the statement.  The number of parameters much match the number
+               for the statement.  The number of parameters must match the number
                required by the statement or an Error will be raised.
            keys (bool, optional): Specify if the keys should be available to 
               retrieve. (Default False) For drivers that do not support
-              batch updates only that last key will be returned.
+              batch updates only the last key will be returned.
 
         Returns:
            This cursor.
@@ -1112,7 +1134,8 @@ class Cursor(object):
         except TypeError as ex:
             raise _UnsupportedTypeError(str(ex))
         except _SQLException as ex:
-            raise ProgrammingError("Failed to prepare '%s'" % operation) from ex
+            raise ProgrammingError(
+                "Failed to prepare '%s'" % operation) from ex
         if self._connection._batch:
             return self._executeBatch(seq_of_parameters)
         else:  # pragma: no cover
@@ -1124,7 +1147,8 @@ class Cursor(object):
                 self._setParams(params)
                 self._statement.addBatch()
         else:
-            raise _UnsupportedTypeError("'%s' is not supported" % type(seq_of_parameters).__name__)
+            raise _UnsupportedTypeError(
+                "'%s' is not supported" % type(seq_of_parameters).__name__)
         try:
             counts = self._statement.executeBatch()
         except _SQLException as ex:  # pragma: no cover
@@ -1147,7 +1171,8 @@ class Cursor(object):
                 except StopIteration:
                     break
         else:
-            raise _UnsupportedTypeError("'%s' is not supported" % str(type(seq_of_parameters)))
+            raise _UnsupportedTypeError(
+                "'%s' is not supported" % str(type(seq_of_parameters)))
         self._rowcount = sum(counts)
         if self._rowcount < 0:
             self._rowcount = -1
@@ -1353,7 +1378,7 @@ def Time(hour, minute, second):
 
 
 def Timestamp(year, month, day, hour, minute, second, nano=0):
-    """ This function constructs an object holding a time stamp value. """
+    """ This function constructs an object holding a timestamp value. """
     return _jpype.JClass('java.sql.Timestamp')(year - 1900, month - 1, day, hour, minute, second, nano)
 
 
@@ -1378,7 +1403,7 @@ def TimeFromTicks(ticks):
 
 def TimestampFromTicks(ticks):
     """
-    This function constructs an object holding a time stamp value from the
+    This function constructs an object holding a timestamp value from the
     given ticks value (number of seconds since the epoch; see the documentation
     of the standard Python time module for details).
     """
diff -pruN 1.5.0-1/jpype/imports.py 1.6.0-1/jpype/imports.py
--- 1.5.0-1/jpype/imports.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/imports.py	2025-06-01 03:57:43.000000000 +0000
@@ -79,9 +79,11 @@ def _JExceptionHandler(pkg, name, ex):
     exname = type(ex).__name__
     ex._expandStacktrace()
     if exname == "java.lang.ExceptionInInitializerError":
-        raise ImportError("Unable to import '%s' due to initializer error" % javaname) from ex
+        raise ImportError(
+            "Unable to import '%s' due to initializer error" % javaname) from ex
     if exname == "java.lang.UnsupportedClassVersionError":
-        raise ImportError("Unable to import '%s' due to incorrect Java version" % javaname) from ex
+        raise ImportError(
+            "Unable to import '%s' due to incorrect Java version" % javaname) from ex
     if exname == "java.lang.NoClassDefFoundError":
         missing = str(ex).replace('/', '.')
         raise ImportError("Unable to import '%s' due to missing dependency '%s'" % (
@@ -152,13 +154,15 @@ class _JImportLoader:
             base = name.partition('.')[0]
             if not base in _JDOMAINS:
                 return None
-            raise ImportError("Attempt to create Java package '%s' without jvm" % name)
+            raise ImportError(
+                "Attempt to create Java package '%s' without jvm" % name)
 
         # Check for aliases
         if name in _JDOMAINS:
             jname = _JDOMAINS[name]
             if not _jpype.isPackage(jname):
-                raise ImportError("Java package '%s' not found, requested by alias '%s'" % (jname, name))
+                raise ImportError(
+                    "Java package '%s' not found, requested by alias '%s'" % (jname, name))
             ms = _ModuleSpec(name, self)
             ms._jname = jname
             return ms
@@ -192,12 +196,14 @@ class _JImportLoader:
             # so we produce a meaningful diagnositic.
             try:
                 # Use forname because it give better diagnostics
-                cls = _jpype._java_lang_Class.forName(name, True, _jpype.JPypeClassLoader)
+                cls = _jpype._java_lang_Class.forName(
+                    name, True, _jpype.JPypeClassLoader)
 
                 # This code only is hit if an error was not thrown
                 if cls.getModifiers() & 1 == 0:
                     raise ImportError("Class `%s` is not public" % name)
-                raise ImportError("Class `%s` was found but was not expected" % name)
+                raise ImportError(
+                    "Class `%s` was found but was not expected" % name)
             # Not found is acceptable
             except Exception as ex:
                 raise ImportError("Failed to import '%s'" % name) from ex
diff -pruN 1.5.0-1/jpype/nio.py 1.6.0-1/jpype/nio.py
--- 1.5.0-1/jpype/nio.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/nio.py	2025-06-01 03:57:43.000000000 +0000
@@ -21,12 +21,9 @@ __all__ = ['convertToDirectBuffer']
 
 
 def convertToDirectBuffer(obj):
-    __doc__ = '''Efficiently convert all array.array and numpy ndarray types, string and unicode to java.nio.Buffer objects.'''
+    __doc__ = '''Efficiently convert all array.array and numpy ndarray types, string and unicode to java.nio.Buffer objects. If the passed object is readonly (i.e. bytes or a readonly memoryview) the returned ByteBuffer will be a readonly Buffer object. Otherwise a writable Buffer is returned.'''
 
     memoryview_of_obj = memoryview(obj)
+    ro_view = memoryview_of_obj.readonly
 
-    if memoryview_of_obj.readonly:
-        raise ValueError(
-            "Memoryview must be writable for wrapping in a byte buffer")
-
-    return _jpype.convertToDirectBuffer(memoryview_of_obj)
+    return _jpype.convertToDirectBuffer(memoryview_of_obj, ro_view)
diff -pruN 1.5.0-1/jpype/protocol.py 1.6.0-1/jpype/protocol.py
--- 1.5.0-1/jpype/protocol.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/jpype/protocol.py	2025-06-01 03:57:43.000000000 +0000
@@ -49,7 +49,8 @@ else:
     # 3.8 onward
     from typing import Protocol, runtime_checkable
     from typing import SupportsIndex, SupportsFloat  # lgtm [py/unused-import]
-    from typing import Sequence, Mapping, Set, Callable  # lgtm [py/unused-import]
+    # lgtm [py/unused-import]
+    from typing import Sequence, Mapping, Set, Callable
 
 # Types we need
 
diff -pruN 1.5.0-1/native/build.xml 1.6.0-1/native/build.xml
--- 1.5.0-1/native/build.xml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/build.xml	2025-06-01 03:57:43.000000000 +0000
@@ -1,10 +1,10 @@
 <project default="all" name="JPype - Native">
 
 	<!-- JAVA_VERSION must match the oldest supported Java version -->
-	<property name="JAVA_VERSION" value="1.8" />
+	<property name="JAVA_VERSION" value="11" />
 
 	<!-- src can't be "java" as it breaks nose tests -->
-	<property name="src" location="java"/>
+	<property name="src" location="jpype_module/src/main/java"/>
 	<property name="build" location="build"/>
 
 	<target name="test" depends="compile">
@@ -12,18 +12,23 @@
 
 	<target name="compile">
 		<mkdir dir="${build}/classes"/>
+		<mkdir dir="${build}/classes/META-INF"/>
+		<mkdir dir="${build}/classes/META-INF/versions"/>
+		<mkdir dir="${build}/classes/META-INF/versions/0"/>
 		<mkdir dir="${build}/lib"/>
 		<javac destdir="${build}/classes"
 			source="${JAVA_VERSION}"
 			target="${JAVA_VERSION}"
-			excludes="**/JPypeClassLoader.java"
+			excludes="**/Reflector0.java.java"
+            includeantruntime="false"
 			>
 			<src path="${src}"/>
 		</javac>
-		<javac destdir="${build}/lib"
+		<javac destdir="${build}/classes/META-INF/versions/0"
 			source="${JAVA_VERSION}"
 			target="${JAVA_VERSION}"
-			includes="**/JPypeClassLoader.java"
+			includes="**/Reflector0.java"
+            includeantruntime="false"
 			>
 			<src path="${src}"/>
 		</javac>
diff -pruN 1.5.0-1/native/common/include/jp_array.h 1.6.0-1/native/common/include/jp_array.h
--- 1.5.0-1/native/common/include/jp_array.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_array.h	2025-06-01 03:57:43.000000000 +0000
@@ -28,7 +28,6 @@ public:
 	~JPArrayView();
 	void reference();
 	bool unreference();
-	JPContext *getContext() const;
 public:
 	JPArray *m_Array;
 	void *m_Memory{};
diff -pruN 1.5.0-1/native/common/include/jp_booleantype.h 1.6.0-1/native/common/include/jp_booleantype.h
--- 1.5.0-1/native/common/include/jp_booleantype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_booleantype.h	2025-06-01 03:57:43.000000000 +0000
@@ -36,15 +36,11 @@ public:
 		return v.z;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Boolean;
-	}
-
-	JPMatch::Type findJavaConversion(JPMatch& match) override;
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
+	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
 	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -72,12 +68,12 @@ public:
 
 	jlong getAsLong(jvalue v) override
 	{
-		return field(v);
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
 
 	jdouble getAsDouble(jvalue v) override
 	{
-		return field(v);
+		return (jdouble) field(v);
 	}
 	// GCOVR_EXCL_STOP
 
diff -pruN 1.5.0-1/native/common/include/jp_bytetype.h 1.6.0-1/native/common/include/jp_bytetype.h
--- 1.5.0-1/native/common/include/jp_bytetype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_bytetype.h	2025-06-01 03:57:43.000000000 +0000
@@ -23,7 +23,6 @@ public:
 	JPByteType();
 	~JPByteType() override;
 
-public:
 	using type_t = jbyte;
 	using array_t = jbyteArray;
 
@@ -37,15 +36,11 @@ public:
 		return v.b;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Byte;
-	}
-
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
 	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
-	JPPyObject  convertToPythonObject(JPJavaFrame &frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -56,7 +51,9 @@ public:
 	void        setField(JPJavaFrame& frame, jobject c, jfieldID fid, PyObject* val) override;
 
 	jarray      newArrayOf(JPJavaFrame& frame, jsize size) override;
-	void        setArrayRange(JPJavaFrame& frame, jarray, jsize start, jsize length, jsize step, PyObject*) override;
+	void        setArrayRange(JPJavaFrame& frame, jarray,
+			jsize start, jsize length, jsize step,
+			PyObject *sequence) override;
 	JPPyObject  getArrayItem(JPJavaFrame& frame, jarray, jsize ndx) override;
 	void        setArrayItem(JPJavaFrame& frame, jarray, jsize ndx, PyObject* val) override;
 
@@ -65,23 +62,26 @@ public:
 		return 'B';
 	}
 
-	template <class T> static T assertRange(const T& l)
+	// GCOVR_EXCL_START
+	// Required but not exercised currently
+	jlong getAsLong(jvalue v) override
 	{
-		if (l < -128 || l > 127)
-		{
-			JP_RAISE(PyExc_OverflowError, "Cannot convert value to Java byte");
-		}
-		return l;
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
+	// GCOVR_EXCL_STOP
 
-	jlong getAsLong(jvalue v) override
+	jdouble getAsDouble(jvalue v) override
 	{
-		return field(v);
+		return (jdouble) field(v);
 	}
 
-	jdouble getAsDouble(jvalue v) override
+	template <class T> static T assertRange(const T& l)
 	{
-		return field(v);
+		if (l < -128 || l > 127)
+		{
+			JP_RAISE(PyExc_OverflowError, "Cannot convert value to Java byte");
+		}
+		return l;
 	}
 
 	void getView(JPArrayView& view) override;
@@ -94,10 +94,6 @@ public:
 
 	PyObject *newMultiArray(JPJavaFrame &frame,
 			JPPyBuffer &buffer, int subs, int base, jobject dims) override;
-
-private:
-	static const jlong _Byte_Min = 127;
-	static const jlong _Byte_Max = -128;
 } ;
 
 #endif // _JPBYTE_TYPE_H_
diff -pruN 1.5.0-1/native/common/include/jp_chartype.h 1.6.0-1/native/common/include/jp_chartype.h
--- 1.5.0-1/native/common/include/jp_chartype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_chartype.h	2025-06-01 03:57:43.000000000 +0000
@@ -23,7 +23,7 @@ public:
 	JPCharType();
 	~JPCharType() override;
 
-public:
+
 	using type_t = jchar;
 	using array_t = jcharArray;
 
@@ -39,15 +39,11 @@ public:
 		return v.c;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Character;
-	}
-
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
 	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
 	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -58,7 +54,9 @@ public:
 	void        setField(JPJavaFrame& frame, jobject c, jfieldID fid, PyObject* val) override;
 
 	jarray      newArrayOf(JPJavaFrame& frame, jsize size) override;
-	void        setArrayRange(JPJavaFrame& frame, jarray, jsize start, jsize length, jsize step, PyObject*) override;
+	void        setArrayRange(JPJavaFrame& frame, jarray,
+			jsize start, jsize length, jsize step,
+			PyObject *sequence) override;
 	JPPyObject  getArrayItem(JPJavaFrame& frame, jarray, jsize ndx) override;
 	void        setArrayItem(JPJavaFrame& frame, jarray, jsize ndx, PyObject* val) override;
 
@@ -67,14 +65,17 @@ public:
 		return 'C';
 	}
 
+	// GCOVR_EXCL_START
+	// Required but not exercised currently
 	jlong getAsLong(jvalue v) override
 	{
-		return field(v);
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
+	// GCOVR_EXCL_STOP
 
 	jdouble getAsDouble(jvalue v) override
 	{
-		return field(v);
+		return (jdouble) field(v);
 	}
 
 	void getView(JPArrayView& view) override;
diff -pruN 1.5.0-1/native/common/include/jp_class.h 1.6.0-1/native/common/include/jp_class.h
--- 1.5.0-1/native/common/include/jp_class.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_class.h	2025-06-01 03:57:43.000000000 +0000
@@ -40,10 +40,7 @@ public:
 
 	void setHints(PyObject* host);
 
-	PyObject* getHints()
-	{
-		return m_Hints.get();
-	}
+	PyObject* getHints();
 
 public:
 	void ensureMembers(JPJavaFrame& frame);
@@ -149,7 +146,7 @@ public:
 	 *
 	 * @return a java value with class.
 	 */
-	virtual JPValue getValueFromObject(const JPValue& obj);
+	virtual JPValue getValueFromObject(JPJavaFrame& frame, const JPValue& obj);
 
 	/**
 	 * Call a static method that returns this type of object.
@@ -201,10 +198,7 @@ public:
 		return m_Interfaces;
 	}
 
-	JPContext* getContext() const;
-
 protected:
-	JPContext*           m_Context;
 	JPClassRef           m_Class;
 	JPClass*             m_SuperClass;
 	JPClassList          m_Interfaces;
@@ -217,4 +211,4 @@ protected:
 	JPPyObject           m_Hints;
 } ;
 
-#endif // _JPPOBJECTTYPE_H_
\ No newline at end of file
+#endif // _JPPOBJECTTYPE_H_
diff -pruN 1.5.0-1/native/common/include/jp_classhints.h 1.6.0-1/native/common/include/jp_classhints.h
--- 1.5.0-1/native/common/include/jp_classhints.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_classhints.h	2025-06-01 03:57:43.000000000 +0000
@@ -98,6 +98,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info);
 
+	bool m_ConvertJava;
 private:
 	std::list<JPConversion*> conversions;
 } ;
@@ -121,4 +122,4 @@ extern JPConversion *boxDoubleConversion
 extern JPConversion *unboxConversion;
 extern JPConversion *proxyConversion;
 
-#endif /* JP_CLASSHINTS_H */
\ No newline at end of file
+#endif /* JP_CLASSHINTS_H */
diff -pruN 1.5.0-1/native/common/include/jp_classloader.h 1.6.0-1/native/common/include/jp_classloader.h
--- 1.5.0-1/native/common/include/jp_classloader.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_classloader.h	2025-06-01 03:57:43.000000000 +0000
@@ -46,11 +46,10 @@ public:
 	jobject getBootLoader();
 
 private:
-	JPContext* m_Context;
 	JPClassRef m_ClassClass;
 	JPObjectRef m_SystemClassLoader;
 	JPObjectRef m_BootLoader;
 	jmethodID m_ForNameID;
 } ;
 
-#endif // _JPCLASSLOADER_H_
\ No newline at end of file
+#endif // _JPCLASSLOADER_H_
diff -pruN 1.5.0-1/native/common/include/jp_context.h 1.6.0-1/native/common/include/jp_context.h
--- 1.5.0-1/native/common/include/jp_context.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_context.h	2025-06-01 03:57:43.000000000 +0000
@@ -26,31 +26,25 @@ template <class jref>
 class JPRef
 {
 private:
-	JPContext* m_Context;
 	jref m_Ref;
 
 public:
 
 	JPRef()
 	{
-		m_Context = nullptr;
 		m_Ref = 0;
 	}
 
-	JPRef(JPContext* context, jref obj)
+	JPRef(jref obj)
 	{
-		m_Context = context;
 		m_Ref = 0;
-		if (context == nullptr)
-			return;
-		JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+		JPJavaFrame frame = JPJavaFrame::outer();
 		m_Ref = (jref) frame.NewGlobalRef((jobject) obj);
 	}
 
 	JPRef(JPJavaFrame& frame, jref obj)
 	{
 
-		m_Context = frame.getContext();
 		m_Ref = 0;
 		m_Ref = (jref) frame.NewGlobalRef((jobject) obj);
 	}
@@ -220,10 +214,10 @@ private:
 public:
 	JPClassRef m_ContextClass;
 	JPClassRef m_RuntimeException;
-	JPClassRef m_NoSuchMethodError;
 
 private:
 	JPClassRef m_Array;
+	JPObjectRef m_Reflector;
 
 	// Java Functions
 	jmethodID m_Object_ToStringID{};
@@ -240,6 +234,7 @@ private:
 	jmethodID m_Context_ClearInterruptID{};
 	jmethodID m_CompareToID{};
 	jmethodID m_Buffer_IsReadOnlyID{};
+	jmethodID m_Buffer_AsReadOnlyID{};
 	jmethodID m_Context_OrderID{};
 	jmethodID m_Object_GetClassID{};
 	jmethodID m_Array_NewInstanceID{};
@@ -271,6 +266,8 @@ public:
 	std::list<JPResource*> m_Resources;
 } ;
 
+extern "C" JPContext* JPContext_global;
+
 extern void JPRef_failed();
 
 // GCOVR_EXCL_START
@@ -279,10 +276,9 @@ extern void JPRef_failed();
 template<class jref>
 JPRef<jref>::JPRef(const JPRef& other)
 {
-	m_Context = other.m_Context;
-	if (m_Context != nullptr)
+	if (JPContext_global != nullptr)
 	{
-		JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv());
+		JPJavaFrame frame = JPJavaFrame::external(JPContext_global->getEnv());
 		m_Ref = (jref) frame.NewGlobalRef((jobject) other.m_Ref);
 	} else
 	{
@@ -294,9 +290,9 @@ JPRef<jref>::JPRef(const JPRef& other)
 template<class jref>
 JPRef<jref>::~JPRef()
 {
-	if (m_Ref != 0 && m_Context != nullptr)
+	if (m_Ref != 0)
 	{
-		m_Context->ReleaseGlobalRef((jobject) m_Ref);
+		JPContext_global->ReleaseGlobalRef((jobject) m_Ref);
 	}
 }
 
@@ -307,18 +303,17 @@ JPRef<jref>& JPRef<jref>::operator=(cons
 		return *this;
 	// m_Context may or may not be set up here, so we need to use a
 	// different frame for unreferencing and referencing
-	if (m_Context != nullptr && m_Ref != 0)
+	if (JPContext_global != nullptr && m_Ref != 0)
 	{  // GCOVR_EXCL_START
 		// This code is not currently used.
-		JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv());
+		JPJavaFrame frame = JPJavaFrame::external(JPContext_global->getEnv());
 		if (m_Ref != 0)
 			frame.DeleteGlobalRef((jobject) m_Ref);
 	}  // GCOVR_EXCL_STOP
-	m_Context = other.m_Context;
 	m_Ref = other.m_Ref;
-	if (m_Context != nullptr && m_Ref != 0)
+	if (JPContext_global != nullptr && m_Ref != 0)
 	{
-		JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv());
+		JPJavaFrame frame = JPJavaFrame::external(JPContext_global->getEnv());
 		m_Ref = (jref) frame.NewGlobalRef((jobject) m_Ref);
 	}
 	return *this;
diff -pruN 1.5.0-1/native/common/include/jp_doubletype.h 1.6.0-1/native/common/include/jp_doubletype.h
--- 1.5.0-1/native/common/include/jp_doubletype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_doubletype.h	2025-06-01 03:57:43.000000000 +0000
@@ -23,7 +23,6 @@ public:
 	JPDoubleType();
 	~JPDoubleType() override = default;
 
-public:
 	using type_t = jdouble;
 	using array_t = jdoubleArray;
 
@@ -37,18 +36,14 @@ public:
 		return v.d;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Double;
-	}
-
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
 	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
-	JPPyObject  convertToPythonObject(JPJavaFrame &frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
-	JPPyObject  invokeStatic(JPJavaFrame &frame, jclass, jmethodID, jvalue*) override;
-	JPPyObject  invoke(JPJavaFrame &frame, jobject, jclass, jmethodID, jvalue*) override;
+	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
+	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
 
 	JPPyObject  getStaticField(JPJavaFrame& frame, jclass c, jfieldID fid) override;
 	void        setStaticField(JPJavaFrame& frame, jclass c, jfieldID fid, PyObject* val) override;
@@ -57,7 +52,8 @@ public:
 
 	jarray      newArrayOf(JPJavaFrame& frame, jsize size) override;
 	void        setArrayRange(JPJavaFrame& frame, jarray,
-			jsize start, jsize length, jsize step, PyObject*) override;
+			jsize start, jsize length, jsize step,
+			PyObject *sequence) override;
 	JPPyObject  getArrayItem(JPJavaFrame& frame, jarray, jsize ndx) override;
 	void        setArrayItem(JPJavaFrame& frame, jarray, jsize ndx, PyObject* val) override;
 
@@ -66,13 +62,11 @@ public:
 		return 'D';
 	}
 
-	// GCOV_EXCL_START
-	// These are required for primitives, but converters for do not currently
-	// use them.
-
+	// GCOVR_EXCL_START
+	// Required but not exercised currently
 	jlong getAsLong(jvalue v) override
 	{
-		return (jlong) field(v);
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
 
 	jdouble getAsDouble(jvalue v) override
@@ -90,7 +84,7 @@ public:
 			void* memory, int offset) override;
 
 	PyObject *newMultiArray(JPJavaFrame &frame,
-			JPPyBuffer& view, int subs, int base, jobject dims) override;
+			JPPyBuffer &buffer, int subs, int base, jobject dims) override;
 } ;
 
 #endif // _JP_DOUBLE_TYPE_H_
diff -pruN 1.5.0-1/native/common/include/jp_exception.h 1.6.0-1/native/common/include/jp_exception.h
--- 1.5.0-1/native/common/include/jp_exception.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_exception.h	2025-06-01 03:57:43.000000000 +0000
@@ -58,7 +58,6 @@ _python_error,
 _python_exc,
 _os_error_unix,
 _os_error_windows,
-_method_not_found,
 };
 
 // Create a stackinfo for a particular location in the code that can then
@@ -135,7 +134,7 @@ public:
 	void from(const JPStackInfo& info);
 
 	void convertJavaToPython();
-	void convertPythonToJava(JPContext* context);
+	void convertPythonToJava();
 
 	/** Transfer handling of this exception to python.
 	 *
@@ -145,19 +144,24 @@ public:
 	void toPython();
 
 	/** Transfer handling of this exception to java. */
-	void toJava(JPContext* context);
+	void toJava();
 
 	int getExceptionType() const
 	{
 		return m_Type;
 	}
 
+	jthrowable getThrowable()
+	{
+		return m_Throwable.get();
+	}
+
 private:
-	JPContext* m_Context{};
 	int m_Type;
 	JPErrorUnion m_Error{};
 	JPStackTrace m_Trace;
 	JPThrowableRef m_Throwable;
+	std::string m_Message;
 };
 
-#endif
\ No newline at end of file
+#endif
diff -pruN 1.5.0-1/native/common/include/jp_field.h 1.6.0-1/native/common/include/jp_field.h
--- 1.5.0-1/native/common/include/jp_field.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_field.h	2025-06-01 03:57:43.000000000 +0000
@@ -47,11 +47,6 @@ public:
 		return this->m_Field.get();
 	}
 
-	JPContext* getContext()
-	{
-		return m_Class->getContext();
-	}
-
 	const string& getName() const
 	{
 		return m_Name;
@@ -87,4 +82,4 @@ private:
 	jint             m_Modifiers;
 } ;
 
-#endif // _JPFIELD_H_
\ No newline at end of file
+#endif // _JPFIELD_H_
diff -pruN 1.5.0-1/native/common/include/jp_floattype.h 1.6.0-1/native/common/include/jp_floattype.h
--- 1.5.0-1/native/common/include/jp_floattype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_floattype.h	2025-06-01 03:57:43.000000000 +0000
@@ -23,7 +23,6 @@ public:
 	JPFloatType();
 	~JPFloatType() override;
 
-public:
 	using type_t = jfloat;
 	using array_t = jfloatArray;
 
@@ -37,15 +36,11 @@ public:
 		return v.f;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Float;
-	}
-
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
 	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
-	JPPyObject  convertToPythonObject(JPJavaFrame &frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -58,7 +53,7 @@ public:
 	jarray      newArrayOf(JPJavaFrame& frame, jsize size) override;
 	void        setArrayRange(JPJavaFrame& frame, jarray,
 			jsize start, jsize length, jsize step,
-			PyObject* sequence) override;
+			PyObject *sequence) override;
 	JPPyObject  getArrayItem(JPJavaFrame& frame, jarray, jsize ndx) override;
 	void        setArrayItem(JPJavaFrame& frame, jarray, jsize ndx, PyObject* val) override;
 
@@ -72,7 +67,7 @@ public:
 
 	jlong getAsLong(jvalue v) override
 	{
-		return (jlong) field(v);
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
 	// GCOVR_EXCL_STOP
 
diff -pruN 1.5.0-1/native/common/include/jp_gc.h 1.6.0-1/native/common/include/jp_gc.h
--- 1.5.0-1/native/common/include/jp_gc.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_gc.h	2025-06-01 03:57:43.000000000 +0000
@@ -30,7 +30,7 @@ class JPGarbageCollection
 {
 public:
 
-	explicit JPGarbageCollection(JPContext *context);
+	explicit JPGarbageCollection();
 
 	void init(JPJavaFrame& frame);
 
@@ -50,14 +50,20 @@ public:
 	void getStats(JPGCStats& stats);
 
 private:
-	JPContext *m_Context;
 	bool running;
 	bool in_python_gc;
 	bool java_triggered;
 	PyObject *python_gc;
 	jclass _SystemClass;
+	jclass _ContextClass;
 	jmethodID _gcMethodID;
 
+	jmethodID _totalMemoryID;
+	jmethodID _freeMemoryID;
+	jmethodID _maxMemoryID;
+	jmethodID _usedMemoryID;
+	jmethodID _heapMemoryID;
+
 	size_t last_python;
 	size_t last_java;
 	size_t low_water;
@@ -69,4 +75,4 @@ private:
 	int python_triggered;
 } ;
 
-#endif /* JP_GC_H */
\ No newline at end of file
+#endif /* JP_GC_H */
diff -pruN 1.5.0-1/native/common/include/jp_inttype.h 1.6.0-1/native/common/include/jp_inttype.h
--- 1.5.0-1/native/common/include/jp_inttype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_inttype.h	2025-06-01 03:57:43.000000000 +0000
@@ -23,7 +23,6 @@ public:
 	JPIntType();
 	~JPIntType() override;
 
-public:
 	using type_t = jint;
 	using array_t = jintArray;
 
@@ -37,15 +36,11 @@ public:
 		return v.i;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Integer;
-	}
-
-	JPMatch::Type findJavaConversion(JPMatch& match) override;
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
+	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
 	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -58,7 +53,7 @@ public:
 	jarray      newArrayOf(JPJavaFrame& frame, jsize size) override;
 	void        setArrayRange(JPJavaFrame& frame, jarray,
 			jsize start, jsize length, jsize step,
-			PyObject* sequence) override;
+			PyObject *sequence) override;
 	JPPyObject  getArrayItem(JPJavaFrame& frame, jarray, jsize ndx) override;
 	void        setArrayItem(JPJavaFrame& frame, jarray, jsize ndx, PyObject* val) override;
 
@@ -67,14 +62,17 @@ public:
 		return 'I';
 	}
 
-	jlong getAsLong(jvalue v) override  // GCOVR_EXCL_LINE
+	// GCOVR_EXCL_START
+	// Required but not exercised currently
+	jlong getAsLong(jvalue v) override
 	{
-		return field(v);  // GCOVR_EXCL_LINE
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
+	// GCOVR_EXCL_STOP
 
 	jdouble getAsDouble(jvalue v) override
 	{
-		return field(v);
+		return (jdouble) field(v);
 	}
 
 	static jlong assertRange(const jlong& l)
diff -pruN 1.5.0-1/native/common/include/jp_javaframe.h 1.6.0-1/native/common/include/jp_javaframe.h
--- 1.5.0-1/native/common/include/jp_javaframe.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_javaframe.h	2025-06-01 03:57:43.000000000 +0000
@@ -43,17 +43,14 @@
  */
 static const int LOCAL_FRAME_DEFAULT = 8;
 
-class JPContext;
-
 class JPJavaFrame
 {
-	JPContext* m_Context;
 	JNIEnv* m_Env;
 	bool m_Popped;
 	bool m_Outer;
 
 private:
-	JPJavaFrame(JPContext* context, JNIEnv* env, int size, bool outer);
+	JPJavaFrame(JNIEnv* env, int size, bool outer);
 
 public:
 
@@ -68,9 +65,9 @@ public:
 	 * @throws JPypeException if the jpype cannot
 	 * acquire an env handle to work with jvm.
 	 */
-	static JPJavaFrame outer(JPContext* context, int size = LOCAL_FRAME_DEFAULT)
+	static JPJavaFrame outer(int size = LOCAL_FRAME_DEFAULT)
 	{
-		return {context, nullptr, size, true};
+		return {nullptr, size, true};
 	}
 
 	/** Create a new JavaFrame when called internal when
@@ -82,9 +79,9 @@ public:
 	 * @throws JPypeException if the jpype cannot
 	 * acquire an env handle to work with jvm.
 	 */
-	static JPJavaFrame inner(JPContext* context, int size = LOCAL_FRAME_DEFAULT)
+	static JPJavaFrame inner(int size = LOCAL_FRAME_DEFAULT)
 	{
-		return {context, nullptr, size, false};
+		return {nullptr, size, false};
 	}
 
 	/** Create a new JavaFrame when called from Java.
@@ -97,9 +94,9 @@ public:
 	 * @throws JPypeException if the jpype cannot
 	 * acquire an env handle to work with jvm.
 	 */
-	static JPJavaFrame external(JPContext* context, JNIEnv* env, int size = LOCAL_FRAME_DEFAULT)
+	static JPJavaFrame external(JNIEnv* env, int size = LOCAL_FRAME_DEFAULT)
 	{
-		return {context, env, size, false};
+		return {env, size, false};
 	}
 
 	JPJavaFrame(const JPJavaFrame& frame);
@@ -112,6 +109,8 @@ public:
 	 */
 	~JPJavaFrame();
 
+	JPContext* getContext();
+
 	void check();
 
 	/** Exit the local frame and keep a local reference to an object
@@ -160,11 +159,6 @@ public:
 		return m_Env;
 	}
 
-	JPContext* getContext() const
-	{
-		return m_Context;
-	}
-
 	string toString(jobject o);
 	string toStringUTF8(jstring str);
 
@@ -374,6 +368,7 @@ public:
 	void* GetDirectBufferAddress(jobject obj);
 	jlong GetDirectBufferCapacity(jobject obj);
 	jboolean isBufferReadOnly(jobject obj);
+	jobject asReadOnlyBuffer(jobject obj);
 	jboolean orderBuffer(jobject obj);
 	jclass getClass(jobject obj);
 
diff -pruN 1.5.0-1/native/common/include/jp_longtype.h 1.6.0-1/native/common/include/jp_longtype.h
--- 1.5.0-1/native/common/include/jp_longtype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_longtype.h	2025-06-01 03:57:43.000000000 +0000
@@ -36,15 +36,11 @@ public:
 		return v.j;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Long;
-	}
-
-	JPMatch::Type findJavaConversion(JPMatch& match) override;
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
+	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
 	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -68,10 +64,9 @@ public:
 
 	// GCOVR_EXCL_START
 	// Required but not exercised currently
-
 	jlong getAsLong(jvalue v) override
 	{
-		return (jlong) field(v);
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
 	// GCOVR_EXCL_STOP
 
diff -pruN 1.5.0-1/native/common/include/jp_match.h 1.6.0-1/native/common/include/jp_match.h
--- 1.5.0-1/native/common/include/jp_match.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_match.h	2025-06-01 03:57:43.000000000 +0000
@@ -35,13 +35,6 @@ public:
 	JPMatch();
 	JPMatch(JPJavaFrame *frame, PyObject *object);
 
-	JPContext *getContext() const
-	{
-		if (frame == nullptr)
-			return nullptr;
-		return frame->getContext();
-	}
-
 	/**
 	 * Get the Java slot associated with the Python object.
 	 *
diff -pruN 1.5.0-1/native/common/include/jp_methoddispatch.h 1.6.0-1/native/common/include/jp_methoddispatch.h
--- 1.5.0-1/native/common/include/jp_methoddispatch.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_methoddispatch.h	2025-06-01 03:57:43.000000000 +0000
@@ -41,11 +41,6 @@ public:
 		return m_Class;
 	}
 
-	JPContext* getContext()
-	{
-		return m_Class->getContext();
-	}
-
 	const string& getName() const;
 
 	bool hasStatic() const
@@ -89,4 +84,4 @@ private:
 	JPMethodCache m_LastCache{};
 } ;
 
-#endif // _JPMETHODDISPATCH_H_
\ No newline at end of file
+#endif // _JPMETHODDISPATCH_H_
diff -pruN 1.5.0-1/native/common/include/jp_monitor.h 1.6.0-1/native/common/include/jp_monitor.h
--- 1.5.0-1/native/common/include/jp_monitor.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_monitor.h	2025-06-01 03:57:43.000000000 +0000
@@ -19,20 +19,14 @@
 class JPMonitor
 {
 public:
-	JPMonitor(JPContext* context, jobject obj);
+	JPMonitor(jobject obj);
 	virtual ~JPMonitor();
 
 	void enter();
 	void exit();
 
-	JPContext* getContext()
-	{
-		return m_Context;
-	}
-
 private:
-	JPContext* m_Context;
 	JPObjectRef m_Value;
 } ;
 
-#endif // _JPMONITOR_H_
\ No newline at end of file
+#endif // _JPMONITOR_H_
diff -pruN 1.5.0-1/native/common/include/jp_primitivetype.h 1.6.0-1/native/common/include/jp_primitivetype.h
--- 1.5.0-1/native/common/include/jp_primitivetype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_primitivetype.h	2025-06-01 03:57:43.000000000 +0000
@@ -26,7 +26,7 @@ protected:
 public:
 	bool isPrimitive() const override;
 
-	virtual JPClass* getBoxedClass(JPContext *context) const = 0;
+	virtual JPClass* getBoxedClass(JPJavaFrame& frame) const = 0;
 
 	virtual char getTypeCode() = 0;
 	virtual jlong getAsLong(jvalue v) = 0;
@@ -34,7 +34,6 @@ public:
 
 	void setClass(JPJavaFrame& frame, jclass o)
 	{
-		m_Context = frame.getContext();
 		m_Class = JPClassRef(frame, o);
 	}
 
diff -pruN 1.5.0-1/native/common/include/jp_proxy.h 1.6.0-1/native/common/include/jp_proxy.h
--- 1.5.0-1/native/common/include/jp_proxy.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_proxy.h	2025-06-01 03:57:43.000000000 +0000
@@ -24,7 +24,7 @@ class JPProxy
 {
 public:
 	friend class JPProxyType;
-	JPProxy(JPContext* context, PyJPProxy* inst, JPClassList& intf);
+	JPProxy(PyJPProxy* inst, JPClassList& intf);
 	virtual ~JPProxy();
 
 	const JPClassList& getInterfaces() const
@@ -34,16 +34,14 @@ public:
 
 	jvalue getProxy();
 
-	JPContext* getContext()
+	virtual JPPyObject getCallable(const string& cname, int& addSelf) = 0;
+	static void releaseProxyPython(void* host);
+
+	PyJPProxy* getInstance()
 	{
-		return m_Context;
+		return m_Instance;
 	}
 
-	virtual JPPyObject getCallable(const string& cname) = 0;
-	static void releaseProxyPython(void* host);
-
-protected:
-	JPContext*    m_Context;
 	PyJPProxy*    m_Instance;
 	JPObjectRef   m_Proxy;
 	JPClassList   m_InterfaceClasses;
@@ -53,25 +51,25 @@ protected:
 class JPProxyDirect : public JPProxy
 {
 public:
-	JPProxyDirect(JPContext* context, PyJPProxy* inst, JPClassList& intf);
+	JPProxyDirect(PyJPProxy* inst, JPClassList& intf);
 	~JPProxyDirect() override;
-	JPPyObject getCallable(const string& cname) override;
+	JPPyObject getCallable(const string& cname, int& addSelf) override;
 } ;
 
 class JPProxyIndirect : public JPProxy
 {
 public:
-	JPProxyIndirect(JPContext* context, PyJPProxy* inst, JPClassList& intf);
+	JPProxyIndirect(PyJPProxy* inst, JPClassList& intf);
 	~JPProxyIndirect() override;
-	JPPyObject getCallable(const string& cname) override;
+	JPPyObject getCallable(const string& cname, int& addSelf) override;
 } ;
 
 class JPProxyFunctional : public JPProxy
 {
 public:
-	JPProxyFunctional(JPContext* context, PyJPProxy* inst, JPClassList& intf);
+	JPProxyFunctional(PyJPProxy* inst, JPClassList& intf);
 	~JPProxyFunctional() override;
-	JPPyObject getCallable(const string& cname) override;
+	JPPyObject getCallable(const string& cname, int& addSelf) override;
 private:
 	JPFunctional *m_Functional;
 } ;
@@ -98,4 +96,4 @@ private:
 	jfieldID   m_InstanceID;
 } ;
 
-#endif // JPPROXY_H
\ No newline at end of file
+#endif // JPPROXY_H
diff -pruN 1.5.0-1/native/common/include/jp_shorttype.h 1.6.0-1/native/common/include/jp_shorttype.h
--- 1.5.0-1/native/common/include/jp_shorttype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_shorttype.h	2025-06-01 03:57:43.000000000 +0000
@@ -36,15 +36,11 @@ public:
 		return v.s;
 	}
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Short;
-	}
-
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
 	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	void getConversionInfo(JPConversionInfo &info) override;
 	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -66,15 +62,18 @@ public:
 		return 'S';
 	}
 
+	// GCOVR_EXCL_START
+	// Required but not exercised currently
 	jlong getAsLong(jvalue v) override
 	{
-		return field(v);
+		return (jlong) field(v);  // GCOVR_EXCL_LINE
 	}
 
 	jdouble getAsDouble(jvalue v) override
 	{
-		return field(v);
+		return (jdouble) field(v);
 	}
+	// GCOV_EXCL_STOP
 
 	template <class T> static T assertRange(const T& l)
 	{
@@ -95,7 +94,6 @@ public:
 
 	PyObject *newMultiArray(JPJavaFrame &frame,
 			JPPyBuffer &buffer, int subs, int base, jobject dims) override;
-
 } ;
 
 #endif // _JP_SHORT_TYPE_H_
diff -pruN 1.5.0-1/native/common/include/jp_typemanager.h 1.6.0-1/native/common/include/jp_typemanager.h
--- 1.5.0-1/native/common/include/jp_typemanager.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_typemanager.h	2025-06-01 03:57:43.000000000 +0000
@@ -44,7 +44,6 @@ public:
     int interfaceParameterCount(JPClass* cls);
 
 private:
-	JPContext* m_Context;
 	JPObjectRef m_JavaTypeManager;
 	jmethodID m_FindClass;
 	jmethodID m_FindClassByName;
@@ -54,4 +53,4 @@ private:
     jmethodID m_InterfaceParameterCount;
 } ;
 
-#endif // _JPCLASS_H_
\ No newline at end of file
+#endif // _JPCLASS_H_
diff -pruN 1.5.0-1/native/common/include/jp_voidtype.h 1.6.0-1/native/common/include/jp_voidtype.h
--- 1.5.0-1/native/common/include/jp_voidtype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jp_voidtype.h	2025-06-01 03:57:43.000000000 +0000
@@ -23,13 +23,10 @@ public:
 	JPVoidType();
 	~JPVoidType() override;
 
-	JPClass* getBoxedClass(JPContext *context) const override
-	{
-		return context->_java_lang_Void;
-	}
-
+	JPClass* getBoxedClass(JPJavaFrame& frame) const override;
 	JPMatch::Type findJavaConversion(JPMatch &match) override;
 	JPPyObject  convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast) override;
+	JPValue     getValueFromObject(JPJavaFrame& frame, const JPValue& obj) override;
 
 	JPPyObject  invokeStatic(JPJavaFrame& frame, jclass, jmethodID, jvalue*) override;
 	JPPyObject  invoke(JPJavaFrame& frame, jobject, jclass, jmethodID, jvalue*) override;
@@ -45,7 +42,6 @@ public:
 			PyObject *sequence) override;
 	JPPyObject  getArrayItem(JPJavaFrame& frame, jarray, jsize ndx) override;
 	void        setArrayItem(JPJavaFrame& frame, jarray, jsize ndx, PyObject* val) override;
-	JPValue     getValueFromObject(const JPValue& obj) override;
 
 	char getTypeCode() override;
 	jlong getAsLong(jvalue v) override;
@@ -58,8 +54,9 @@ public:
 	void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
+
 	PyObject *newMultiArray(JPJavaFrame &frame,
-			JPPyBuffer& view, int subs, int base, jobject dims) override;
+			JPPyBuffer &buffer, int subs, int base, jobject dims) override;
 } ;
 
 #endif // _JP_VOID_TYPE_H_
diff -pruN 1.5.0-1/native/common/include/jpype.h 1.6.0-1/native/common/include/jpype.h
--- 1.5.0-1/native/common/include/jpype.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/include/jpype.h	2025-06-01 03:57:43.000000000 +0000
@@ -70,7 +70,7 @@ using std::string;
 using std::vector;
 
 #ifdef JP_INSTRUMENTATION
-
+#include <cstdint>
 template <size_t i>
 constexpr uint32_t _hash(const char *q, uint32_t v)
 {
@@ -168,7 +168,6 @@ public:
 #define JP_RAISE_PYTHON()                   { throw JPypeException(JPError::_python_error, nullptr, JP_STACKINFO()); }
 #define JP_RAISE_OS_ERROR_UNIX(err, msg)    { throw JPypeException(JPError::_os_error_unix,  msg, err, JP_STACKINFO()); }
 #define JP_RAISE_OS_ERROR_WINDOWS(err, msg) { throw JPypeException(JPError::_os_error_windows,  msg, err, JP_STACKINFO()); }
-#define JP_RAISE_METHOD_NOT_FOUND(msg)      { throw JPypeException(JPError::_method_not_found, nullptr, msg, JP_STACKINFO()); }
 #define JP_RAISE(type, msg)                 { throw JPypeException(JPError::_python_exc, type, msg, JP_STACKINFO()); }
 
 #ifndef PyObject_HEAD
@@ -176,12 +175,18 @@ struct _object;
 using PyObject = _object;
 #endif
 
+#include "jp_pythontypes.h"
+
+template <typename... T>
+static inline JPPyObject JPPyTuple_Pack(T... args) {
+	return JPPyObject::call(PyTuple_Pack(sizeof...(T), args...));
+}
+
 // Base utility headers
 #include "jp_javaframe.h"
 #include "jp_context.h"
 #include "jp_exception.h"
 #include "jp_tracer.h"
-#include "jp_pythontypes.h"
 #include "jp_typemanager.h"
 #include "jp_encoding.h"
 #include "jp_modifier.h"
@@ -196,4 +201,4 @@ using PyObject = _object;
 // Primitives classes
 #include "jp_primitivetype.h"
 
-#endif // _JPYPE_H_
\ No newline at end of file
+#endif // _JPYPE_H_
diff -pruN 1.5.0-1/native/common/jp_array.cpp 1.6.0-1/native/common/jp_array.cpp
--- 1.5.0-1/native/common/jp_array.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_array.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -24,10 +24,10 @@
 // carry them around so that we can match types.
 
 JPArray::JPArray(const JPValue &value)
-: m_Object(value.getClass()->getContext(), (jarray) value.getValue().l)
+: m_Object((jarray) value.getValue().l)
 {
 	m_Class = dynamic_cast<JPArrayClass*>( value.getClass());
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JP_TRACE_IN("JPArray::JPArray");
 	ASSERT_NOT_NULL(m_Class);
 	JP_TRACE(m_Class->toString());
@@ -46,7 +46,7 @@ JPArray::JPArray(const JPValue &value)
 }
 
 JPArray::JPArray(JPArray* instance, jsize start, jsize stop, jsize step)
-: m_Object(instance->m_Class->getContext(), (jarray) instance->getJava())
+: m_Object((jarray) instance->getJava())
 {
 	JP_TRACE_IN("JPArray::JPArraySlice");
 	m_Class = instance->m_Class;
@@ -78,7 +78,7 @@ void JPArray::setRange(jsize start, jsiz
 	if (!PySequence_Check(val))
 		JP_RAISE(PyExc_TypeError, "can only assign a sequence");
 
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClass* compType = m_Class->getComponentType();
 	JPPySequence seq = JPPySequence::use(val);
 	long plength = (long) seq.size();
@@ -101,7 +101,7 @@ void JPArray::setRange(jsize start, jsiz
 
 void JPArray::setItem(jsize ndx, PyObject* val)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClass* compType = m_Class->getComponentType();
 
 	if (ndx < 0)
@@ -115,7 +115,7 @@ void JPArray::setItem(jsize ndx, PyObjec
 
 JPPyObject JPArray::getItem(jsize ndx)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClass* compType = m_Class->getComponentType();
 
 	if (ndx < 0)
@@ -140,7 +140,7 @@ jarray JPArray::clone(JPJavaFrame& frame
 
 JPArrayView::JPArrayView(JPArray* array)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(array->m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	m_Array = array;
 	m_RefCount = 0;
 	m_Buffer.obj = nullptr;
@@ -162,7 +162,7 @@ JPArrayView::JPArrayView(JPArray* array,
 {
 	JP_TRACE_IN("JPArrayView::JPArrayView");
 	// All of the work has already been done by org.jpype.Utilities
-	JPJavaFrame frame = JPJavaFrame::outer(array->m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	m_Array = array;
 
 	jint len = frame.GetArrayLength((jarray) collection);
@@ -235,11 +235,6 @@ JPArrayView::~JPArrayView()
 		delete [] (char*) m_Memory;
 }
 
-JPContext *JPArrayView::getContext() const
-{
-	return m_Array->getClass()->getContext();
-}
-
 void JPArrayView::reference()
 {
 	m_RefCount++;
diff -pruN 1.5.0-1/native/common/jp_arrayclass.cpp 1.6.0-1/native/common/jp_arrayclass.cpp
--- 1.5.0-1/native/common/jp_arrayclass.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_arrayclass.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -52,7 +52,7 @@ JPMatch::Type JPArrayClass::findJavaConv
 
 void JPArrayClass::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	objectConversion->getInfo(this, info);
 	charArrayConversion->getInfo(this, info);
 	byteArrayConversion->getInfo(this, info);
diff -pruN 1.5.0-1/native/common/jp_booleantype.cpp 1.6.0-1/native/common/jp_booleantype.cpp
--- 1.5.0-1/native/common/jp_booleantype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_booleantype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -28,17 +28,20 @@ JPBooleanType::JPBooleanType()
 JPBooleanType::~JPBooleanType()
 = default;
 
+JPClass* JPBooleanType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Boolean;
+}
+
 JPPyObject JPBooleanType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast)
 {
 	return JPPyObject::call(PyBool_FromLong(val.z));
 }
 
-JPValue JPBooleanType::getValueFromObject(const JPValue& obj)
+JPValue JPBooleanType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
-	field(v) = frame.CallBooleanMethodA(obj.getValue().l, context->_java_lang_Boolean->m_BooleanValueID, nullptr) != 0;
+	field(v) = frame.CallBooleanMethodA(obj.getValue().l, frame.getContext()->_java_lang_Boolean->m_BooleanValueID, nullptr) != 0;
 	return JPValue(this, v);
 }
 
@@ -92,7 +95,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_boolean->getHost());
 		unboxConversion->getInfo(cls, info);
 	}
@@ -161,7 +164,7 @@ JPMatch::Type JPBooleanType::findJavaCon
 
 void JPBooleanType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	asBooleanExact.getInfo(this, info);
 	asBooleanJBool.getInfo(this, info);
 	asBooleanLong.getInfo(this, info);
@@ -306,7 +309,7 @@ void JPBooleanType::setArrayItem(JPJavaF
 
 void JPBooleanType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_Memory = (void*) frame.GetBooleanArrayElements(
 			(jbooleanArray) view.m_Array->getJava(), &view.m_IsCopy);
 	view.m_Buffer.format = "?";
@@ -317,7 +320,7 @@ void JPBooleanType::releaseView(JPArrayV
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseBooleanArrayElements((jbooleanArray) view.m_Array->getJava(),
 				(jboolean*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_boxedtype.cpp 1.6.0-1/native/common/jp_boxedtype.cpp
--- 1.5.0-1/native/common/jp_boxedtype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_boxedtype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -84,7 +84,7 @@ JPMatch::Type JPBoxedType::findJavaConve
 void JPBoxedType::getConversionInfo(JPConversionInfo &info)
 {
 	JP_TRACE_IN("JPBoxedType::getConversionInfo");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	m_PrimitiveType->getConversionInfo(info);
 	JPPyObject::call(PyObject_CallMethod(info.expl, "extend", "O", info.implicit));
 	JPPyObject::call(PyObject_CallMethod(info.implicit, "clear", ""));
@@ -117,13 +117,13 @@ JPPyObject JPBoxedType::convertToPythonO
 
 	JPPyObject wrapper = PyJPClass_create(frame, cls);
 	JPPyObject obj;
-	JPContext *context = frame.getContext();
+	JPContext *context = JPContext_global;
 	if (this->getPrimitive() == context->_char)
 	{
 		jchar value2 = 0;
 		// Not null get the char value
 		if (value.l != nullptr)
-			value2 = context->_char->getValueFromObject(JPValue(this, value)).getValue().c;
+			value2 = context->_char->getValueFromObject(frame, JPValue(this, value)).getValue().c;
 		// Create a char string object
 		obj = JPPyObject::call(PyJPChar_Create((PyTypeObject*) wrapper.get(), value2));
 	} else
diff -pruN 1.5.0-1/native/common/jp_buffer.cpp 1.6.0-1/native/common/jp_buffer.cpp
--- 1.5.0-1/native/common/jp_buffer.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_buffer.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -20,10 +20,10 @@
 #include "jp_buffertype.h"
 
 JPBuffer::JPBuffer(const JPValue &value)
-: m_Object(value.getClass()->getContext(), value.getValue().l)
+: m_Object(value.getValue().l)
 {
 	m_Class = dynamic_cast<JPBufferType*>( value.getClass());
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JP_TRACE_IN("JPBuffer::JPBuffer");
 	m_Address = frame.GetDirectBufferAddress(m_Object.get());
 	m_Capacity = (Py_ssize_t) frame.GetDirectBufferCapacity(m_Object.get());
diff -pruN 1.5.0-1/native/common/jp_bytetype.cpp 1.6.0-1/native/common/jp_bytetype.cpp
--- 1.5.0-1/native/common/jp_bytetype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_bytetype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -27,6 +27,11 @@ JPByteType::JPByteType()
 JPByteType::~JPByteType()
 = default;
 
+JPClass* JPByteType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Byte;
+}
+
 JPPyObject JPByteType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast)
 {
 	JPPyObject tmp = JPPyObject::call(PyLong_FromLong(field(val)));
@@ -35,10 +40,8 @@ JPPyObject JPByteType::convertToPythonOb
 	return out;
 }
 
-JPValue JPByteType::getValueFromObject(const JPValue& obj)
+JPValue JPByteType::getValueFromObject(JPJavaFrame &frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
 	jobject jo = obj.getValue().l;
 	auto* jb = dynamic_cast<JPBoxedType*>( frame.findClassForObject(jo));
@@ -70,7 +73,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_byte->getHost());
 		unboxConversion->getInfo(cls, info);
 	}
@@ -95,11 +98,11 @@ JPMatch::Type JPByteType::findJavaConver
 
 void JPByteType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jbyteConversion.getInfo(this, info);
 	byteConversion.getInfo(this, info);
 	byteNumberConversion.getInfo(this, info);
-	PyList_Append(info.ret, (PyObject*) m_Context->_int->getHost());
+	PyList_Append(info.ret, (PyObject*) JPContext_global->_int->getHost());
 }
 
 jarray JPByteType::newArrayOf(JPJavaFrame& frame, jsize sz)
@@ -244,7 +247,7 @@ void JPByteType::setArrayItem(JPJavaFram
 
 void JPByteType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_Memory = (void*) frame.GetByteArrayElements(
 			(jbyteArray) view.m_Array->getJava(), &view.m_IsCopy);
 	view.m_Buffer.format = "b";
@@ -255,7 +258,7 @@ void JPByteType::releaseView(JPArrayView
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseByteArrayElements((jbyteArray) view.m_Array->getJava(),
 				(jbyte*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_chartype.cpp 1.6.0-1/native/common/jp_chartype.cpp
--- 1.5.0-1/native/common/jp_chartype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_chartype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -28,6 +28,11 @@ JPCharType::JPCharType()
 JPCharType::~JPCharType()
 = default;
 
+JPClass* JPCharType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Character;
+}
+	
 JPValue JPCharType::newInstance(JPJavaFrame& frame, JPPyObjectVector& args)
 {
 	// This is only callable from one location so error checking is minimal
@@ -54,12 +59,10 @@ JPPyObject JPCharType::convertToPythonOb
 	//	return out;
 }
 
-JPValue JPCharType::getValueFromObject(const JPValue& obj)
+JPValue JPCharType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
-	field(v) = frame.CallCharMethodA(obj.getValue().l, context->_java_lang_Character->m_CharValueID, nullptr);
+	field(v) = frame.CallCharMethodA(obj.getValue().l, frame.getContext()->_java_lang_Character->m_CharValueID, nullptr);
 	return JPValue(this, v);
 }
 
@@ -109,12 +112,12 @@ public:
 			return match.type;
 
 		// Unboxing must be to the from the exact boxed type (JLS 5.1.8)
-		return JPMatch::_implicit;  // stop search
+		return JPMatch::_implicit; // stop the search
 	}
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_char->getHost());
 		unboxConversion->getInfo(cls, info);
 	}
@@ -138,10 +141,10 @@ JPMatch::Type JPCharType::findJavaConver
 
 void JPCharType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	asJCharConversion.getInfo(this, info);
 	asCharConversion.getInfo(this, info);
-	PyList_Append(info.ret, (PyObject*) m_Context->_char->getHost());
+	PyList_Append(info.ret, (PyObject*) JPContext_global->_char->getHost());
 }
 
 jarray JPCharType::newArrayOf(JPJavaFrame& frame, jsize sz)
@@ -247,7 +250,7 @@ void JPCharType::setArrayItem(JPJavaFram
 
 void JPCharType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_Memory = (void*) frame.GetCharArrayElements(
 			(jcharArray) view.m_Array->getJava(), &view.m_IsCopy);
 	view.m_Buffer.format = "H";
@@ -258,7 +261,7 @@ void JPCharType::releaseView(JPArrayView
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseCharArrayElements((jcharArray) view.m_Array->getJava(),
 				(jchar*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_class.cpp 1.6.0-1/native/common/jp_class.cpp
--- 1.5.0-1/native/common/jp_class.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_class.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -23,7 +23,6 @@ JPClass::JPClass(
 		const string& name,
 		jint modifiers)
 {
-	m_Context = nullptr;
 	m_CanonicalName = name;
 	m_SuperClass = nullptr;
 	m_Interfaces = JPClassList();
@@ -38,7 +37,6 @@ JPClass::JPClass(JPJavaFrame& frame,
 		jint modifiers)
 : m_Class(frame, clss)
 {
-	m_Context = frame.getContext();
 	m_CanonicalName = name;
 	m_SuperClass = super;
 	m_Interfaces = interfaces;
@@ -68,7 +66,7 @@ jclass JPClass::getJavaClass() const
 
 void JPClass::ensureMembers(JPJavaFrame& frame)
 {
-	JPContext* context = frame.getContext();
+	JPContext* context = JPContext_global;
 	JPTypeManager* typeManager = context->getTypeManager();
 	typeManager->populateMembers(this);
 }
@@ -99,14 +97,6 @@ JPValue JPClass::newInstance(JPJavaFrame
 	return m_Constructors->invokeConstructor(frame, args);
 }
 
-JPContext* JPClass::getContext() const
-{
-	// This sanity check is for during shutdown.
-	if (m_Context == nullptr)
-		JP_RAISE(PyExc_RuntimeError, "Null context"); // GCOVR_EXCL_LINE
-	return m_Context;
-}
-
 JPClass* JPClass::newArrayType(JPJavaFrame &frame, long d)
 {
 	if (d < 0 || d > 255)
@@ -136,9 +126,9 @@ jarray JPClass::newArrayOf(JPJavaFrame&
 string JPClass::toString() const
 {
 	// This sanity check will not be hit in normal operation
-	if (m_Context == nullptr)
+	if (JPContext_global == nullptr)
 		return m_CanonicalName;  // GCOVR_EXCL_LINE
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	return frame.toString(m_Class.get());
 }
 // GCOVR_EXCL_STOP
@@ -146,11 +136,11 @@ string JPClass::toString() const
 string JPClass::getName() const
 {
 	// This sanity check will not be hit in normal operation
-	if (m_Context == nullptr)
+	if (JPContext_global == nullptr)
 		return m_CanonicalName;  // GCOVR_EXCL_LINE
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	return frame.toString(frame.CallObjectMethodA(
-			(jobject) m_Class.get(), m_Context->m_Class_GetNameID, nullptr));
+			(jobject) m_Class.get(), JPContext_global->m_Class_GetNameID, nullptr));
 }
 
 //</editor-fold>
@@ -320,7 +310,7 @@ JPPyObject JPClass::getArrayItem(JPJavaF
 //</editor-fold>
 //<editor-fold desc="conversion" defaultstate="collapsed">
 
-JPValue JPClass::getValueFromObject(const JPValue& obj)
+JPValue JPClass::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
 	JP_TRACE_IN("JPClass::getValueFromObject");
 	return JPValue(this, obj.getJavaObject());
@@ -372,16 +362,15 @@ JPPyObject JPClass::convertToPythonObjec
 			jstring m = frame.getMessage((jthrowable) value.l);
 			if (m != nullptr)
 			{
-				tuple0 = JPPyObject::call(PyTuple_Pack(1,
-						JPPyString::fromStringUTF8(frame.toStringUTF8(m)).get()));
+				tuple0 = JPPyTuple_Pack(
+						JPPyString::fromStringUTF8(frame.toStringUTF8(m)).get());
 			} else
 			{
-				tuple0 = JPPyObject::call(PyTuple_Pack(1,
-						JPPyString::fromStringUTF8(frame.toString(value.l)).get()));
+				tuple0 = JPPyTuple_Pack(
+						JPPyString::fromStringUTF8(frame.toString(value.l)).get());
 			}
 		}
-		JPPyObject tuple1 = JPPyObject::call(PyTuple_Pack(2,
-				_JObjectKey, tuple0.get()));
+		JPPyObject tuple1 = JPPyTuple_Pack(_JObjectKey, tuple0.get());
 		// Exceptions need new and init
 		obj = JPPyObject::call(PyObject_Call(wrapper.get(), tuple1.get(), nullptr));
 	} else
@@ -412,10 +401,21 @@ JPMatch::Type JPClass::findJavaConversio
 	JP_TRACE_OUT;
 }
 
+PyObject* JPClass::getHints()
+{
+	PyObject* out = m_Hints.get();
+	if (out != nullptr)
+		return out;
+	// Force creation
+	JPJavaFrame frame = JPJavaFrame::outer();
+	PyJPClass_create(frame, this);
+	return m_Hints.get();
+}
+
 void JPClass::getConversionInfo(JPConversionInfo &info)
 {
 	JP_TRACE_IN("JPClass::getConversionInfo");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	objectConversion->getInfo(this, info);
 	hintsConversion->getInfo(this, info);
 	PyList_Append(info.ret, PyJPClass_create(frame, this).get());
diff -pruN 1.5.0-1/native/common/jp_classhints.cpp 1.6.0-1/native/common/jp_classhints.cpp
--- 1.5.0-1/native/common/jp_classhints.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_classhints.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -85,7 +85,10 @@ JPMethodMatch::JPMethodMatch(JPJavaFrame
 }
 
 JPConversion::~JPConversion() = default;
-JPClassHints::JPClassHints() = default;
+JPClassHints::JPClassHints()
+{
+	m_ConvertJava = false;
+}
 
 JPClassHints::~JPClassHints()
 {
@@ -146,8 +149,7 @@ public:
 	{
 		JP_TRACE_IN("JPPythonConversion::convert");
 		JPClass *cls = ((JPClass*) match.closure);
-		JPPyObject args = JPPyObject::call(PyTuple_Pack(2,
-				cls->getHost(), match.object));
+		JPPyObject args = JPPyTuple_Pack(cls->getHost(), match.object);
 		JPPyObject ret = JPPyObject::call(PyObject_Call(method_.get(), args.get(), nullptr));
 		JPValue *value = PyJPValue_getJavaSlot(ret.get());
 		if (value != nullptr)
@@ -298,6 +300,8 @@ private:
 void JPClassHints::addTypeConversion(PyObject *type, PyObject *method, bool exact)
 {
 	JP_TRACE_IN("JPClassHints::addTypeConversion", this);
+	if (PyJPClass_Check(type))
+		m_ConvertJava = true;
 	conversions.push_back(new JPTypeConversion(type, method, exact));
 	JP_TRACE_OUT;
 }
@@ -325,16 +329,6 @@ public:
 	JPMatch::Type matches(JPClass *cls, JPMatch &match) override
 	{
 		auto *pyhints = (PyJPClassHints*) cls->getHints();
-		// GCOVR_EXCL_START
-		if (pyhints == nullptr)
-		{
-			// Force creation of the class that will create the hints
-			PyJPClass_create(*match.frame, cls);
-			pyhints = (PyJPClassHints*) cls->getHints();
-			if (pyhints == nullptr)
-				return match.type = JPMatch::_none;
-		}
-		// GCOVR_EXCL_STOP
 		JPClassHints *hints = pyhints->m_Hints;
 		hints->getConversion(match, cls);
 		return match.type;
@@ -343,8 +337,6 @@ public:
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
 		auto *pyhints = (PyJPClassHints*) cls->getHints();
-		if (pyhints == nullptr)
-			return;
 		JPClassHints *hints = pyhints->m_Hints;
 		hints->getInfo(cls, info);
 	}
@@ -366,7 +358,7 @@ public:
 		JP_TRACE_IN("JPConversionCharArray::matches");
 		auto* acls = dynamic_cast<JPArrayClass*>( cls);
 		if (match.frame == nullptr  || !JPPyString::check(match.object) ||
-				acls->getComponentType() != match.getContext()->_char)
+				acls->getComponentType() != JPContext_global->_char)
 			return match.type = JPMatch::_none;
 		match.conversion = this;
 		return match.type = JPMatch::_implicit;
@@ -376,7 +368,7 @@ public:
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
 		auto* acls = dynamic_cast<JPArrayClass*>( cls);
-		if (acls->getComponentType() != cls->getContext()->_char)
+		if (acls->getComponentType() != JPContext_global->_char)
 			return;
 		PyList_Append(info.implicit, (PyObject*) & PyUnicode_Type);
 	}
@@ -408,7 +400,7 @@ public:
 		JP_TRACE_IN("JPConversionByteArray::matches");
 		auto* acls = dynamic_cast<JPArrayClass*>( cls);
 		if (match.frame == nullptr  || !PyBytes_Check(match.object) ||
-				acls->getComponentType() != match.frame->getContext()->_byte)
+				acls->getComponentType() != JPContext_global->_byte)
 			return match.type = JPMatch::_none;
 		match.conversion = this;
 		return match.type = JPMatch::_implicit;
@@ -418,7 +410,7 @@ public:
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
 		auto* acls = dynamic_cast<JPArrayClass*>( cls);
-		if (acls->getComponentType() != cls->getContext()->_byte)
+		if (acls->getComponentType() != JPContext_global->_byte)
 			return;
 		PyList_Append(info.implicit, (PyObject*) & PyBytes_Type);
 	}
@@ -542,7 +534,7 @@ public:
 		JPPyObject proto = JPPyObject::call(PyObject_GetAttrString(typing, "Sequence"));
 		PyList_Append(info.implicit, proto.get());
 		auto* acls = dynamic_cast<JPArrayClass*>( cls);
-		if (acls->getComponentType() == cls->getContext()->_char)
+		if (acls->getComponentType() == JPContext_global->_char)
 			return;
 		PyList_Append(info.none, (PyObject*) & PyUnicode_Type);
 	}
@@ -607,7 +599,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(cls->getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		PyList_Append(info.implicit, (PyObject*) PyJPClass_Type);
 	}
 
@@ -643,6 +635,15 @@ public:
 		JP_TRACE("assignable", assignable, oc->getCanonicalName(), cls->getCanonicalName());
 		match.type = (assignable ? JPMatch::_derived : JPMatch::_none);
 
+		// User has request a Java class to class conversion.  We must pass through check it.
+		if (!assignable)
+		{
+			auto *pyhints = (PyJPClassHints*) cls->getHints();
+			JPClassHints *hints = pyhints->m_Hints;
+			if (hints->m_ConvertJava)
+				return match.type;
+		}
+
 		// This is the one except to the conversion rule patterns.
 		// If it is a Java value then we must prevent it from proceeding
 		// through the conversion rules even if it was not a match.
@@ -653,7 +654,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(cls->getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		PyList_Append(info.exact, PyJPClass_create(frame, cls).get());
 	}
 
@@ -679,7 +680,7 @@ JPMatch::Type JPConversionJavaValue::mat
 
 void JPConversionJavaValue::getInfo(JPClass *cls, JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(cls->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	PyList_Append(info.exact, PyJPClass_create(frame, cls).get());
 }
 
@@ -701,7 +702,7 @@ public:
 		if (match.frame == nullptr || !JPPyString::check(match.object))
 			return match.type = JPMatch::_none;
 		match.conversion = this;
-		if (cls == match.getContext()->_java_lang_String)
+		if (cls == JPContext_global->_java_lang_String)
 			return match.type = JPMatch::_exact;
 		return match.type = JPMatch::_implicit;
 		JP_TRACE_OUT;
@@ -746,7 +747,7 @@ public:
 		if (!PyBool_Check(match.object))
 			return match.type = JPMatch::_none;
 		match.conversion = this;
-		match.closure = match.frame->getContext()->_java_lang_Boolean;
+		match.closure = JPContext_global->_java_lang_Boolean;
 		return match.type = JPMatch::_implicit;
 		JP_TRACE_OUT;
 	}
@@ -786,18 +787,17 @@ public:
 	jvalue convert(JPMatch &match) override
 	{
 		PyTypeObject* type = Py_TYPE(match.object);
-		JPJavaFrame *frame = match.frame;
 		const char *name = type->tp_name;
-		match.closure = frame->getContext()->_java_lang_Long;
+		match.closure = JPContext_global->_java_lang_Long;
 		if (strncmp(name, "numpy", 5) == 0)
 		{
 			// We only handle specific sized types, all others go to long.
 			if (strcmp(&name[5], ".int8") == 0)
-				match.closure = frame->getContext()->_java_lang_Byte;
+				match.closure = JPContext_global->_java_lang_Byte;
 			else if (strcmp(&name[5], ".int16") == 0)
-				match.closure = frame->getContext()->_java_lang_Short;
+				match.closure = JPContext_global->_java_lang_Short;
 			else if (strcmp(&name[5], ".int32") == 0)
-				match.closure = frame->getContext()->_java_lang_Integer;
+				match.closure = JPContext_global->_java_lang_Integer;
 		}
 		return JPConversionBox::convert(match);
 	}
@@ -830,15 +830,14 @@ public:
 
 	jvalue convert(JPMatch &match) override
 	{
-		JPJavaFrame *frame = match.frame;
 		PyTypeObject* type = Py_TYPE(match.object);
 		const char *name = type->tp_name;
-		match.closure = frame->getContext()->_java_lang_Double;
+		match.closure = JPContext_global->_java_lang_Double;
 		if (strncmp(name, "numpy", 5) == 0)
 		{
 			// We only handle specific sized types, all others go to double.
 			if (strcmp(&name[5], ".float32") == 0)
-				match.closure = frame->getContext()->_java_lang_Float;
+				match.closure = JPContext_global->_java_lang_Float;
 		}
 		return JPConversionBox::convert(match);
 	}
@@ -867,8 +866,8 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(cls->getContext());
-		PyList_Append(info.implicit, PyJPClass_create(frame, cls->getContext()->_java_lang_Object).get());
+		JPJavaFrame frame = JPJavaFrame::outer();
+		PyList_Append(info.implicit, PyJPClass_create(frame, JPContext_global->_java_lang_Object).get());
 	}
 
 	jvalue convert(JPMatch &match) override
@@ -884,7 +883,7 @@ public:
 		{
 			// Okay we need to box it.
 			auto* type = dynamic_cast<JPPrimitiveType*> (value->getClass());
-			match.closure = type->getBoxedClass(frame->getContext());
+			match.closure = type->getBoxedClass(*frame);
 			res = JPConversionBox::convert(match);
 			return res;
 		}
@@ -898,7 +897,7 @@ public:
 	JPMatch::Type matches(JPClass *cls, JPMatch &match) override
 	{
 		JP_TRACE_IN("JPConversionJavaNumberAny::matches");
-		JPContext *context = match.getContext();
+		JPContext *context = JPContext_global;
 		JPValue *value = match.getJavaSlot();
 		// This converter only works for number types, thus boolean and char
 		// are excluded.
@@ -934,12 +933,12 @@ public:
 
 	JPMatch::Type matches(JPClass *cls, JPMatch &match) override
 	{
-		JPContext *context = match.getContext();
+		JPContext *context = JPContext_global;
 		if (context == nullptr)
 			return match.type = JPMatch::_none;
 		JPValue *slot = match.slot;
 		auto *pcls = dynamic_cast<JPPrimitiveType*>( cls);
-		if (slot->getClass() != pcls->getBoxedClass(context))
+		if (slot->getClass() != pcls->getBoxedClass(*match.frame))
 			return match.type = JPMatch::_none;
 		match.conversion = this;
 		match.closure = cls;
@@ -948,18 +947,17 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(cls->getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		auto *pcls = dynamic_cast<JPPrimitiveType*>( cls);
-		JPContext *context = cls->getContext();
 		PyList_Append(info.implicit,
-				PyJPClass_create(frame, pcls->getBoxedClass(context)).get());
+				PyJPClass_create(frame, pcls->getBoxedClass(frame)).get());
 	}
 
 	jvalue convert(JPMatch &match) override
 	{
 		JPValue* value = match.getJavaSlot();
 		auto *cls = (JPClass*) match.closure;
-		return cls->getValueFromObject(*value);
+		return cls->getValueFromObject(*match.frame, *value);
 	}
 } _unboxConversion;
 
diff -pruN 1.5.0-1/native/common/jp_classloader.cpp 1.6.0-1/native/common/jp_classloader.cpp
--- 1.5.0-1/native/common/jp_classloader.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_classloader.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -23,27 +23,9 @@ jobject JPClassLoader::getBootLoader()
 	return m_BootLoader.get();
 }
 
-static jobject toURL(JPJavaFrame &frame, const string& path)
-{
-	//  file = new File("org.jpype.jar");
-	jclass fileClass = frame.FindClass("java/io/File");
-	jmethodID newFile = frame.GetMethodID(fileClass, "<init>", "(Ljava/lang/String;)V");
-	jvalue v[3];
-	v[0].l = frame.NewStringUTF(path.c_str());
-	jobject file = frame.NewObjectA(fileClass, newFile, v);
-
-	// url = file.toURI().toURL();
-	jmethodID toURI = frame.GetMethodID(fileClass, "toURI", "()Ljava/net/URI;");
-	jobject uri = frame.CallObjectMethodA(file, toURI, nullptr);
-	jclass uriClass = frame.GetObjectClass(uri);
-	jmethodID toURL = frame.GetMethodID(uriClass, "toURL", "()Ljava/net/URL;");
-	return frame.CallObjectMethodA(uri, toURL, nullptr);
-}
-
 JPClassLoader::JPClassLoader(JPJavaFrame& frame)
 {
 	JP_TRACE_IN("JPClassLoader::JPClassLoader");
-	m_Context = frame.getContext();
 
 	// Define the class loader
 	m_ClassClass = JPClassRef(frame, frame.FindClass("java/lang/Class"));
@@ -55,9 +37,16 @@ JPClassLoader::JPClassLoader(JPJavaFrame
 	m_SystemClassLoader = JPObjectRef(frame,
 			frame.CallStaticObjectMethodA(classLoaderClass, getSystemClassLoader, nullptr));
 
-	jclass dynamicLoaderClass = frame.getEnv()->FindClass("org/jpype/classloader/DynamicClassLoader");
+	jclass dynamicLoaderClass = frame.getEnv()->FindClass("org/jpype/JPypeClassLoader");
 	if (dynamicLoaderClass != nullptr)
 	{
+		// Use the one in place already
+		if (frame.IsInstanceOf(m_SystemClassLoader.get(), dynamicLoaderClass))
+		{
+			m_BootLoader = m_SystemClassLoader;
+			return;
+		}
+
 		// Easy the Dynamic loader is already in the path, so just use it as the bootloader
 		jmethodID newDyLoader = frame.GetMethodID(dynamicLoaderClass, "<init>",
 				"(Ljava/lang/ClassLoader;)V");
@@ -68,44 +57,8 @@ JPClassLoader::JPClassLoader(JPJavaFrame
 	}
 	frame.ExceptionClear();
 
-	// Harder, we need to find the _jpype module and use __file__ to obtain a
-	// path.
-	JPPyObject pypath = JPPyObject::call(PyObject_GetAttrString(PyJPModule, "__file__"));
-	string path = JPPyString::asStringUTF8(pypath.get());
-	string::size_type i = path.find_last_of('\\');
-	if (i == string::npos)
-		i = path.find_last_of('/');
-	if (i == string::npos)
-		JP_RAISE(PyExc_RuntimeError, "Can't find jar path");
-	path = path.substr(0, i + 1);
-	jobject url1 = toURL(frame, path + "org.jpype.jar");
-	//	jobject url2 = toURL(frame, path + "lib/asm-8.0.1.jar");
-
-	// urlArray = new URL[]{url};
-	jclass urlClass = frame.GetObjectClass(url1);
-	jobjectArray urlArray = frame.NewObjectArray(1, urlClass, nullptr);
-	frame.SetObjectArrayElement(urlArray, 0, url1);
-	//	frame.SetObjectArrayElement(urlArray, 1, url2);
-
-	// cl = new URLClassLoader(urlArray);
-	jclass urlLoaderClass = frame.FindClass("java/net/URLClassLoader");
-	jmethodID newURLClassLoader = frame.GetMethodID(urlLoaderClass, "<init>", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V");
-	jvalue v[3];
-	v[0].l = (jobject) urlArray;
-	v[1].l = (jobject) m_SystemClassLoader.get();
-	jobject cl = frame.NewObjectA(urlLoaderClass, newURLClassLoader, v);
-
-	// Class dycl = Class.forName("org.jpype.classloader.DynamicClassLoader", true, cl);
-	v[0].l = frame.NewStringUTF("org.jpype.classloader.DynamicClassLoader");
-	v[1].z = true;
-	v[2].l = cl;
-	auto dyClass = (jclass) frame.CallStaticObjectMethodA(m_ClassClass.get(), m_ForNameID, v);
-
-	// dycl.newInstance(systemClassLoader);
-	jmethodID newDyLoader = frame.GetMethodID(dyClass, "<init>", "(Ljava/lang/ClassLoader;)V");
-	v[0].l = cl;
-	m_BootLoader = JPObjectRef(frame, frame.NewObjectA(dyClass, newDyLoader, v));
-
+	// org.jpype was not loaded already so we can't proceed
+	JP_RAISE(PyExc_RuntimeError, "Can't find org.jpype.jar support library");
 	JP_TRACE_OUT;  // GCOVR_EXCL_LINE
 }
 
diff -pruN 1.5.0-1/native/common/jp_classtype.cpp 1.6.0-1/native/common/jp_classtype.cpp
--- 1.5.0-1/native/common/jp_classtype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_classtype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -46,7 +46,7 @@ JPMatch::Type JPClassType::findJavaConve
 
 void JPClassType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	nullConversion->getInfo(this, info);
 	objectConversion->getInfo(this, info);
 	classConversion->getInfo(this, info);
diff -pruN 1.5.0-1/native/common/jp_context.cpp 1.6.0-1/native/common/jp_context.cpp
--- 1.5.0-1/native/common/jp_context.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_context.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -22,6 +22,18 @@
 #include "jp_platform.h"
 #include "jp_gc.h"
 
+#ifdef WIN32
+#include <Windows.h>
+#else
+#if defined(_HPUX) && !defined(_IA64)
+#include <dl.h>
+#else
+#include <dlfcn.h>
+#endif // HPUX
+#include <errno.h>
+#endif
+
+
 JPResource::~JPResource() = default;
 
 
@@ -36,7 +48,7 @@ JPContext::JPContext()
 {
 	m_Embedded = false;
 
-	m_GC = new JPGarbageCollection(this);
+	m_GC = new JPGarbageCollection();
 }
 
 JPContext::~JPContext()
@@ -107,24 +119,38 @@ void JPContext::startJVM(const string& v
 		throw;
 	}
 
+	// Determine the memory requirements
+#define PAD(x) ((x+31)&~31)
+	size_t mem = PAD(sizeof(JavaVMInitArgs));
+	size_t oblock = mem;
+	mem += PAD(sizeof(JavaVMOption)*args.size() + 1);
+	size_t sblock = mem;
+	for (size_t i = 0; i < args.size(); i++)
+	{
+		mem += PAD(args[i].size()+1);
+	}
+
 	// Pack the arguments
 	JP_TRACE("Pack arguments");
-	JavaVMInitArgs jniArgs;
-	jniArgs.options = nullptr;
+	char *block = (char*) malloc(mem);
+	JavaVMInitArgs* jniArgs = (JavaVMInitArgs*) block;
+	memset(jniArgs, 0, mem);
+	jniArgs->options = (JavaVMOption*)(&block[oblock]);
 
 	// prepare this ...
-	jniArgs.version = USE_JNI_VERSION;
-	jniArgs.ignoreUnrecognized = ignoreUnrecognized;
+	jniArgs->version = USE_JNI_VERSION;
+	jniArgs->ignoreUnrecognized = ignoreUnrecognized;
 	JP_TRACE("IgnoreUnrecognized", ignoreUnrecognized);
 
-	jniArgs.nOptions = (jint) args.size();
-	JP_TRACE("NumOptions", jniArgs.nOptions);
-	jniArgs.options = new JavaVMOption[jniArgs.nOptions];
-	memset(jniArgs.options, 0, sizeof (JavaVMOption) * jniArgs.nOptions);
-	for (int i = 0; i < jniArgs.nOptions; i++)
+	jniArgs->nOptions = (jint) args.size();
+	JP_TRACE("NumOptions", jniArgs->nOptions);
+	size_t j = sblock;
+	for (size_t i = 0; i < args.size(); i++)
 	{
 		JP_TRACE("Option", args[i]);
-		jniArgs.options[i].optionString = (char*) args[i].c_str();
+		strncpy(&block[j], args[i].c_str(), args[i].size());
+		jniArgs->options[i].optionString = (char*) &block[j];
+		j += PAD(args[i].size()+1);
 	}
 
 	// Launch the JVM
@@ -132,13 +158,13 @@ void JPContext::startJVM(const string& v
 	JP_TRACE("Create JVM");
 	try
 	{
-		CreateJVM_Method(&m_JavaVM, (void**) &env, (void*) &jniArgs);
+		CreateJVM_Method(&m_JavaVM, (void**) &env, (void*) jniArgs);
 	} catch (...)
 	{
 		JP_TRACE("Exception in CreateJVM?");
 	}
 	JP_TRACE("JVM created");
-	delete [] jniArgs.options;
+	free(jniArgs);
 
 	if (m_JavaVM == nullptr)
 	{
@@ -146,6 +172,8 @@ void JPContext::startJVM(const string& v
 		JP_RAISE(PyExc_RuntimeError, "Unable to start JVM");
 	}
 
+	// Mark running for assert
+	m_Running = true;
 	initializeResources(env, interrupt);
 	JP_TRACE_OUT;
 }
@@ -159,9 +187,39 @@ void JPContext::attachJVM(JNIEnv* env)
 	initializeResources(env, false);
 }
 
+std::string getShared() 
+{
+#ifdef WIN32
+	// Windows specific
+	char path[MAX_PATH];
+	HMODULE hm = NULL;
+	if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 
+		GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+		(LPCSTR) &getShared, &hm) != 0 &&
+		GetModuleFileName(hm, path, sizeof(path)) != 0)
+	{
+		// This is needed when there is no-ascii characters in path
+		char shortPathBuffer[MAX_PATH];
+		long len = GetShortPathName(path, shortPathBuffer, MAX_PATH);
+		if (len != 0)
+			return std::string(shortPathBuffer);
+	}
+#else
+	// Linux specific
+	Dl_info info;
+	if (dladdr((void*)getShared, &info))
+		return info.dli_fname;
+#endif
+	// Generic
+	JPPyObject import = JPPyObject::use(PyImport_AddModule("importlib.util"));
+	JPPyObject jpype = JPPyObject::call(PyObject_CallMethod(import.get(), "find_spec", "s", "_jpype"));
+	JPPyObject origin = JPPyObject::call(PyObject_GetAttrString(jpype.get(), "origin"));
+	return JPPyString::asStringUTF8(origin.get());
+}
+
 void JPContext::initializeResources(JNIEnv* env, bool interrupt)
 {
-	JPJavaFrame frame = JPJavaFrame::external(this, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	// This is the only frame that we can use until the system
 	// is initialized.  Any other frame creation will result in an error.
 
@@ -181,7 +239,6 @@ void JPContext::initializeResources(JNIE
 	m_Object_HashCodeID = frame.GetMethodID(objectClass, "hashCode", "()I");
 	m_Object_GetClassID = frame.GetMethodID(objectClass, "getClass", "()Ljava/lang/Class;");
 
-	m_NoSuchMethodError = JPClassRef(frame, (jclass) frame.FindClass("java/lang/NoSuchMethodError"));
 	m_RuntimeException = JPClassRef(frame, (jclass) frame.FindClass("java/lang/RuntimeException"));
 
 	jclass stringClass = frame.FindClass("java/lang/String");
@@ -216,10 +273,8 @@ void JPContext::initializeResources(JNIE
 
 	if (!m_Embedded)
 	{
-		JPPyObject import = JPPyObject::use(PyImport_AddModule("importlib.util"));
-		JPPyObject jpype = JPPyObject::call(PyObject_CallMethod(import.get(), "find_spec", "s", "_jpype"));
-		JPPyObject origin = JPPyObject::call(PyObject_GetAttrString(jpype.get(), "origin"));
-		val[2].l = frame.fromStringUTF8(JPPyString::asStringUTF8(origin.get()));
+		std::string shared = getShared();
+		val[2].l = frame.fromStringUTF8(shared);
 	}
 
 	// Required before launch
@@ -239,7 +294,10 @@ void JPContext::initializeResources(JNIE
 
 	// Set up methods after everything is start so we get better error
 	// messages
-	m_CallMethodID = frame.GetMethodID(contextClass, "callMethod",
+	jclass reflectorClass = frame.FindClass("org/jpype/JPypeReflector");
+	jfieldID reflectorField = frame.GetFieldID(contextClass, "reflector", "Lorg/jpype/JPypeReflector;");
+	m_Reflector = JPObjectRef(frame, frame.GetObjectField(m_JavaContext.get(), reflectorField));
+	m_CallMethodID = frame.GetMethodID(reflectorClass, "callMethod",
 			"(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
 	m_Context_collectRectangularID = frame.GetMethodID(contextClass,
 			"collectRectangular",
@@ -276,6 +334,10 @@ void JPContext::initializeResources(JNIE
 	m_Buffer_IsReadOnlyID = frame.GetMethodID(bufferClass, "isReadOnly",
 			"()Z");
 
+	jclass bytebufferClass = frame.FindClass("java/nio/ByteBuffer");
+	m_Buffer_AsReadOnlyID = frame.GetMethodID(bytebufferClass, "asReadOnlyBuffer",
+			"()Ljava/nio/ByteBuffer;");
+
 	jclass comparableClass = frame.FindClass("java/lang/Comparable");
 	m_CompareToID = frame.GetMethodID(comparableClass, "compareTo",
 			"(Ljava/lang/Object;)I");
@@ -297,7 +359,6 @@ void JPContext::initializeResources(JNIE
 	// FIXME find a way to call this from instrumentation.
 	// throw std::runtime_error("Failed");
 	// Everything is started.
-	m_Running = true;
 }
 
 void JPContext::onShutdown()
@@ -404,7 +465,9 @@ JNIEnv* JPContext::getEnv()
 		// not deadlock the shutdown.  The user can convert later if they want.
 		res = m_JavaVM->AttachCurrentThreadAsDaemon((void**) &env, nullptr);
 		if (res != JNI_OK)
+		{
 			JP_RAISE(PyExc_RuntimeError, "Unable to attach to local thread");
+		}
 	}
 	return env;
 }
@@ -447,10 +510,14 @@ extern "C" JNIEXPORT void JNICALL Java_o
 
 static int interruptState = 0;
 extern "C" JNIEXPORT void JNICALL Java_org_jpype_JPypeSignal_interruptPy
-(JNIEnv *env, jclass cls)
+(JNIEnv *env, jclass cls, jint signal)
 {
 	interruptState = 1;
+#if PY_MINOR_VERSION<10
 	PyErr_SetInterrupt();
+#else
+	PyErr_SetInterruptEx((int) signal);
+#endif
 }
 
 extern "C" JNIEXPORT void JNICALL Java_org_jpype_JPypeSignal_acknowledgePy
diff -pruN 1.5.0-1/native/common/jp_convert.cpp 1.6.0-1/native/common/jp_convert.cpp
--- 1.5.0-1/native/common/jp_convert.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_convert.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -14,10 +14,62 @@
    See NOTICE file for details.
  *****************************************************************************/
 #include "jpype.h"
+#include <math.h>
+#include <bitset>
 
 namespace
 {
 
+template <jvalue func(void *c) >
+class Half
+{
+public:
+	static jvalue convert(void* c)
+    {
+        uint16_t i = *(uint16_t*) c;
+		uint32_t sign = (i&0x8000)>>15;
+		uint32_t exp  = (i&0x7C00)>>10;
+		uint32_t frac = (i&0x03ff);
+		uint32_t k = sign<<31;
+		uint32_t count = (i&0x03ff);
+
+		if (exp == 0)
+		{
+			// subnormal numbers
+			if (frac != 0)
+			{
+				count = count | (count >> 1);
+				count = count | (count >> 2);
+				count = count | (count >> 4);
+				count = count | (count >> 8);
+				int zeros = std::bitset<32>(~count).count();
+				exp = 127-zeros+7;
+				exp <<= 23;
+				frac <<= zeros-8;
+				frac &= 0x7fffff;
+				k |= exp | frac;
+			}
+		}
+		else if (exp < 31)
+		{
+			// normal numbers
+			exp = exp-15+127;
+			exp <<= 23;
+			frac <<= 13;
+			k |= exp | frac;
+		}
+		else
+		{
+			// to infinity and beyond!
+			if (frac == 0)
+				k |= 0x7f800000;
+			else 
+				k |= 0x7f800001 | ((frac&0x200)<<12);
+		}
+		return func(&k);
+	}
+};
+
 template <class T>
 class Convert
 {
@@ -385,6 +437,31 @@ jconverter getConverter(const char* from
 				case 'd': return &Convert<double>::toD;
 			}
 			break;
+		case 'e':
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Half<Convert<float>::toZ>::convert>::call4;
+				case 'b': return &Reverse<Half<Convert<float>::toB>::convert>::call4;
+				case 'c': return &Reverse<Half<Convert<float>::toC>::convert>::call4;
+				case 's': return &Reverse<Half<Convert<float>::toS>::convert>::call4;
+				case 'i': return &Reverse<Half<Convert<float>::toI>::convert>::call4;
+				case 'j': return &Reverse<Half<Convert<float>::toJ>::convert>::call4;
+				case 'f': return &Reverse<Half<Convert<float>::toF>::convert>::call4;
+				case 'd': return &Reverse<Half<Convert<float>::toD>::convert>::call4;
+			}
+			else switch (to[0])
+			{
+				case 'z': return &Half<Convert<float>::toZ>::convert;
+				case 'b': return &Half<Convert<float>::toB>::convert;
+				case 'c': return &Half<Convert<float>::toC>::convert;
+				case 's': return &Half<Convert<float>::toS>::convert;
+				case 'i': return &Half<Convert<float>::toI>::convert;
+				case 'j': return &Half<Convert<float>::toJ>::convert;
+				case 'f': return &Half<Convert<float>::toF>::convert;
+				case 'd': return &Half<Convert<float>::toD>::convert;
+			}
+			break;
+
 		case 'n':
 			if (reverse) switch (to[0])
 			{
diff -pruN 1.5.0-1/native/common/jp_doubletype.cpp 1.6.0-1/native/common/jp_doubletype.cpp
--- 1.5.0-1/native/common/jp_doubletype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_doubletype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -24,6 +24,11 @@ JPDoubleType::JPDoubleType()
 {
 }
 
+JPClass* JPDoubleType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Double;
+}
+
 JPPyObject JPDoubleType::convertToPythonObject(JPJavaFrame& frame, jvalue value, bool cast)
 {
 	PyTypeObject * wrapper = getHost();
@@ -33,10 +38,8 @@ JPPyObject JPDoubleType::convertToPython
 	return obj;
 }
 
-JPValue JPDoubleType::getValueFromObject(const JPValue& obj)
+JPValue JPDoubleType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
 	jobject jo = obj.getValue().l;
 	auto* jb = dynamic_cast<JPBoxedType*>( frame.findClassForObject(jo));
@@ -106,7 +109,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_double->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_byte->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_char->getHost());
@@ -137,7 +140,7 @@ JPMatch::Type JPDoubleType::findJavaConv
 
 void JPDoubleType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	asJDoubleConversion.getInfo(this, info);
 	asDoubleExactConversion.getInfo(this, info);
 	asDoubleLongConversion.getInfo(this, info);
@@ -282,7 +285,7 @@ void JPDoubleType::setArrayItem(JPJavaFr
 
 void JPDoubleType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_Memory = (void*) frame.GetDoubleArrayElements(
 			(jdoubleArray) view.m_Array->getJava(), &view.m_IsCopy);
 	view.m_Buffer.format = "d";
@@ -293,7 +296,7 @@ void JPDoubleType::releaseView(JPArrayVi
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseDoubleArrayElements((jdoubleArray) view.m_Array->getJava(),
 				(jdouble*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_exception.cpp 1.6.0-1/native/common/jp_exception.cpp
--- 1.5.0-1/native/common/jp_exception.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_exception.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -27,7 +27,6 @@ PyObject* PyTrace_FromJPStackTrace(JPSta
 
 JPypeException::JPypeException(JPJavaFrame &frame, jthrowable th, const JPStackInfo& stackInfo)
 : std::runtime_error(frame.toString(th)),
-  m_Context(frame.getContext()),
   m_Type(JPError::_java_error),
   m_Throwable(frame, th)
 {
@@ -65,7 +64,7 @@ JPypeException::JPypeException(int type,
 }
 
 JPypeException::JPypeException(const JPypeException &ex) noexcept
-        : runtime_error(ex.what()), m_Context(ex.m_Context),  m_Type(ex.m_Type),  m_Error(ex.m_Error),
+        : runtime_error(ex.what()), m_Type(ex.m_Type),  m_Error(ex.m_Error),
         m_Trace(ex.m_Trace), m_Throwable(ex.m_Throwable)
 {
 }
@@ -76,7 +75,6 @@ JPypeException& JPypeException::operator
 	{
 		return *this;
 	}
-	m_Context = ex.m_Context;
 	m_Type = ex.m_Type;
 	m_Trace = ex.m_Trace;
 	m_Throwable = ex.m_Throwable;
@@ -91,37 +89,6 @@ void JPypeException::from(const JPStackI
 	m_Trace.push_back(info);
 }
 
-// Okay from this point on we have to suit up in full Kevlar because
-// this code must handle every conceivable and still reach a resolution.
-// Exceptions may be throws during initialization where only a fraction
-// of the resources are available, during the middle of normal operation,
-// or worst of all as the system is being yanked out from under us during
-// shutdown.  Each and every one of these cases must be considered.
-// Further each and every function called here must be hardened similarly
-// or they will become the weak link. And remember it is not paranoia if
-// they are actually out to get you.
-//
-// Onward my friends to victory or a glorious segfault!
-/*
-string JPypeException::getMessage()
-{
-	JP_TRACE_IN("JPypeException::getMessage");
-	// Must be bullet proof
-	try
-	{
-		stringstream str;
-		str << m_Message << endl;
-		JP_TRACE(str.str());
-		return str.str();
-		// GCOVR_EXCL_START
-	} catch (...)
-	{
-		return "error during get message";
-	}
-	JP_TRACE_OUT;
-	// GCOVR_EXCL_STOP
-}*/
-
 bool isJavaThrowable(PyObject* exceptionClass)
 {
 	JPClass* cls = PyJPClass_getJPClass(exceptionClass);
@@ -135,7 +102,8 @@ void JPypeException::convertJavaToPython
 	// Welcome to paranoia land, where they really are out to get you!
 	JP_TRACE_IN("JPypeException::convertJavaToPython");
 	// GCOVR_EXCL_START
-	if (m_Context == nullptr)
+	JPContext* context = JPContext_global;
+	if (context == nullptr)
 	{
 		PyErr_SetString(PyExc_RuntimeError, "Unable to convert java error, context is null.");
 		return;
@@ -143,28 +111,28 @@ void JPypeException::convertJavaToPython
 	// GCOVR_EXCL_STOP
 
 	// Okay we can get to a frame to talk to the object
-	JPJavaFrame frame = JPJavaFrame::external(m_Context, m_Context->getEnv());
+	JPJavaFrame frame = JPJavaFrame::external(context->getEnv());
 	jthrowable th = m_Throwable.get();
 	jvalue v;
 	v.l = th;
 	// GCOVR_EXCL_START
 	// This is condition is only hit if something fails during the initial boot
-	if (m_Context->getJavaContext() == nullptr || m_Context->m_Context_GetExcClassID == nullptr)
+	if (context->getJavaContext() == nullptr || context->m_Context_GetExcClassID == nullptr)
 	{
 		PyErr_SetString(PyExc_SystemError, frame.toString(th).c_str());
 		return;
 	}
 	// GCOVR_EXCL_STOP
-	jlong pycls = frame.CallLongMethodA(m_Context->getJavaContext(), m_Context->m_Context_GetExcClassID, &v);
+	jlong pycls = frame.CallLongMethodA(context->getJavaContext(), context->m_Context_GetExcClassID, &v);
 	if (pycls != 0)
 	{
-		jlong value = frame.CallLongMethodA(m_Context->getJavaContext(), m_Context->m_Context_GetExcValueID, &v);
+		jlong value = frame.CallLongMethodA(context->getJavaContext(), context->m_Context_GetExcValueID, &v);
 		PyErr_SetObject((PyObject*) pycls, (PyObject*) value);
 		return;
 	}
 	JP_TRACE("Check typemanager");
 	// GCOVR_EXCL_START
-	if (!m_Context->isRunning())
+	if (!context->isRunning())
 	{
 		PyErr_SetString(PyExc_RuntimeError, frame.toString((jobject) th).c_str());
 		return;
@@ -216,11 +184,12 @@ void JPypeException::convertJavaToPython
 		{
 			jvalue a;
 			a.l = (jobject) jcause;
-			JPPyObject prev = frame.getContext()->_java_lang_Object->convertToPythonObject(frame, a, false);
+			JPPyObject prev = context->_java_lang_Object->convertToPythonObject(frame, a, false);
 			PyJPException_normalize(frame, prev, jcause, th);
 			PyException_SetCause(cause.get(), prev.keep());
 		}
-		PyException_SetTraceback(cause.get(), trace.get());
+		if (trace.get() != nullptr)
+			PyException_SetTraceback(cause.get(), trace.get());
 		PyException_SetCause(pyvalue.get(), cause.keep());
 	}	catch (JPypeException& ex)
 	{
@@ -235,10 +204,11 @@ void JPypeException::convertJavaToPython
 	JP_TRACE_OUT; // GCOVR_EXCL_LINE
 }
 
-void JPypeException::convertPythonToJava(JPContext* context)
+void JPypeException::convertPythonToJava()
 {
 	JP_TRACE_IN("JPypeException::convertPythonToJava");
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
+	JPContext *context = frame.getContext();
 	jthrowable th;
 	JPPyErrFrame eframe;
 	if (eframe.good && isJavaThrowable(eframe.m_ExceptionClass.get()))
@@ -303,12 +273,6 @@ void JPypeException::toPython()
 		} else if (m_Type == JPError::_python_error)
 		{
 			// Already on the stack
-		} else if (m_Type == JPError::_method_not_found)
-		{
-			// This is hit when a proxy fails to implement a required
-			// method.  Only older style proxies should be able hit this.
-			JP_TRACE("Runtime error");
-			PyErr_SetString(PyExc_RuntimeError, mesg);
 		}// This section is only reachable during startup of the JVM.
 			// GCOVR_EXCL_START
 		else if (m_Type == JPError::_os_error_unix)
@@ -408,13 +372,14 @@ void JPypeException::toPython()
 	JP_TRACE_OUT; // GCOVR_EXCL_LINE
 }
 
-void JPypeException::toJava(JPContext *context)
+void JPypeException::toJava()
 {
 	JP_TRACE_IN("JPypeException::toJava");
+	JPContext* context = JPContext_global;
 	try
 	{
 		const char* mesg = what();
-		JPJavaFrame frame = JPJavaFrame::external(context, context->getEnv());
+		JPJavaFrame frame = JPJavaFrame::external(context->getEnv());
 		if (m_Type == JPError::_java_error)
 		{
 			JP_TRACE("Java exception");
@@ -428,17 +393,11 @@ void JPypeException::toJava(JPContext *c
 			return;
 		}
 
-		if (m_Type == JPError::_method_not_found)
-		{
-			frame.ThrowNew(context->m_NoSuchMethodError.get(), mesg);
-			return;
-		}
-
 		if (m_Type == JPError::_python_error)
 		{
 			JPPyCallAcquire callback;
 			JP_TRACE("Python exception");
-			convertPythonToJava(context);
+			convertPythonToJava();
 			return;
 		}
 
@@ -448,7 +407,7 @@ void JPypeException::toJava(JPContext *c
 			// All others are Python errors
 			JP_TRACE(Py_TYPE(m_Error.l)->tp_name);
 			PyErr_SetString((PyObject*) m_Error.l, mesg);
-			convertPythonToJava(context);
+			convertPythonToJava();
 			return;
 		}
 
@@ -511,7 +470,7 @@ PyObject *tb_create(
 	JPPyObject lasti = JPPyObject::claim(PyLong_FromLong(PyFrame_GetLasti(pframe)));
 #endif
 	JPPyObject linenuma = JPPyObject::claim(PyLong_FromLong(linenum));
-	JPPyObject tuple = JPPyObject::call(PyTuple_Pack(4, Py_None, frame.get(), lasti.get(), linenuma.get()));
+	JPPyObject tuple = JPPyTuple_Pack(Py_None, frame.get(), lasti.get(), linenuma.get());
 	JPPyObject traceback = JPPyObject::accept(PyObject_Call((PyObject*) &PyTraceBack_Type, tuple.get(), NULL));
 
 	// We could fail in process
@@ -574,6 +533,10 @@ JPPyObject PyTrace_FromJavaException(JPJ
 		jint lineNum =
 				frame.CallIntMethodA(frame.GetObjectArrayElement(obj, i + 3), context->_java_lang_Integer->m_IntValueID, nullptr);
 
+		// sending -1 will cause issues on Windows
+		if (lineNum<0)
+			lineNum = 0;
+
 		last_traceback = tb_create(last_traceback, dict,  filename.c_str(),
 				method.c_str(), lineNum);
 		frame.DeleteLocalRef(jclassname);
diff -pruN 1.5.0-1/native/common/jp_field.cpp 1.6.0-1/native/common/jp_field.cpp
--- 1.5.0-1/native/common/jp_field.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_field.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -38,7 +38,7 @@ JPField::~JPField()
 JPPyObject JPField::getStaticField()
 {
 	JP_TRACE_IN("JPField::getStaticAttribute");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	return m_Type->getStaticField(frame, m_Class->getJavaClass(), m_FieldID);
 	JP_TRACE_OUT;
 }
@@ -46,7 +46,7 @@ JPPyObject JPField::getStaticField()
 void JPField::setStaticField(PyObject *pyobj)
 {
 	JP_TRACE_IN("JPField::setStaticAttribute");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	m_Type->setStaticField(frame, m_Class->getJavaClass(), m_FieldID, pyobj);
 	JP_TRACE_OUT;
 }
@@ -54,7 +54,7 @@ void JPField::setStaticField(PyObject *p
 JPPyObject JPField::getField(jobject inst)
 {
 	JP_TRACE_IN("JPField::getAttribute");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	ASSERT_NOT_NULL(m_Type);
 	JP_TRACE("field type", m_Type->getCanonicalName());
 	return m_Type->getField(frame, inst, m_FieldID);
@@ -64,7 +64,7 @@ JPPyObject JPField::getField(jobject ins
 void JPField::setField(jobject inst, PyObject *pyobj)
 {
 	JP_TRACE_IN("JPField::setAttribute");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Class->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	m_Type->setField(frame, inst, m_FieldID, pyobj);
 	JP_TRACE_OUT;
 }
diff -pruN 1.5.0-1/native/common/jp_floattype.cpp 1.6.0-1/native/common/jp_floattype.cpp
--- 1.5.0-1/native/common/jp_floattype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_floattype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -28,6 +28,11 @@ JPFloatType::JPFloatType()
 JPFloatType::~JPFloatType()
 = default;
 
+JPClass* JPFloatType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Float;
+}
+
 JPPyObject JPFloatType::convertToPythonObject(JPJavaFrame& frame, jvalue value, bool cast)
 {
 	PyTypeObject * wrapper = getHost();
@@ -37,10 +42,8 @@ JPPyObject JPFloatType::convertToPythonO
 	return obj;
 }
 
-JPValue JPFloatType::getValueFromObject(const JPValue& obj)
+JPValue JPFloatType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
 	jobject jo = obj.getValue().l;
 	auto* jb = dynamic_cast<JPBoxedType*>( frame.findClassForObject(jo));
@@ -94,7 +97,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_float->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_byte->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_char->getHost());
@@ -124,11 +127,11 @@ JPMatch::Type JPFloatType::findJavaConve
 
 void JPFloatType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	asJFloatConversion.getInfo(this, info);
 	asFloatLongConversion.getInfo(this, info);
 	asFloatConversion.getInfo(this, info);
-	PyList_Append(info.ret, (PyObject*) m_Context->_float->getHost());
+	PyList_Append(info.ret, (PyObject*) JPContext_global->_float->getHost());
 }
 
 jarray JPFloatType::newArrayOf(JPJavaFrame& frame, jsize sz)
@@ -268,7 +271,7 @@ void JPFloatType::setArrayItem(JPJavaFra
 
 void JPFloatType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_Memory = (void*) frame.GetFloatArrayElements(
 			(jfloatArray) view.m_Array->getJava(), &view.m_IsCopy);
 	view.m_Buffer.format = "f";
@@ -279,7 +282,7 @@ void JPFloatType::releaseView(JPArrayVie
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseFloatArrayElements((jfloatArray) view.m_Array->getJava(),
 				(jfloat*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_functional.cpp 1.6.0-1/native/common/jp_functional.cpp
--- 1.5.0-1/native/common/jp_functional.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_functional.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -61,7 +61,7 @@ public:
 			JPPyObject defaults = JPPyObject::accept(PyObject_GetAttrString(func, "__defaults__"));
 			if (!defaults.isNull() && defaults.get() != Py_None)
 				optional = PyTuple_Size(defaults.get());
-			const int jargs = cls->getContext()->getTypeManager()->interfaceParameterCount(cls);
+			const int jargs = JPContext_global->getTypeManager()->interfaceParameterCount(cls);
 			// Too few arguments
 			if (!is_varargs && args < jargs)
 				return match.type = JPMatch::_none;
@@ -79,7 +79,7 @@ public:
 			JPPyObject defaults = JPPyObject::accept(PyObject_GetAttrString(func, "__defaults__"));
 			if (!defaults.isNull() && defaults.get() != Py_None)
 				optional = PyTuple_Size(defaults.get());
-			const int jargs = cls->getContext()->getTypeManager()->interfaceParameterCount(cls);
+			const int jargs = JPContext_global->getTypeManager()->interfaceParameterCount(cls);
 			// Bound self argument removes one argument
 			if ((PyMethod_Self(match.object))!=nullptr) // borrowed
 				args--;
@@ -106,16 +106,17 @@ public:
 	{
 		auto *cls = (JPFunctional*) match.closure;
 		JP_TRACE_IN("JPConversionFunctional::convert");
-		JPContext *context = PyJPModule_getContext();
-		JPJavaFrame frame = JPJavaFrame::inner(context);
+		JPJavaFrame frame = JPJavaFrame::inner();
 		auto *self = (PyJPProxy*) PyJPProxy_Type->tp_alloc(PyJPProxy_Type, 0);
 		JP_PY_CHECK();
 		JPClassList cl;
 		cl.push_back(cls);
-		self->m_Proxy = new JPProxyFunctional(context, self, cl);
+		self->m_Proxy = new JPProxyFunctional(self, cl);
 		self->m_Target = match.object;
+		self->m_Dispatch = match.object;
 		self->m_Convert = true;
-		Py_INCREF(match.object);
+		Py_INCREF(self->m_Target);
+		Py_INCREF(self->m_Dispatch);
 		jvalue v = self->m_Proxy->getProxy();
 		v.l = frame.keep(v.l);
 		Py_DECREF(self);
diff -pruN 1.5.0-1/native/common/jp_gc.cpp 1.6.0-1/native/common/jp_gc.cpp
--- 1.5.0-1/native/common/jp_gc.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_gc.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -111,9 +111,8 @@ void JPGarbageCollection::triggered()
 	}
 }
 
-JPGarbageCollection::JPGarbageCollection(JPContext *context)
+JPGarbageCollection::JPGarbageCollection()
 {
-	m_Context = context;
 	running = false;
 	in_python_gc = false;
 	java_triggered = false;
@@ -154,6 +153,14 @@ void JPGarbageCollection::init(JPJavaFra
 	_SystemClass = (jclass) frame.NewGlobalRef(frame.FindClass("java/lang/System"));
 	_gcMethodID = frame.GetStaticMethodID(_SystemClass, "gc", "()V");
 
+	jclass ctxt = JPContext_global->m_ContextClass.get();
+	_ContextClass = ctxt;
+	_totalMemoryID = frame.GetStaticMethodID(ctxt, "getTotalMemory", "()J");
+	_freeMemoryID = frame.GetStaticMethodID(ctxt, "getFreeMemory", "()J");
+	_maxMemoryID = frame.GetStaticMethodID(ctxt, "getMaxMemory", "()J");
+	_usedMemoryID = frame.GetStaticMethodID(ctxt, "getUsedMemory", "()J");
+	_heapMemoryID = frame.GetStaticMethodID(ctxt, "getHeapMemory", "()J");
+
 	running = true;
 	high_water = getWorkingSize();
 	limit = high_water + DELTA_LIMIT;
@@ -237,17 +244,31 @@ void JPGarbageCollection::onEnd()
 		Py_ssize_t pred = current + 2 * (current - last);
 		last = current;
 		if ((Py_ssize_t) pred > (Py_ssize_t) limit)
+		{
 			run_gc = 2;
+			limit = high_water + (high_water>>3) + 8 * (current - last);
+		}
 
-		//		printf("consider gc %d (%ld, %ld, %ld, %ld) %ld\n", run_gc,
-		//				current, low_water, high_water, limit, limit - pred);
+#if 0
+		{
+			JPJavaFrame frame = JPJavaFrame::outer();
+			jlong totalMemory = frame.CallStaticLongMethodA(_ContextClass, _totalMemoryID, nullptr);
+			jlong freeMemory = frame.CallStaticLongMethodA(_ContextClass, _freeMemoryID, nullptr);
+			jlong maxMemory = frame.CallStaticLongMethodA(_ContextClass, _maxMemoryID, nullptr);
+			jlong usedMemory = frame.CallStaticLongMethodA(_ContextClass, _usedMemoryID, nullptr);
+			jlong heapMemory = frame.CallStaticLongMethodA(_ContextClass, _heapMemoryID, nullptr);
+			printf("consider gc run=%d (current=%ld, low=%ld, high=%ld, limit=%ld) %ld\n", run_gc,
+				current, low_water, high_water, limit, limit - pred);
+			printf(" java total=%ld free=%ld max=%ld used=%ld heap=%ld\n", totalMemory, freeMemory, maxMemory, usedMemory, heapMemory);
+		}
+#endif
 
 		if (run_gc > 0)
 		{
 			// Move up the low water
 			low_water = (low_water + high_water) / 2;
 			// Don't reset the limit if it was count triggered
-			JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+			JPJavaFrame frame = JPJavaFrame::outer();
 			frame.CallStaticVoidMethodA(_SystemClass, _gcMethodID, nullptr);
 			python_triggered++;
 		}
diff -pruN 1.5.0-1/native/common/jp_inttype.cpp 1.6.0-1/native/common/jp_inttype.cpp
--- 1.5.0-1/native/common/jp_inttype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_inttype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -27,6 +27,11 @@ JPIntType::JPIntType()
 JPIntType::~JPIntType()
 = default;
 
+JPClass* JPIntType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Integer;
+}
+
 JPPyObject JPIntType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast)
 {
 	JPPyObject tmp = JPPyObject::call(PyLong_FromLong(field(val)));
@@ -37,10 +42,8 @@ JPPyObject JPIntType::convertToPythonObj
 	return out;
 }
 
-JPValue JPIntType::getValueFromObject(const JPValue& obj)
+JPValue JPIntType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
 	jobject jo = obj.getValue().l;
 	auto* jb = dynamic_cast<JPBoxedType*>( frame.findClassForObject(jo));
@@ -92,7 +95,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_int->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_byte->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_char->getHost());
@@ -120,11 +123,11 @@ JPMatch::Type JPIntType::findJavaConvers
 
 void JPIntType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jintConversion.getInfo(this, info);
 	intConversion.getInfo(this, info);
 	intNumberConversion.getInfo(this, info);
-	PyList_Append(info.ret, (PyObject*) m_Context->_int->getHost());
+	PyList_Append(info.ret, (PyObject*) JPContext_global->_int->getHost());
 }
 
 jarray JPIntType::newArrayOf(JPJavaFrame& frame, jsize sz)
@@ -270,7 +273,7 @@ void JPIntType::setArrayItem(JPJavaFrame
 
 void JPIntType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_IsCopy = false;
 	view.m_Memory = (void*) frame.GetIntArrayElements(
 			(jintArray) view.m_Array->getJava(), &view.m_IsCopy);
@@ -282,7 +285,7 @@ void JPIntType::releaseView(JPArrayView&
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseIntArrayElements((jintArray) view.m_Array->getJava(),
 				(jint*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_javaframe.cpp 1.6.0-1/native/common/jp_javaframe.cpp
--- 1.5.0-1/native/common/jp_javaframe.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_javaframe.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -34,11 +34,22 @@ static void jpype_frame_check(int popped
 #define JP_FRAME_CHECK() if (false) while (false)
 #endif
 
-JPJavaFrame::JPJavaFrame(JPContext* context, JNIEnv* p_env, int size, bool outer)
-: m_Context(context), m_Env(p_env), m_Popped(false), m_Outer(outer)
+JPJavaFrame::JPJavaFrame(JNIEnv* p_env, int size, bool outer)
+: m_Env(p_env), m_Popped(false), m_Outer(outer)
 {
+	JPContext* context = JPContext_global;
+
 	if (p_env == nullptr)
+	{
+		if (outer)
+		{
+#ifdef JP_INSTRUMENTATION
+			PyJPModuleFault_throw(compile_hash("PyJPModule_getContext"));
+#endif
+			assertJVMRunning((JPContext*)context, JP_STACKINFO());
+		}
 		m_Env = context->getEnv();
+	}
 
 	// Create a memory management frame to live in
 	m_Env->PushLocalFrame(size);
@@ -46,13 +57,19 @@ JPJavaFrame::JPJavaFrame(JPContext* cont
 }
 
 JPJavaFrame::JPJavaFrame(const JPJavaFrame& frame)
-: m_Context(frame.m_Context), m_Env(frame.m_Env), m_Popped(false), m_Outer(false)
+: m_Env(frame.m_Env), m_Popped(false), m_Outer(false)
 {
 	// Create a memory management frame to live in
 	m_Env->PushLocalFrame(LOCAL_FRAME_DEFAULT);
 	JP_TRACE_JAVA("JavaFrame (copy)", (jobject) - 1);
 }
 
+JPContext* JPJavaFrame::getContext()
+{
+	// We can add guard statements here.
+	return JPContext_global;
+}
+
 jobject JPJavaFrame::keep(jobject obj)
 {
 	if (m_Outer)
@@ -1022,15 +1039,22 @@ jlong JPJavaFrame::GetDirectBufferCapaci
 
 jboolean JPJavaFrame::isBufferReadOnly(jobject obj)
 {
-	return CallBooleanMethodA(obj, m_Context->m_Buffer_IsReadOnlyID, nullptr);
+	return CallBooleanMethodA(obj, getContext()->m_Buffer_IsReadOnlyID, nullptr);
+}
+
+jobject JPJavaFrame::asReadOnlyBuffer(jobject obj)
+{
+	JAVA_RETURN_OBJ(jobject, "JPJavaFrame::asReadOnlyBuffer",
+			m_Env->CallObjectMethod(obj, getContext()->m_Buffer_AsReadOnlyID));
 }
 
 jboolean JPJavaFrame::orderBuffer(jobject obj)
 {
 	jvalue arg;
 	arg.l = obj;
-	return CallBooleanMethodA(m_Context->m_JavaContext.get(),
-			m_Context->m_Context_OrderID, &arg);
+	JPContext *context = getContext();
+	return CallBooleanMethodA(context->m_JavaContext.get(),
+			context->m_Context_OrderID, &arg);
 }
 
 // GCOVR_EXCL_START
@@ -1038,7 +1062,7 @@ jboolean JPJavaFrame::orderBuffer(jobjec
 
 jclass JPJavaFrame::getClass(jobject obj)
 {
-	return (jclass) CallObjectMethodA(obj, m_Context->m_Object_GetClassID, nullptr);
+	return (jclass) CallObjectMethodA(obj, getContext()->m_Object_GetClassID, nullptr);
 }
 // GCOVR_EXCL_STOP
 
@@ -1075,7 +1099,9 @@ public:
 
 string JPJavaFrame::toString(jobject o)
 {
-	auto str = (jstring) CallObjectMethodA(o, m_Context->m_Object_ToStringID, nullptr);
+	auto str = (jstring) CallObjectMethodA(o, getContext()->m_Object_ToStringID, nullptr);
+	if (str == nullptr)
+		return "null";
 	return toStringUTF8(str);
 }
 
@@ -1101,100 +1127,105 @@ jstring JPJavaFrame::fromStringUTF8(cons
 
 jobject JPJavaFrame::toCharArray(jstring jstr)
 {
-	return CallObjectMethodA(jstr, m_Context->m_String_ToCharArrayID, nullptr);
+	return CallObjectMethodA(jstr, getContext()->m_String_ToCharArrayID, nullptr);
 }
 
 bool JPJavaFrame::equals(jobject o1, jobject o2 )
 {
 	jvalue args;
 	args.l = o2;
-	return CallBooleanMethodA(o1, m_Context->m_Object_EqualsID, &args) != 0;
+	return CallBooleanMethodA(o1, getContext()->m_Object_EqualsID, &args) != 0;
 }
 
 jint JPJavaFrame::hashCode(jobject o)
 {
-	return CallIntMethodA(o, m_Context->m_Object_HashCodeID, nullptr);
+	return CallIntMethodA(o, getContext()->m_Object_HashCodeID, nullptr);
 }
 
 jobject JPJavaFrame::collectRectangular(jarray obj)
 {
-	if (m_Context->m_Context_collectRectangularID == nullptr)
+	JPContext* context = getContext();
+	if (context->m_Context_collectRectangularID == nullptr)
 		return nullptr;
 	jvalue v;
 	v.l = (jobject) obj;
 	JAVA_RETURN(jobject, "JPJavaFrame::collectRectangular",
 			CallObjectMethodA(
-			m_Context->m_JavaContext.get(),
-			m_Context->m_Context_collectRectangularID, &v));
+			context->m_JavaContext.get(),
+			context->m_Context_collectRectangularID, &v));
 }
 
 jobject JPJavaFrame::assemble(jobject dims, jobject parts)
 {
-	if (m_Context->m_Context_collectRectangularID == nullptr)
+	JPContext* context = getContext();
+	if (context->m_Context_collectRectangularID == nullptr)
 		return nullptr;
 	jvalue v[2];
 	v[0].l = (jobject) dims;
 	v[1].l = (jobject) parts;
 	JAVA_RETURN(jobject, "JPJavaFrame::assemble",
 			CallObjectMethodA(
-			m_Context->m_JavaContext.get(),
-			m_Context->m_Context_assembleID, v));
+			context->m_JavaContext.get(),
+			context->m_Context_assembleID, v));
 }
 
 jobject JPJavaFrame::newArrayInstance(jclass c, jintArray dims)
 {
+	JPContext* context = getContext();
 	jvalue v[2];
 	v[0].l = (jobject) c;
 	v[1].l = (jobject) dims;
 	JAVA_RETURN(jobject, "JPJavaFrame::newArrayInstance",
 			CallStaticObjectMethodA(
-			m_Context->m_Array.get(),
-			m_Context->m_Array_NewInstanceID, v));
+			context->m_Array.get(),
+			context->m_Array_NewInstanceID, v));
 }
 
 jobject JPJavaFrame::callMethod(jobject method, jobject obj, jobject args)
 {
 	JP_TRACE_IN("JPJavaFrame::callMethod");
-	if (m_Context->m_CallMethodID == nullptr)
+	JPContext* context = getContext();
+	if (context->m_CallMethodID == nullptr)
 		return nullptr;
 	JPJavaFrame frame(*this);
 	jvalue v[3];
 	v[0].l = method;
 	v[1].l = obj;
 	v[2].l = args;
-	return frame.keep(frame.CallObjectMethodA(m_Context->m_JavaContext.get(), m_Context->m_CallMethodID, v));
+	return frame.keep(frame.CallObjectMethodA(context->m_Reflector.get(), context->m_CallMethodID, v));
 	JP_TRACE_OUT;
 }
 
 string JPJavaFrame::getFunctional(jclass c)
 {
+	JPContext* context = getContext();
 	jvalue v;
 	v.l = (jobject) c;
 	return toStringUTF8((jstring) CallStaticObjectMethodA(
-			m_Context->m_ContextClass.get(),
-			m_Context->m_Context_GetFunctionalID, &v));
+			context->m_ContextClass.get(),
+			context->m_Context_GetFunctionalID, &v));
 }
 
 JPClass *JPJavaFrame::findClass(jclass obj)
 {
-	return m_Context->getTypeManager()->findClass(obj);
+	return getContext()->getTypeManager()->findClass(obj);
 }
 
 JPClass *JPJavaFrame::findClassByName(const string& name)
 {
-	return m_Context->getTypeManager()->findClassByName(name);
+	return getContext()->getTypeManager()->findClassByName(name);
 }
 
 JPClass *JPJavaFrame::findClassForObject(jobject obj)
 {
-	return m_Context->getTypeManager()->findClassForObject(obj);
+	return getContext()->getTypeManager()->findClassForObject(obj);
 }
 
 jint JPJavaFrame::compareTo(jobject obj, jobject obj2)
 {
 	jvalue v;
 	v.l = obj2;
-	jint ret = m_Env->CallIntMethodA(obj, m_Context->m_CompareToID, &v);
+	jint ret = m_Env->CallIntMethodA(obj, getContext()->m_CompareToID, &v);
 	if (m_Env->ExceptionOccurred())
 	{
 		m_Env->ExceptionClear();
@@ -1205,28 +1236,30 @@ jint JPJavaFrame::compareTo(jobject obj,
 
 jthrowable JPJavaFrame::getCause(jthrowable th)
 {
-	return (jthrowable) CallObjectMethodA((jobject) th, m_Context->m_Throwable_GetCauseID, nullptr);
+	return (jthrowable) CallObjectMethodA((jobject) th, getContext()->m_Throwable_GetCauseID, nullptr);
 }
 
 jstring JPJavaFrame::getMessage(jthrowable th)
 {
-	return (jstring) CallObjectMethodA((jobject) th, m_Context->m_Throwable_GetMessageID, nullptr);
+	return (jstring) CallObjectMethodA((jobject) th, getContext()->m_Throwable_GetMessageID, nullptr);
 }
 
 jboolean JPJavaFrame::isPackage(const string& str)
 {
+	JPContext* context = getContext();
 	jvalue v;
 	v.l = fromStringUTF8(str);
 	JAVA_RETURN(jboolean, "JPJavaFrame::isPackage",
-			CallBooleanMethodA(m_Context->m_JavaContext.get(), m_Context->m_Context_IsPackageID, &v));
+			CallBooleanMethodA(context->m_JavaContext.get(), context->m_Context_IsPackageID, &v));
 }
 
 jobject JPJavaFrame::getPackage(const string& str)
 {
+	JPContext* context = getContext();
 	jvalue v;
 	v.l = fromStringUTF8(str);
 	JAVA_RETURN(jobject, "JPJavaFrame::getPackage",
-			CallObjectMethodA(m_Context->m_JavaContext.get(), m_Context->m_Context_GetPackageID, &v));
+			CallObjectMethodA(context->m_JavaContext.get(), context->m_Context_GetPackageID, &v));
 }
 
 jobject JPJavaFrame::getPackageObject(jobject pkg, const string& str)
@@ -1234,23 +1267,24 @@ jobject JPJavaFrame::getPackageObject(jo
 	jvalue v;
 	v.l = fromStringUTF8(str);
 	JAVA_RETURN(jobject, "JPJavaFrame::getPackageObject",
-			CallObjectMethodA(pkg, m_Context->m_Package_GetObjectID, &v));
+			CallObjectMethodA(pkg, getContext()->m_Package_GetObjectID, &v));
 }
 
 jarray JPJavaFrame::getPackageContents(jobject pkg)
 {
 	jvalue v;
 	JAVA_RETURN(auto, "JPJavaFrame::getPackageContents",
-			(jarray) CallObjectMethodA(pkg, m_Context->m_Package_GetContentsID, &v));
+			(jarray) CallObjectMethodA(pkg, getContext()->m_Package_GetContentsID, &v));
 }
 
 void JPJavaFrame::newWrapper(JPClass* cls)
 {
+	JPContext* context = getContext();
 	JPPyCallRelease call;
 	jvalue jv;
 	jv.j = (jlong) cls;
-	CallVoidMethodA(m_Context->getJavaContext(),
-			m_Context->m_Context_NewWrapperID, &jv);
+	CallVoidMethodA(context->getJavaContext(),
+			context->m_Context_NewWrapperID, &jv);
 }
 
 void JPJavaFrame::registerRef(jobject obj, PyObject* hostRef)
@@ -1265,9 +1299,10 @@ void JPJavaFrame::registerRef(jobject ob
 
 void JPJavaFrame::clearInterrupt(bool throws)
 {
+	JPContext* context = getContext();
 	JPPyCallRelease call;
 	jvalue jv;
 	jv.z = throws;
-	CallVoidMethodA(m_Context->m_ContextClass.get(),
-			m_Context->m_Context_ClearInterruptID, &jv);
+	CallVoidMethodA(context->m_ContextClass.get(),
+			context->m_Context_ClearInterruptID, &jv);
 }
diff -pruN 1.5.0-1/native/common/jp_longtype.cpp 1.6.0-1/native/common/jp_longtype.cpp
--- 1.5.0-1/native/common/jp_longtype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_longtype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -27,6 +27,11 @@ JPLongType::JPLongType()
 JPLongType::~JPLongType()
 = default;
 
+JPClass* JPLongType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Long;
+}
+
 JPPyObject JPLongType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast)
 {
 	JPPyObject tmp = JPPyObject::call(PyLong_FromLongLong(field(val)));
@@ -35,10 +40,8 @@ JPPyObject JPLongType::convertToPythonOb
 	return out;
 }
 
-JPValue JPLongType::getValueFromObject(const JPValue& obj)
+JPValue JPLongType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
 	jobject jo = obj.getValue().l;
 	auto* jb = dynamic_cast<JPBoxedType*>( frame.findClassForObject(jo));
@@ -91,7 +94,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_long->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_byte->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_char->getHost());
@@ -120,11 +123,11 @@ JPMatch::Type JPLongType::findJavaConver
 
 void JPLongType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jlongConversion.getInfo(this, info);
 	longConversion.getInfo(this, info);
 	longNumberConversion.getInfo(this, info);
-	PyList_Append(info.ret, (PyObject*) m_Context->_long->getHost());
+	PyList_Append(info.ret, (PyObject*) JPContext_global->_long->getHost());
 }
 
 jarray JPLongType::newArrayOf(JPJavaFrame& frame, jsize sz)
@@ -270,7 +273,7 @@ void JPLongType::setArrayItem(JPJavaFram
 
 void JPLongType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_Memory = (void*) frame.GetLongArrayElements(
 			(jlongArray) view.m_Array->getJava(), &view.m_IsCopy);
 	view.m_Buffer.format = "=q";
@@ -281,7 +284,7 @@ void JPLongType::releaseView(JPArrayView
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseLongArrayElements((jlongArray) view.m_Array->getJava(),
 				(jlong*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_method.cpp 1.6.0-1/native/common/jp_method.cpp
--- 1.5.0-1/native/common/jp_method.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_method.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -252,9 +252,9 @@ JPPyObject JPMethod::invoke(JPJavaFrame&
 JPPyObject JPMethod::invokeCallerSensitive(JPMethodMatch& match, JPPyObjectVector& arg, bool instance)
 {
 	JP_TRACE_IN("JPMethod::invokeCallerSensitive");
-	JPContext *context = m_Class->getContext();
 	size_t alen = m_ParameterTypes.size();
-	JPJavaFrame frame = JPJavaFrame::outer(context, (int) (8 + alen));
+	JPJavaFrame frame = JPJavaFrame::outer((int) (8 + alen));
+	JPContext *context = frame.getContext();
 	JPClass* retType = m_ReturnType;
 
 	// Pack the arguments
@@ -285,7 +285,7 @@ JPPyObject JPMethod::invokeCallerSensiti
 			auto* type = dynamic_cast<JPPrimitiveType*>( cls);
 			PyObject *u = arg[i + match.m_Skip];
 			JPMatch conv(&frame, u);
-			JPClass *boxed = type->getBoxedClass(context);
+			JPClass *boxed = type->getBoxedClass(frame);
 			boxed->findJavaConversion(conv);
 			jvalue v = conv.convert();
 			frame.SetObjectArrayElement(ja, i, v.l);
@@ -309,8 +309,8 @@ JPPyObject JPMethod::invokeCallerSensiti
 	if (retType->isPrimitive())
 	{
 		JP_TRACE("Return primitive");
-		JPClass *boxed = (dynamic_cast<JPPrimitiveType*>( retType))->getBoxedClass(context);
-		JPValue out = retType->getValueFromObject(JPValue(boxed, o));
+		JPClass *boxed = (dynamic_cast<JPPrimitiveType*>( retType))->getBoxedClass(frame);
+		JPValue out = retType->getValueFromObject(frame, JPValue(boxed, o));
 		return retType->convertToPythonObject(frame, out.getValue(), false);
 	} else
 	{
@@ -336,8 +336,7 @@ JPValue JPMethod::invokeConstructor(JPJa
 string JPMethod::matchReport(JPPyObjectVector& args)
 {
 	ensureTypeCache();
-	JPContext *context = m_Class->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	std::stringstream res;
 
 	res << m_ReturnType->getCanonicalName() << " (";
@@ -397,5 +396,5 @@ void JPMethod::ensureTypeCache()
 	if (m_ReturnType != (JPClass*) (-1))
 		return;
 
-	m_Class->getContext()->getTypeManager()->populateMethod(this, m_Method.get());
+	JPContext_global->getTypeManager()->populateMethod(this, m_Method.get());
 }
diff -pruN 1.5.0-1/native/common/jp_monitor.cpp 1.6.0-1/native/common/jp_monitor.cpp
--- 1.5.0-1/native/common/jp_monitor.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_monitor.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -16,9 +16,8 @@
 #include "jpype.h"
 #include "jp_monitor.h"
 
-JPMonitor::JPMonitor(JPContext* context, jobject value) : m_Value(context, value)
+JPMonitor::JPMonitor(jobject value) : m_Value(value)
 {
-	m_Context = context;
 }
 
 JPMonitor::~JPMonitor()
@@ -29,13 +28,13 @@ void JPMonitor::enter()
 	// This can hold off for a while so we need to release resource
 	// so that we don't dead lock.
 	JPPyCallRelease call;
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	frame.MonitorEnter(m_Value.get());
 }
 
 void JPMonitor::exit()
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	frame.MonitorExit(m_Value.get());
 }
 
diff -pruN 1.5.0-1/native/common/jp_numbertype.cpp 1.6.0-1/native/common/jp_numbertype.cpp
--- 1.5.0-1/native/common/jp_numbertype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_numbertype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -48,11 +48,11 @@ JPMatch::Type JPNumberType::findJavaConv
 void JPNumberType::getConversionInfo(JPConversionInfo &info)
 {
 	JP_TRACE_IN("JPNumberType::getConversionInfo");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	javaNumberAnyConversion->getInfo(this, info);
 	boxLongConversion->getInfo(this, info);
 	boxDoubleConversion->getInfo(this, info);
 	hintsConversion->getInfo(this, info);
 	PyList_Append(info.ret, PyJPClass_create(frame, this).get());
 	JP_TRACE_OUT;
-}
\ No newline at end of file
+}
diff -pruN 1.5.0-1/native/common/jp_objecttype.cpp 1.6.0-1/native/common/jp_objecttype.cpp
--- 1.5.0-1/native/common/jp_objecttype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_objecttype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -52,7 +52,7 @@ JPMatch::Type JPObjectType::findJavaConv
 void JPObjectType::getConversionInfo(JPConversionInfo &info)
 {
 	JP_TRACE_IN("JPObjectType::getConversionInfo");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	nullConversion->getInfo(this, info);
 	objectConversion->getInfo(this, info);
 	stringConversion->getInfo(this, info);
diff -pruN 1.5.0-1/native/common/jp_platform.cpp 1.6.0-1/native/common/jp_platform.cpp
--- 1.5.0-1/native/common/jp_platform.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_platform.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -64,7 +64,13 @@ public:
 	virtual void loadLibrary(const char* path) override
 	{
 		JP_TRACE_IN("Win32PlatformAdapter::loadLibrary");
-		jvmLibrary = LoadLibrary(path);
+		wchar_t *wpath = Py_DecodeLocale(path, NULL);
+		if (wpath == NULL)
+		{
+			JP_RAISE(PyExc_SystemError, "Unable to get JVM path with locale.");
+		}
+		jvmLibrary = LoadLibraryW(wpath);
+		PyMem_RawFree(wpath);
 		if (jvmLibrary == NULL)
 		{
 			JP_RAISE_OS_ERROR_WINDOWS( GetLastError(), path);
diff -pruN 1.5.0-1/native/common/jp_proxy.cpp 1.6.0-1/native/common/jp_proxy.cpp
--- 1.5.0-1/native/common/jp_proxy.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_proxy.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -22,16 +22,22 @@
 #include "jp_boxedtype.h"
 #include "jp_functional.h"
 
-JPPyObject getArgs(JPContext* context, jlongArray parameterTypePtrs,
-		jobjectArray args)
+JPPyObject getArgs(jlongArray parameterTypePtrs,
+		jobjectArray args, PyObject* self, int addSelf)
 {
 	JP_TRACE_IN("JProxy::getArgs");
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jsize argLen = frame.GetArrayLength(parameterTypePtrs);
-	JPPyObject pyargs = JPPyObject::call(PyTuple_New(argLen));
+	jsize extra = addSelf?1:0;
+	JPPyObject pyargs = JPPyObject::call(PyTuple_New(argLen+extra));
 	JPPrimitiveArrayAccessor<jlongArray, jlong*> accessor(frame, parameterTypePtrs,
 			&JPJavaFrame::GetLongArrayElements, &JPJavaFrame::ReleaseLongArrayElements);
 
+	if (addSelf)
+	{
+		Py_IncRef(self);
+		PyTuple_SetItem(pyargs.get(), 0, self);
+	}
 	jlong* types = accessor.get();
 	for (jsize i = 0; i < argLen; i++)
 	{
@@ -39,8 +45,8 @@ JPPyObject getArgs(JPContext* context, j
 		JPClass* type = frame.findClassForObject(obj);
 		if (type == nullptr)
 			type = reinterpret_cast<JPClass*> (types[i]);
-		JPValue val = type->getValueFromObject(JPValue(type, obj));
-		PyTuple_SetItem(pyargs.get(), i, type->convertToPythonObject(frame, val, false).keep());
+		JPValue val = type->getValueFromObject(frame, JPValue(type, obj));
+		PyTuple_SetItem(pyargs.get(), i+extra, type->convertToPythonObject(frame, val, false).keep());
 	}
 	return pyargs;
 	JP_TRACE_OUT;
@@ -52,17 +58,16 @@ extern "C" JNIEXPORT jobject JNICALL Jav
 		jlong hostObj,
 		jlong returnTypePtr,
 		jlongArray parameterTypePtrs,
-		jobjectArray args)
+		jobjectArray args,
+		jobject missing)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 
 	// We need the resources to be held for the full duration of the proxy.
 	JPPyCallAcquire callback;
 	try
 	{
 		JP_TRACE_IN("JPype_InvocationHandler_hostInvoke");
-		JP_TRACE("context", context);
 		JP_TRACE("hostObj", (void*) hostObj);
 		try
 		{
@@ -70,7 +75,7 @@ extern "C" JNIEXPORT jobject JNICALL Jav
 			// Sanity check, should never be hit
 			if (hostObj == 0)
 			{
-				env->functions->ThrowNew(env, context->m_RuntimeException.get(),
+				env->functions->ThrowNew(env, JPContext_global->m_RuntimeException.get(),
 						"host reference is null");
 				return nullptr;
 			}
@@ -80,15 +85,13 @@ extern "C" JNIEXPORT jobject JNICALL Jav
 			JP_TRACE("Get callable for", cname);
 
 			// Get the callable object
-			JPPyObject callable(((JPProxy*) hostObj)->getCallable(cname));
+			int addSelf = 0;
+			JPProxy* proxy = (JPProxy*) hostObj;
+			JPPyObject callable(proxy->getCallable(cname, addSelf));
 
 			// If method can't be called, throw an exception
 			if (callable.isNull() || callable.get() == Py_None)
-			{
-				JP_TRACE("Callable not found");
-				JP_RAISE_METHOD_NOT_FOUND(cname);
-				return nullptr;
-			}
+                return missing;
 
 			// Find the return type
 			auto* returnClass = (JPClass*) returnTypePtr;
@@ -96,13 +99,13 @@ extern "C" JNIEXPORT jobject JNICALL Jav
 
 			// convert the arguments into a python list
 			JP_TRACE("Convert arguments");
-			JPPyObject pyargs = getArgs(context, parameterTypePtrs, args);
+			JPPyObject pyargs = getArgs(parameterTypePtrs, args, proxy->m_Instance->m_Target, addSelf);
 
 			JP_TRACE("Call Python");
 			JPPyObject returnValue = JPPyObject::call(PyObject_Call(callable.get(), pyargs.get(), nullptr));
 
 			JP_TRACE("Handle return", Py_TYPE(returnValue.get())->tp_name);
-			if (returnClass == context->_void)
+			if (returnClass == JPContext_global->_void)
 			{
 				JP_TRACE("Void return");
 				return nullptr;
@@ -124,7 +127,7 @@ extern "C" JNIEXPORT jobject JNICALL Jav
 				if (returnClass->findJavaConversion(returnMatch) == JPMatch::_none)
 					JP_RAISE(PyExc_TypeError, "Return value is not compatible with required type.");
 				jvalue res = returnMatch.convert();
-				auto *boxed =  dynamic_cast<JPBoxedType *>( (dynamic_cast<JPPrimitiveType*>( returnClass))->getBoxedClass(context));
+				auto *boxed =  dynamic_cast<JPBoxedType *>( (dynamic_cast<JPPrimitiveType*>( returnClass))->getBoxedClass(frame));
 				jvalue res2;
 				res2.l = boxed->box(frame, res);
 				return frame.keep(res2.l);
@@ -142,11 +145,11 @@ extern "C" JNIEXPORT jobject JNICALL Jav
 		} catch (JPypeException& ex)
 		{
 			JP_TRACE("JPypeException raised");
-			ex.toJava(context);
+			ex.toJava();
 		} catch (...)  // GCOVR_EXCL_LINE
 		{
 			JP_TRACE("Other Exception raised");
-			env->functions->ThrowNew(env, context->m_RuntimeException.get(),
+			env->functions->ThrowNew(env, JPContext_global->m_RuntimeException.get(),
 					"unknown error occurred");
 		}
 		return nullptr;
@@ -157,30 +160,29 @@ extern "C" JNIEXPORT jobject JNICALL Jav
 	return NULL;
 }
 
-JPProxy::JPProxy(JPContext* context, PyJPProxy* inst, JPClassList& intf)
-: m_Context(context), m_Instance(inst), m_InterfaceClasses(intf)
+JPProxy::JPProxy(PyJPProxy* inst, JPClassList& intf)
+: m_Instance(inst), m_InterfaceClasses(intf)
 {
 	JP_TRACE_IN("JPProxy::JPProxy");
-	JP_TRACE("Context", m_Context);
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	// Convert the interfaces to a Class[]
 	jobjectArray ar = frame.NewObjectArray((int) intf.size(),
-			m_Context->_java_lang_Class->getJavaClass(), nullptr);
+			JPContext_global->_java_lang_Class->getJavaClass(), nullptr);
 	for (unsigned int i = 0; i < intf.size(); i++)
 	{
 		frame.SetObjectArrayElement(ar, i, intf[i]->getJavaClass());
 	}
 	jvalue v[4];
-	v[0].l = m_Context->getJavaContext();
+	v[0].l = JPContext_global->getJavaContext();
 	v[1].j = (jlong) this;
 	v[2].j = (jlong) & JPProxy::releaseProxyPython;
 	v[3].l = ar;
 
 	// Create the proxy
-	jobject proxy = frame.CallStaticObjectMethodA(context->m_ProxyClass.get(),
-			context->m_Proxy_NewID, v);
-	m_Proxy = JPObjectRef(m_Context, proxy);
+	jobject proxy = frame.CallStaticObjectMethodA(JPContext_global->m_ProxyClass.get(),
+			JPContext_global->m_Proxy_NewID, v);
+	m_Proxy = JPObjectRef(proxy);
 	m_Ref = nullptr;
 	JP_TRACE_OUT;
 }
@@ -189,9 +191,9 @@ JPProxy::~JPProxy()
 {
 	try
 	{
-		if (m_Ref != nullptr && m_Context->isRunning())
+		if (m_Ref != nullptr && JPContext_global->isRunning())
 		{
-			m_Context->getEnv()->DeleteWeakGlobalRef(m_Ref);
+			JPContext_global->getEnv()->DeleteWeakGlobalRef(m_Ref);
 		}
 	} catch (JPypeException &ex)  // GCOVR_EXCL_LINE
 	{
@@ -207,8 +209,7 @@ void JPProxy::releaseProxyPython(void* h
 jvalue JPProxy::getProxy()
 {
 	JP_TRACE_IN("JPProxy::getProxy");
-	JPContext* context = getContext();
-	JPJavaFrame frame = JPJavaFrame::inner(context);
+	JPJavaFrame frame = JPJavaFrame::inner();
 
 	jobject instance = nullptr;
 	if (m_Ref != nullptr)
@@ -222,7 +223,7 @@ jvalue JPProxy::getProxy()
 		JP_TRACE("Create handler");
 		Py_INCREF(m_Instance);
 		instance = frame.CallObjectMethodA(m_Proxy.get(),
-				m_Context->m_Proxy_NewInstanceID, nullptr);
+				JPContext_global->m_Proxy_NewInstanceID, nullptr);
 		m_Ref = frame.NewWeakGlobalRef(instance);
 	}
 	jvalue out;
@@ -254,45 +255,58 @@ JPPyObject JPProxyType::convertToPythonO
 	JP_TRACE_IN("JPProxyType::convertToPythonObject");
 	jobject ih = frame.CallStaticObjectMethodA(m_ProxyClass.get(),
 			m_GetInvocationHandlerID, &val);
-	PyJPProxy *target = ((JPProxy*) frame.GetLongField(ih, m_InstanceID))->m_Instance;
-	if (target->m_Target != Py_None && target->m_Convert)
-		return JPPyObject::use(target->m_Target);
-	JP_TRACE("Target", target);
-	return JPPyObject::use((PyObject*) target);
+
+	JPProxy *proxy = (JPProxy*) frame.GetLongField(ih, m_InstanceID);
+	PyJPProxy *pproxy = proxy->m_Instance;
+
+	// Is it a native Python object
+	if (pproxy->m_Convert && pproxy->m_Target != Py_None)
+		return JPPyObject::use(pproxy->m_Target);
+
+	// Is it a user extended class
+	if (pproxy->m_Dispatch == Py_None)
+		return JPPyObject::use((PyObject*) pproxy);
+
+	// Return the Proxy itself
+	JP_TRACE("Target", pproxy);
+	return JPPyObject::use((PyObject*) pproxy);
 	JP_TRACE_OUT;  // GCOVR_EXCL_LINE
 }
 
-JPProxyDirect::JPProxyDirect(JPContext* context, PyJPProxy* inst, JPClassList& intf)
-: JPProxy(context, inst, intf)
+JPProxyDirect::JPProxyDirect(PyJPProxy* inst, JPClassList& intf)
+: JPProxy(inst, intf)
 {
 }
 
 JPProxyDirect::~JPProxyDirect()
 = default;
 
-JPPyObject JPProxyDirect::getCallable(const string& cname)
+JPPyObject JPProxyDirect::getCallable(const string& cname, int& addSelf)
 {
 	return JPPyObject::accept(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str()));
 }
 
-JPProxyIndirect::JPProxyIndirect(JPContext* context, PyJPProxy* inst, JPClassList& intf)
-: JPProxy(context, inst, intf)
+JPProxyIndirect::JPProxyIndirect(PyJPProxy* inst, JPClassList& intf)
+: JPProxy(inst, intf)
 {
 }
 
 JPProxyIndirect::~JPProxyIndirect()
 = default;
 
-JPPyObject JPProxyIndirect::getCallable(const string& cname)
+JPPyObject JPProxyIndirect::getCallable(const string& cname, int& addSelf)
 {
-	JPPyObject out = JPPyObject::accept(PyObject_GetAttrString(m_Instance->m_Target, cname.c_str()));
+	JPPyObject out = JPPyObject::accept(PyObject_GetAttrString(m_Instance->m_Dispatch, cname.c_str()));
 	if (!out.isNull())
+	{
+		addSelf = (m_Instance->m_Dispatch != m_Instance->m_Target) && (m_Instance->m_Target != Py_None);
 		return out;
+	}
 	return JPPyObject::accept(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str()));
 }
 
-JPProxyFunctional::JPProxyFunctional(JPContext* context, PyJPProxy* inst, JPClassList& intf)
-: JPProxy(context, inst, intf)
+JPProxyFunctional::JPProxyFunctional(PyJPProxy* inst, JPClassList& intf)
+: JPProxy(inst, intf)
 {
 	m_Functional = dynamic_cast<JPFunctional*>( intf[0]);
 }
@@ -300,9 +314,9 @@ JPProxyFunctional::JPProxyFunctional(JPC
 JPProxyFunctional::~JPProxyFunctional()
 = default;
 
-JPPyObject JPProxyFunctional::getCallable(const string& cname)
+JPPyObject JPProxyFunctional::getCallable(const string& cname, int& addSelf)
 {
 	if (cname == m_Functional->getMethod())
-		return JPPyObject::accept(PyObject_GetAttrString(m_Instance->m_Target, "__call__"));
+		return JPPyObject::accept(PyObject_GetAttrString(m_Instance->m_Dispatch, "__call__"));
 	return JPPyObject::accept(PyObject_GetAttrString((PyObject*) m_Instance, cname.c_str()));
 }
diff -pruN 1.5.0-1/native/common/jp_reference_queue.cpp 1.6.0-1/native/common/jp_reference_queue.cpp
--- 1.5.0-1/native/common/jp_reference_queue.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_reference_queue.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -47,11 +47,10 @@ JNIEXPORT void JNICALL Java_org_jpype_re
 JNIEXPORT void JNICALL Java_org_jpype_ref_JPypeReferenceNative_removeHostReference
 (JNIEnv *env, jclass, jlong host, jlong cleanup)
 {
-	JPContext* context = JPContext_global;
 	// Exceptions are not allowed here
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::external((JPContext*) context, env);
+		JPJavaFrame frame = JPJavaFrame::external(env);
 		JPPyCallAcquire callback;
 		if (cleanup != 0)
 		{
diff -pruN 1.5.0-1/native/common/jp_shorttype.cpp 1.6.0-1/native/common/jp_shorttype.cpp
--- 1.5.0-1/native/common/jp_shorttype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_shorttype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -27,6 +27,11 @@ JPShortType::JPShortType()
 JPShortType::~JPShortType()
 = default;
 
+JPClass* JPShortType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Short;
+}
+
 JPPyObject JPShortType::convertToPythonObject(JPJavaFrame& frame, jvalue val, bool cast)
 {
 	JPPyObject tmp = JPPyObject::call(PyLong_FromLong(field(val)));
@@ -35,10 +40,8 @@ JPPyObject JPShortType::convertToPythonO
 	return out;
 }
 
-JPValue JPShortType::getValueFromObject(const JPValue& obj)
+JPValue JPShortType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
-	JPContext *context = obj.getClass()->getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	jvalue v;
 	jobject jo = obj.getValue().l;
 	auto* jb = dynamic_cast<JPBoxedType*>( frame.findClassForObject(jo));
@@ -89,7 +92,7 @@ public:
 
 	void getInfo(JPClass *cls, JPConversionInfo &info) override
 	{
-		JPContext *context = cls->getContext();
+		JPContext *context = JPContext_global;
 		PyList_Append(info.exact, (PyObject*) context->_short->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_byte->getHost());
 		PyList_Append(info.implicit, (PyObject*) context->_char->getHost());
@@ -117,11 +120,11 @@ JPMatch::Type JPShortType::findJavaConve
 
 void JPShortType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jshortConversion.getInfo(this, info);
 	shortConversion.getInfo(this, info);
 	shortNumberConversion.getInfo(this, info);
-	PyList_Append(info.ret, (PyObject*) m_Context->_short->getHost());
+	PyList_Append(info.ret, (PyObject*) JPContext_global->_short->getHost());
 }
 
 jarray JPShortType::newArrayOf(JPJavaFrame& frame, jsize sz)
@@ -267,7 +270,7 @@ void JPShortType::setArrayItem(JPJavaFra
 
 void JPShortType::getView(JPArrayView& view)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	view.m_Memory = (void*) frame.GetShortArrayElements(
 			(jshortArray) view.m_Array->getJava(), &view.m_IsCopy);
 	view.m_Buffer.format = "h";
@@ -278,7 +281,7 @@ void JPShortType::releaseView(JPArrayVie
 {
 	try
 	{
-		JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
+		JPJavaFrame frame = JPJavaFrame::outer();
 		frame.ReleaseShortArrayElements((jshortArray) view.m_Array->getJava(),
 				(jshort*) view.m_Memory, view.m_Buffer.readonly ? JNI_ABORT : 0);
 	}	catch (JPypeException&)
diff -pruN 1.5.0-1/native/common/jp_stringtype.cpp 1.6.0-1/native/common/jp_stringtype.cpp
--- 1.5.0-1/native/common/jp_stringtype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_stringtype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -69,11 +69,12 @@ JPMatch::Type JPStringType::findJavaConv
 
 void JPStringType::getConversionInfo(JPConversionInfo &info)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
+	JPContext* context = frame.getContext();
 	objectConversion->getInfo(this, info);
 	stringConversion->getInfo(this, info);
 	hintsConversion->getInfo(this, info);
-	if (m_Context->getConvertStrings())
+	if (context->getConvertStrings())
 		PyList_Append(info.ret, (PyObject*) & PyUnicode_Type); // GCOVR_EXCL_LINE
 	else
 		PyList_Append(info.ret, (PyObject*) getHost());
diff -pruN 1.5.0-1/native/common/jp_tracer.cpp 1.6.0-1/native/common/jp_tracer.cpp
--- 1.5.0-1/native/common/jp_tracer.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_tracer.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -150,7 +150,13 @@ void JPypeTracer::tracePythonObject(cons
 	if (ref != nullptr)
 	{
 		std::stringstream str;
-		str << msg << " " << (void*) ref << " " << ref->ob_refcnt << " " << Py_TYPE(ref)->tp_name;
+		str << msg << " " << (void*) ref << " "<<
+    #ifndef Py_GIL_DISABLED
+        Py_REFCNT(ref)
+    #else
+        -1
+    #endif
+        << " " << Py_TYPE(ref)->tp_name;
 		JPypeTracer::trace1("PY", str.str().c_str());
 
 	} else
@@ -204,4 +210,4 @@ void JPypeTracer::traceLocks(const strin
 	JPYPE_TRACING_OUTPUT.flush();
 }
 
-// GCOVR_EXCL_STOP
\ No newline at end of file
+// GCOVR_EXCL_STOP
diff -pruN 1.5.0-1/native/common/jp_typefactory.cpp 1.6.0-1/native/common/jp_typefactory.cpp
--- 1.5.0-1/native/common/jp_typefactory.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_typefactory.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -47,7 +47,7 @@ void JPTypeFactory_rethrow(JPJavaFrame&
 		throw;
 	} catch (JPypeException& ex)
 	{
-		ex.toJava(frame.getContext());
+		ex.toJava();
 	} catch (...)  // GCOVR_EXCL_LINE
 	{
 		// GCOVR_EXCL_START
@@ -90,8 +90,7 @@ extern "C"
 JNIEXPORT void JNICALL Java_org_jpype_manager_TypeFactoryNative_newWrapper(
 		JNIEnv *env, jobject self, jlong contextPtr, jlong jcls)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	JP_JAVA_TRY("JPTypeFactory_newWrapper");
 	JPPyCallAcquire callback;
 	auto* cls = (JPClass*) jcls;
@@ -104,8 +103,8 @@ JNIEXPORT void JNICALL Java_org_jpype_ma
 		jlongArray resources,
 		jint sz)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
+	auto* context = frame.getContext();
 	JP_JAVA_TRY("JPTypeFactory_destroy");
 	JPPrimitiveArrayAccessor<jlongArray, jlong*> accessor(frame, resources,
 			&JPJavaFrame::GetLongArrayElements, &JPJavaFrame::ReleaseLongArrayElements);
@@ -125,8 +124,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_m
 		jlongArray overloadPtrs,
 		jint modifiers)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	JP_JAVA_TRY("JPTypeFactory_defineMethodDispatch");
 	auto* cls = (JPClass*) clsPtr;
 	JPMethodList overloadList;
@@ -146,8 +144,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_m
 		jlong componentClass,
 		jint modifiers)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	JP_JAVA_TRY("JPTypeFactory_defineArrayClass");
 	string cname = frame.toStringUTF8(name);
 	JP_TRACE(cname);
@@ -169,8 +166,8 @@ JNIEXPORT jlong JNICALL Java_org_jpype_m
 		jint modifiers)
 {
 	// All resources are created here are owned by Java and deleted by Java shutdown routine
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
+	auto* context = frame.getContext();
 	JP_JAVA_TRY("JPTypeFactory_defineObjectClass");
 	string className = frame.toStringUTF8(name);
 	JP_TRACE(className);
@@ -304,8 +301,8 @@ JNIEXPORT jlong JNICALL Java_org_jpype_m
 		jint modifiers)
 {
 	// These resources are created by the boxed types
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
+	auto* context = frame.getContext();
 	JP_JAVA_TRY("JPTypeFactory_definePrimitive");
 	string cname = frame.toStringUTF8(name);
 	JP_TRACE(cname);
@@ -366,8 +363,7 @@ JNIEXPORT void JNICALL Java_org_jpype_ma
 		jlongArray methodPtrs,
 		jlongArray fieldPtrs)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	JP_JAVA_TRY("JPTypeFactory_assignMembers");
 	auto* cls = (JPClass*) clsPtr;
 	JPMethodDispatchList methodList;
@@ -391,8 +387,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_m
 		jlong fieldType,
 		jint modifiers)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	JP_JAVA_TRY("JPTypeFactory_defineField");
 	string cname = frame.toStringUTF8(name);
 	JP_TRACE("class", cls);
@@ -414,8 +409,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_m
 		jobject method,
 		jlongArray overloadList, jint modifiers)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	JP_JAVA_TRY("JPTypeFactory_defineMethod");
 	jmethodID mid = frame.FromReflectedMethod(method);
 	JPMethodList cover;
@@ -439,8 +433,7 @@ JNIEXPORT void JNICALL Java_org_jpype_ma
 		jlongArray argumentTypes
 		)
 {
-	auto* context = (JPContext*) contextPtr;
-	JPJavaFrame frame = JPJavaFrame::external(context, env);
+	JPJavaFrame frame = JPJavaFrame::external(env);
 	JP_JAVA_TRY("JPTypeFactory_populateMethod");
 	JPClassList cargs;
 	convert(frame, argumentTypes, cargs);
diff -pruN 1.5.0-1/native/common/jp_typemanager.cpp 1.6.0-1/native/common/jp_typemanager.cpp
--- 1.5.0-1/native/common/jp_typemanager.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_typemanager.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -19,9 +19,7 @@
 JPTypeManager::JPTypeManager(JPJavaFrame& frame)
 {
 	JP_TRACE_IN("JPTypeManager::init");
-	m_Context = frame.getContext();
-
-	jclass cls = m_Context->getClassLoader()->findClass(frame, "org.jpype.manager.TypeManager");
+	jclass cls = frame.getContext()->getClassLoader()->findClass(frame, "org.jpype.manager.TypeManager");
 	m_FindClass = frame.GetMethodID(cls, "findClass", "(Ljava/lang/Class;)J");
 	m_FindClassByName = frame.GetMethodID(cls, "findClassByName", "(Ljava/lang/String;)J");
 	m_FindClassForObject = frame.GetMethodID(cls, "findClassForObject", "(Ljava/lang/Object;)J");
@@ -36,7 +34,7 @@ JPTypeManager::JPTypeManager(JPJavaFrame
 JPClass* JPTypeManager::findClass(jclass obj)
 {
 	JP_TRACE_IN("JPTypeManager::findClass");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jvalue val;
 	val.l = obj;
 	return (JPClass*) (frame.CallLongMethodA(m_JavaTypeManager.get(), m_FindClass, &val));
@@ -46,7 +44,7 @@ JPClass* JPTypeManager::findClass(jclass
 JPClass* JPTypeManager::findClassByName(const string& name)
 {
 	JP_TRACE_IN("JPTypeManager::findClassByName");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jvalue val;
 	val.l = (jobject) frame.fromStringUTF8(name);
 	auto* out = (JPClass*) (frame.CallLongMethodA(m_JavaTypeManager.get(), m_FindClassByName, &val));
@@ -63,7 +61,7 @@ JPClass* JPTypeManager::findClassByName(
 JPClass* JPTypeManager::findClassForObject(jobject obj)
 {
 	JP_TRACE_IN("JPTypeManager::findClassForObject");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jvalue val;
 	val.l = obj;
 	auto *cls = (JPClass*) (frame.CallLongMethodA(m_JavaTypeManager.get(), m_FindClassForObject, &val));
@@ -76,7 +74,7 @@ JPClass* JPTypeManager::findClassForObje
 void JPTypeManager::populateMethod(void* method, jobject obj)
 {
 	JP_TRACE_IN("JPTypeManager::populateMethod");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jvalue val[2];
 	val[0].j = (jlong) method;
 	val[1].l = obj;
@@ -88,7 +86,7 @@ void JPTypeManager::populateMethod(void*
 void JPTypeManager::populateMembers(JPClass* cls)
 {
 	JP_TRACE_IN("JPTypeManager::populateMembers");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jvalue val[1];
 	val[0].l = (jobject) cls->getJavaClass();
 	frame.CallVoidMethodA(m_JavaTypeManager.get(), m_PopulateMembers, val);
@@ -98,7 +96,7 @@ void JPTypeManager::populateMembers(JPCl
 int JPTypeManager::interfaceParameterCount(JPClass *cls)
 {
 	JP_TRACE_IN("JPTypeManager::interfaceParameterCount");
-	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jvalue val[1];
 	val[0].l = (jobject) cls->getJavaClass();
 	return frame.CallIntMethodA(m_JavaTypeManager.get(), m_InterfaceParameterCount, val);
diff -pruN 1.5.0-1/native/common/jp_voidtype.cpp 1.6.0-1/native/common/jp_voidtype.cpp
--- 1.5.0-1/native/common/jp_voidtype.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/common/jp_voidtype.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -24,6 +24,11 @@ JPVoidType::JPVoidType()
 JPVoidType::~JPVoidType()
 = default;
 
+JPClass* JPVoidType::getBoxedClass(JPJavaFrame& frame) const
+{
+	return frame.getContext()->_java_lang_Void;
+}
+
 JPPyObject JPVoidType::invokeStatic(JPJavaFrame& frame, jclass claz, jmethodID mth, jvalue* val)
 {
 	{
@@ -47,7 +52,7 @@ JPPyObject JPVoidType::invoke(JPJavaFram
 
 // GCOVR_EXCL_START
 
-JPValue JPVoidType::getValueFromObject(const JPValue& obj)
+JPValue JPVoidType::getValueFromObject(JPJavaFrame& frame, const JPValue& obj)
 {
 	// This is needed if we call a caller sensitive method
 	// and we get a return which is expected to be a void object
diff -pruN 1.5.0-1/native/java/org/jpype/JPypeContext.java 1.6.0-1/native/java/org/jpype/JPypeContext.java
--- 1.5.0-1/native/java/org/jpype/JPypeContext.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/JPypeContext.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,640 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype;
-
-import java.io.File;
-import java.lang.reflect.Array;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.nio.Buffer;
-import java.nio.ByteOrder;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.jpype.classloader.DynamicClassLoader;
-import org.jpype.manager.TypeFactory;
-import org.jpype.manager.TypeFactoryNative;
-import org.jpype.manager.TypeManager;
-import org.jpype.pkg.JPypePackage;
-import org.jpype.pkg.JPypePackageManager;
-import org.jpype.ref.JPypeReferenceQueue;
-
-/**
- * Context for JPype.
- * <p>
- * This is the part of JPype that holds all resources. After the classloader is
- * created this class is given the address of the context object in JPype. Any
- * resources in JPype Java layer can be contacted using the context.
- * <p>
- * Boot order is - create the C++ portion of the context. - start the jvm - load
- * the bootloader - install the jar into the bootloader - install all native
- * methods using the bootloader - create the Java portion of the context. - use
- * the Java context to access the resources (ReferenceQueue, TypeFactory,
- * TypeManager)
- * <p>
- * Once started, python calls use the context to get a frame and attach their
- * threads. Methods called from Java will get the env and use it to get their
- * context from which they can create a frame.
- * <p>
- * The C++ context will hold all the previous global variables thus allowing the
- * C++ portion to be cleaned up properly when the JVM is shutdown or
- * disconnected.
- * <p>
- * As the JPypeContext can't be tested directly from Java code, it will need to
- * be kept light.
- * <p>
- * Our goal is to remove as much direct contact methods as possible from the C++
- * layer. Previous globals in JPTypeManager move to the context as do the
- * contents of JPJni.
- *
- *
- *
- * @author nelson85
- */
-public class JPypeContext
-{
-
-  public final String VERSION = "1.5.0";
-
-  private static JPypeContext INSTANCE = new JPypeContext();
-  // This is the C++ portion of the context.
-  private long context;
-  private TypeFactory typeFactory;
-  private TypeManager typeManager;
-  private DynamicClassLoader classLoader;
-  private final AtomicInteger shutdownFlag = new AtomicInteger();
-  private final List<Thread> shutdownHooks = new ArrayList<>();
-  private final List<Runnable> postHooks = new ArrayList<>();
-  public static boolean freeResources = true;
-
-  static public JPypeContext getInstance()
-  {
-    return INSTANCE;
-  }
-
-  /**
-   * Start the JPype system.
-   *
-   * @param context is the C++ portion of the context.
-   * @param bootLoader is the classloader holding JPype resources.
-   * @return the created context.
-   */
-  static JPypeContext createContext(long context, ClassLoader bootLoader, String nativeLib, boolean interrupt)
-  {
-    if (nativeLib != null)
-    {
-      System.load(nativeLib);
-    }
-    INSTANCE.context = context;
-    INSTANCE.classLoader = (DynamicClassLoader) bootLoader;
-    INSTANCE.typeFactory = new TypeFactoryNative();
-    INSTANCE.typeManager = new TypeManager(context, INSTANCE.typeFactory);
-    INSTANCE.initialize(interrupt);
-
-    scanExistingJars();
-    return INSTANCE;
-  }
-
-  private JPypeContext()
-  {
-  }
-
-  void initialize(boolean interrupt)
-  {
-    // Okay everything is setup so lets give it a go.
-    this.typeManager.init();
-    JPypeReferenceQueue.getInstance().start();
-    if (!interrupt)
-      JPypeSignal.installHandlers();
-
-    // Install a shutdown hook to clean up Python resources.
-    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        INSTANCE.shutdown();
-      }
-    }));
-
-  }
-
-  /**
-   * Shutdown and remove all Python resources.
-   *
-   * This hook is only called after the last user thread has died. Thus the only
-   * remaining connections are proxies that were attached to the JVM shutdown
-   * hook, the reference queue, and the typemanager.
-   *
-   * This routine will try to take out the last connections in an orderly
-   * fashion. Inherently this is a very dangerous time as portions of Java have
-   * already been deactivated.
-   */
-  @SuppressWarnings(
-          {
-            "CallToThreadYield", "SleepWhileInLoop"
-          })
-  private void shutdown()
-  {
-    try
-    {
-      // Try to yield in case there is a race condition.  The user
-      // may have installed a shutdown hook, but we cannot verify
-      // the order that shutdown hook threads are executed.  Thus we will
-      // try to intentionally lose the race.
-      //
-      // This will only occur if something registered a shutdown hook through
-      // a Java API.  Those registered though the JPype API will be joined
-      // manually.
-      for (int i = 0; i < 5; i++)
-      {
-        try
-        {
-          Thread.sleep(1);
-          Thread.yield();
-        } catch (InterruptedException ex)
-        {
-        }
-      }
-
-      // Execute any used defined shutdown hooks registered with JPype.
-      if (!this.shutdownHooks.isEmpty())
-      {
-        for (Thread thread : this.shutdownHooks)
-        {
-          thread.start();
-        }
-        for (Thread thread : this.shutdownHooks)
-        {
-          try
-          {
-            thread.join();
-          } catch (InterruptedException ex)
-          {
-          }
-        }
-      }
-
-      // Disable all future calls to proxies
-      this.shutdownFlag.incrementAndGet();
-
-      // Past this point any further execution of a Python proxy would
-      // be fatal.
-      Thread t1 = Thread.currentThread();
-      Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();
-
-      for (Thread t : threads.keySet())
-      {
-        if (t1 == t || t.isDaemon())
-          continue;
-        t.interrupt();
-      }
-
-      // Inform Python no more calls are permitted
-      onShutdown(this.context);
-      Thread.yield();
-
-      // Wait for any unregistered proxies to finish so that we don't yank
-      // the rug out from under them result in a segfault.
-//      while (this.proxyCount.get() > 0)
-//      {
-//        try
-//        {
-//          Thread.sleep(10);
-//        } catch (InterruptedException ex)
-//        {
-//        }
-//      }
-//      // Check to see if who is alive
-//      threads = Thread.getAllStackTraces();
-//      System.out.println("Check for remaining");
-//      for (Thread t : threads.keySet())
-//      {
-//        // Daemon threads don't count for shutdown so skip them.
-//        if (t.isDaemon())
-//          continue;
-//        System.out.println("  " + t.getName() + " " + t.getState() + " " + t.isDaemon());
-//        for (StackTraceElement e : t.getStackTrace())
-//        {
-//          System.out.println("    " + e.getClassName());
-//        }
-//      }
-    } catch (Throwable th)
-    {
-    }
-
-    if (freeResources)
-    {
-       // Release all Python references
-       try
-       {
-         JPypeReferenceQueue.getInstance().stop();
-       } catch (Throwable th)
-       {
-       }
-
-       // Release any C++ resources
-       try
-       {
-         this.typeManager.shutdown();
-       } catch (Throwable th)
-       {
-       }
-    }
-
-    // Execute post hooks
-    for (Runnable run : this.postHooks)
-    {
-      run.run();
-    }
-
-  }
-
-  static native void onShutdown(long ctxt);
-
-  public void addShutdownHook(Thread th)
-  {
-    this.shutdownHooks.add(th);
-  }
-
-  public boolean removeShutdownHook(Thread th)
-  {
-    if (this.shutdownHooks.contains(th))
-    {
-      this.shutdownHooks.remove(th);
-      return true;
-    } else
-      return Runtime.getRuntime().removeShutdownHook(th);
-  }
-
-  /**
-   * Get the C++ portion.
-   *
-   * @return
-   */
-  public long getContext()
-  {
-    return context;
-  }
-
-  public ClassLoader getClassLoader()
-  {
-    return this.classLoader;
-  }
-
-  public TypeFactory getTypeFactory()
-  {
-    return this.typeFactory;
-  }
-
-  public TypeManager getTypeManager()
-  {
-    return this.typeManager;
-  }
-
-  /**
-   * Add a hook to run after Python interface is shutdown.
-   *
-   * This must never have a Python method attached.
-   *
-   * @param run
-   */
-  public void _addPost(Runnable run)
-  {
-    this.postHooks.add(run);
-  }
-
-  /**
-   * Call a method using reflection.This method creates a stackframe so that
-   * caller sensitive methods will execute properly.
-   *
-   *
-   * @param method is the method to call.
-   * @param obj is the object to operate on, it will be null if the method is
-   * static.
-   * @param args the arguments to method.
-   * @return the object that results form the invocation.
-   * @throws java.lang.Throwable throws whatever type the called method
-   * produces.
-   */
-  public Object callMethod(Method method, Object obj, Object[] args)
-          throws Throwable
-  {
-    try
-    {
-      return method.invoke(obj, args);
-    } catch (InvocationTargetException ex)
-    {
-      throw ex.getCause();
-    }
-  }
-
-  /**
-   * Helper function for collect rectangular,
-   */
-  private static boolean collect(List l, Object o, int q, int[] shape, int d)
-  {
-    if (Array.getLength(o) != shape[q])
-      return false;
-    if (q + 1 == d)
-    {
-      l.add(o);
-      return true;
-    }
-    for (int i = 0; i < shape[q]; ++i)
-    {
-      if (!collect(l, Array.get(o, i), q + 1, shape, d))
-        return false;
-    }
-    return true;
-  }
-
-  /**
-   * Collect up a rectangular primitive array for a Python memory view.
-   *
-   * If it is a rectangular primitive array then the result will be an object
-   * array containing. - the primitive type - an int array with the shape of the
-   * array - each of the primitive arrays that will need be visited in order.
-   *
-   * This is the safest way to provide a view as we are verifying and collected
-   * thus even if something mutates the shape of the array after we have
-   * visited, we have a locked copy.
-   *
-   * @param o is the object to be tested.
-   * @return null if the object is not a rectangular primitive array.
-   */
-  public Object[] collectRectangular(Object o)
-  {
-    if (o == null || !o.getClass().isArray())
-      return null;
-    int[] shape = new int[5];
-    int d = 0;
-    ArrayList<Object> out = new ArrayList<>();
-    Object o1 = o;
-    Class c1 = o1.getClass();
-    for (int i = 0; i < 5; ++i)
-    {
-      int l = Array.getLength(o1);
-      if (l == 0)
-        return null;
-      shape[d++] = l;
-      o1 = Array.get(o1, 0);
-      if (o1 == null)
-        return null;
-      c1 = c1.getComponentType();
-      if (!c1.isArray())
-        break;
-    }
-    if (!c1.isPrimitive())
-      return null;
-    out.add(c1);
-    shape = Arrays.copyOfRange(shape, 0, d);
-    out.add(shape);
-    int total = 1;
-    for (int i = 0; i < d - 1; i++)
-      total *= shape[i];
-    out.ensureCapacity(total + 2);
-    if (d == 5)
-      return null;
-    if (!collect(out, o, 0, shape, d))
-      return null;
-    return out.toArray();
-  }
-
-  private Object unpack(int size, Object parts)
-  {
-    Object e0 = Array.get(parts, 0);
-    Class c = e0.getClass();
-    int segments = Array.getLength(parts) / size;
-    Object a2 = Array.newInstance(c, size);
-    Object a1 = Array.newInstance(a2.getClass(), segments);
-    int k = 0;
-    for (int i = 0; i < segments; i++)
-    {
-      for (int j = 0; j < size; j++, k++)
-      {
-        Object o = Array.get(parts, k);
-        Array.set(a2, j, o);
-      }
-      Array.set(a1, i, a2);
-      if (i < segments - 1)
-        a2 = Array.newInstance(c, size);
-    }
-    return a1;
-  }
-
-  public Object assemble(int[] dims, Object parts)
-  {
-    int n = dims.length;
-    if (n == 1)
-      return Array.get(parts, 0);
-    if (n == 2)
-      return Array.get(unpack(dims[0], parts), 0);
-    for (int i = 0; i < n - 2; ++i)
-    {
-      parts = unpack(dims[n - i - 2], parts);
-    }
-    return parts;
-  }
-
-  public boolean isShutdown()
-  {
-    return shutdownFlag.get() > 0;
-  }
-
-//  public void incrementProxy()
-//  {
-//    proxyCount.incrementAndGet();
-//  }
-//
-//  public void decrementProxy()
-//  {
-//    proxyCount.decrementAndGet();
-//  }
-  /**
-   * Clear the current interrupt.
-   *
-   * @param x is true if an exception should be thrown.
-   * @throws InterruptedException
-   */
-  public static void clearInterrupt(boolean x) throws InterruptedException
-  {
-    try
-    {
-      Thread th = Thread.currentThread();
-
-      // Only relevant if this is the main thread for signal handling
-      if (th != JPypeSignal.main)
-        return;
-
-      // Unconditionally clear the interrupt flag if we are called from 
-      // C++.  This happens when a field get() or method call() is 
-      // invoked.
-      if (!x)
-        JPypeSignal.acknowledgePy();
-
-      // Check if this thread is interrupted
-      if (th.isInterrupted())
-      {
-        // Clear the flag in C++
-        JPypeSignal.acknowledgePy();
-
-        // Clear the flag in Java
-        Thread.sleep(1);
-      }
-    } catch (InterruptedException ex)
-    {
-      if (x)
-        throw ex;
-    }
-  }
-
-  public long getExcClass(Throwable th)
-  {
-    if (th instanceof PyExceptionProxy)
-      return ((PyExceptionProxy) th).cls;
-    return 0;
-  }
-
-  public long getExcValue(Throwable th)
-  {
-    if (th instanceof PyExceptionProxy)
-      return ((PyExceptionProxy) th).value;
-    return 0;
-  }
-
-  public Exception createException(long l0, long l1)
-  {
-    return new PyExceptionProxy(l0, l1);
-  }
-
-  public boolean order(Buffer b)
-  {
-    if (b instanceof java.nio.ByteBuffer)
-      return ((java.nio.ByteBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
-    if (b instanceof java.nio.ShortBuffer)
-      return ((java.nio.ShortBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
-    if (b instanceof java.nio.CharBuffer)
-      return ((java.nio.CharBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
-    if (b instanceof java.nio.IntBuffer)
-      return ((java.nio.IntBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
-    if (b instanceof java.nio.LongBuffer)
-      return ((java.nio.LongBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
-    if (b instanceof java.nio.FloatBuffer)
-      return ((java.nio.FloatBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
-    if (b instanceof java.nio.DoubleBuffer)
-      return ((java.nio.DoubleBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
-    return true;
-  }
-
-  public boolean isPackage(String s)
-  {
-    s = JPypeKeywords.safepkg(s);
-    return JPypePackageManager.isPackage(s);
-  }
-
-  public JPypePackage getPackage(String s)
-  {
-    s = JPypeKeywords.safepkg(s);
-    if (!JPypePackageManager.isPackage(s))
-      return null;
-    return new JPypePackage(s);
-  }
-
-  /**
-   * Utility to probe functional interfaces.
-   *
-   * @param cls
-   * @return
-   */
-  public static String getFunctional(Class cls)
-  {
-    Method m = JPypeUtilities.getFunctionalInterfaceMethod(cls);
-    return m != null ? m.getName() : null;
-  }
-
-  /**
-   * Utility function for extracting the unique portion of a stack trace.
-   *
-   * This is a bit different that the Java method which works from the back. We
-   * will be using fake stacktraces from Python at some point so finding the
-   * first common is a better approach.
-   *
-   * @param th is the throwable.
-   * @param enclosing is the throwsble that holds this or null if top level.
-   * @return the unique frames as an object array with 4 objects per frame.
-   */
-  public Object[] getStackTrace(Throwable th, Throwable enclosing)
-  {
-    StackTraceElement[] trace = th.getStackTrace();
-    if (trace == null || enclosing == null)
-      return toFrames(trace);
-    StackTraceElement[] te = enclosing.getStackTrace();
-    if (te == null)
-      return toFrames(trace);
-    for (int i = 0; i < trace.length; ++i)
-    {
-      if (trace[i].equals(te[0]))
-      {
-        return toFrames(Arrays.copyOfRange(trace, 0, i));
-      }
-    }
-    return toFrames(trace);
-  }
-
-  private Object[] toFrames(StackTraceElement[] stackTrace)
-  {
-    if (stackTrace == null)
-      return null;
-    Object[] out = new Object[4 * stackTrace.length];
-    int i = 0;
-    for (StackTraceElement fr : stackTrace)
-    {
-      out[i++] = fr.getClassName();
-      out[i++] = fr.getMethodName();
-      out[i++] = fr.getFileName();
-      out[i++] = fr.getLineNumber();
-    }
-    return out;
-
-  }
-
-  public void newWrapper(long l)
-  {
-    // We can only go through this point single file.
-    synchronized (this.typeFactory)
-    {
-      this.typeFactory.newWrapper(context, l);
-    }
-  }
-
-  private static void scanExistingJars()
-  {
-    // Scan existing jars for missing directory entries
-    String[] paths = System.getProperty("java.class.path").split(File.pathSeparator);
-    for (String path : paths)
-    {
-      INSTANCE.classLoader.scanJar(Paths.get(path));
-    }
-  }
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/JPypeKeywords.java 1.6.0-1/native/java/org/jpype/JPypeKeywords.java
--- 1.5.0-1/native/java/org/jpype/JPypeKeywords.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/JPypeKeywords.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,62 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- *
- * @author nelson85
- */
-public class JPypeKeywords
-{
-
-  final public static Set<String> keywords = new HashSet<>();
-
-  public static void setKeywords(String[] s)
-  {
-    keywords.addAll(Arrays.asList(s));
-  }
-
-  public static String wrap(String name)
-  {
-    if (keywords.contains(name))
-      return name + "_";
-    return name;
-  }
-
-  public static String unwrap(String name)
-  {
-    if (!name.endsWith("_"))
-      return name;
-    String name2 = name.substring(0, name.length() - 1);
-    if (keywords.contains(name2))
-      return name2;;
-    return name;
-  }
-
-  static String safepkg(String s)
-  {
-    if (!s.contains("_"))
-      return s;
-    String[] parts = s.split("\\.");
-    for (int i = 0; i < parts.length; ++i)
-      parts[i] = unwrap(parts[i]);
-    return String.join(".", parts);
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/JPypeSignal.java 1.6.0-1/native/java/org/jpype/JPypeSignal.java
--- 1.5.0-1/native/java/org/jpype/JPypeSignal.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/JPypeSignal.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,66 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-
-/**
- * Java wants to make this action nearly impossible.
- *
- * Thus the have warnings against it that cannot be disabled. So we will skin
- * this cat another way.
- */
-public class JPypeSignal
-{
-
-  static Thread main;
-
-  static void installHandlers()
-  {
-    try
-    {
-      Class Signal = Class.forName("sun.misc.Signal");
-      Class SignalHandler = Class.forName("sun.misc.SignalHandler");
-      main = Thread.currentThread();
-      Method method = Signal.getMethod("handle", Signal, SignalHandler);
-
-      Object handler = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]
-      {
-        SignalHandler
-      }, new InvocationHandler()
-      {
-        @Override
-        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
-        {
-          main.interrupt();
-          interruptPy();
-          return null;
-        }
-      });
-      Object intr = Signal.getDeclaredConstructor(String.class).newInstance("INT");
-      method.invoke(null, intr, handler);
-    } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException | InstantiationException | ClassNotFoundException | NoSuchMethodException | SecurityException ex)
-    {
-      // If we don't get the signal handler run without it.  (ANDROID)
-    }
-  }
-
-  native static void interruptPy();
-  native static void acknowledgePy();
-}
diff -pruN 1.5.0-1/native/java/org/jpype/JPypeUtilities.java 1.6.0-1/native/java/org/jpype/JPypeUtilities.java
--- 1.5.0-1/native/java/org/jpype/JPypeUtilities.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/JPypeUtilities.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,110 +0,0 @@
-package org.jpype;
-
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandleProxies;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.net.URISyntaxException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.function.Predicate;
-
-public class JPypeUtilities
-{
-  
-  // a functional interface can only re-declare a public non-final method from Object
-  // this should end up being an array of equals, hashCode and toString
-  private static final Method[] OBJECT_METHODS =
-    Arrays.stream(Object.class.getMethods())
-      .filter(m -> !Modifier.isFinal(m.getModifiers()))
-      .toArray(Method[]::new);
-  
-  private static final Predicate<Class> isSealed;
-
-  static
-  {
-    Predicate<Class> result = null;
-    try
-    {
-      Method m = Class.class.getMethod("isSealed");
-      MethodHandle handle = MethodHandles.publicLookup().unreflect(m);
-      result = MethodHandleProxies.asInterfaceInstance(Predicate.class, handle);
-    } catch (IllegalAccessException e)
-    {
-      // it's a public method so this should never occur
-      throw new IllegalAccessError(e.getMessage());
-    } catch (NoSuchMethodException e)
-    {
-      // if isSealed doesn't exist then neither do sealed classes
-      result = c -> false;
-    }
-    isSealed = result;
-  }
-
-  public static Path getJarPath(Class c)
-  {
-    try
-    {
-      return Paths.get(c.getProtectionDomain().getCodeSource().getLocation()
-              .toURI()).getParent();
-    } catch (URISyntaxException ex)
-    {
-      return null;
-    }
-  }
-
-  public static Method getFunctionalInterfaceMethod(Class cls)
-  {
-    if (!cls.isInterface() || cls.isAnnotation() || isSealed.test(cls))
-      return null;
-
-    Method result = null;
-    for (Method m : cls.getMethods())
-    {
-      if (Modifier.isAbstract(m.getModifiers()))
-      {
-        if (isObjectMethodOverride(m))
-          continue;
-        
-        if (result != null && !equals(m, result))
-          return null;
-
-        if (result == null || cls.equals(m.getDeclaringClass()))
-          result = m;
-      }
-    }
-    return result;
-  }
-
-  private static boolean isObjectMethodOverride(Method m)
-  {
-    for (Method objectMethod : OBJECT_METHODS)
-    {
-      if (equals(m, objectMethod))
-        return true;
-    }
-    return false;
-  }
-  
-  private static boolean equals(Method a, Method b)
-  {
-    // this should be the fastest possible short circuit
-    if (a.getParameterCount() != b.getParameterCount())
-      return false;
-
-    if (!a.getName().equals(b.getName()))
-      return false;
-
-    // if the return types are different it wouldn't compile
-    // parameters must be exactly the same and may not be an extended class
-    if (!Arrays.equals(a.getParameterTypes(), b.getParameterTypes()))
-      return false;
-
-    // if declared exceptions were different it wouldn't compile
-    // if it did compile then it is an override
-    return true;
-  }
-  
-}
diff -pruN 1.5.0-1/native/java/org/jpype/PyExceptionProxy.java 1.6.0-1/native/java/org/jpype/PyExceptionProxy.java
--- 1.5.0-1/native/java/org/jpype/PyExceptionProxy.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/PyExceptionProxy.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,19 +0,0 @@
-package org.jpype;
-
-/**
- *
- * @author nelson85
- */
-public class PyExceptionProxy extends RuntimeException
-{
-
-  long cls;
-  long value;
-
-  public PyExceptionProxy(long l0, long l1)
-  {
-    cls = l0;
-    value = l1;
-  }
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/classloader/DynamicClassLoader.java 1.6.0-1/native/java/org/jpype/classloader/DynamicClassLoader.java
--- 1.5.0-1/native/java/org/jpype/classloader/DynamicClassLoader.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/classloader/DynamicClassLoader.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,251 +0,0 @@
-package org.jpype.classloader;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.net.URLConnection;
-import java.nio.file.FileSystems;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-public class DynamicClassLoader extends ClassLoader
-{
-
-  List<URLClassLoader> loaders = new LinkedList<>();
-  HashMap<String, ArrayList<URL>> map = new HashMap<>();
-
-  public DynamicClassLoader(ClassLoader parent)
-  {
-    super(parent);
-  }
-
-  public int getCode()
-  {
-    return loaders.hashCode();
-  }
-
-  /**
-   * Add a set of jars to the classpath.
-   *
-   * @param root
-   * @param glob
-   * @throws IOException
-   */
-  public void addFiles(Path root, String glob) throws IOException
-  {
-    final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob);
-
-    List<URL> urls = new LinkedList<>();
-    Files.walkFileTree(root, new SimpleFileVisitor<Path>()
-    {
-
-      @Override
-      public FileVisitResult visitFile(Path path,
-              BasicFileAttributes attrs) throws IOException
-      {
-        if (pathMatcher.matches(root.relativize(path)))
-        {
-          URL url = path.toUri().toURL();
-          urls.add(url);
-        }
-        return FileVisitResult.CONTINUE;
-      }
-
-      @Override
-      public FileVisitResult visitFileFailed(Path file, IOException exc)
-              throws IOException
-      {
-        return FileVisitResult.CONTINUE;
-      }
-    });
-
-    loaders.add(new URLClassLoader(urls.toArray(new URL[urls.size()])));
-  }
-
-  public void addFile(Path path) throws FileNotFoundException
-  {
-    try
-    {
-      if (!Files.exists(path))
-        throw new FileNotFoundException(path.toString());
-      URL[] urls = new URL[]
-      {
-        path.toUri().toURL()
-      };
-      loaders.add(new URLClassLoader(urls));
-
-      // Scan the file for directory entries
-      this.scanJar(path);
-    } catch (MalformedURLException ex)
-    {
-      // This should never happen
-      throw new RuntimeException(ex);
-    }
-  }
-
-  /**
-   * Loads a class from the class loader.
-   *
-   * @param name is the name of the class with java class notation (using dots).
-   * @return the class
-   * @throws ClassNotFoundException was not found by the class loader.
-   * @throws ClassFormatError if the class byte code was invalid.
-   */
-  @Override
-  public Class findClass(String name) throws ClassNotFoundException, ClassFormatError
-  {
-    String aname = name.replace('.', '/') + ".class";
-    URL url = this.getResource(aname);
-    if (url == null)
-      throw new ClassNotFoundException(name);
-
-    try
-    {
-      URLConnection connection = url.openConnection();
-      try ( InputStream is = connection.getInputStream())
-      {
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        int bytes;
-        byte[] d = new byte[1024];
-        while ((bytes = is.read(d, 0, d.length)) != -1)
-        {
-          buffer.write(d, 0, bytes);
-        }
-
-        buffer.flush();
-        byte[] data = buffer.toByteArray();
-        return defineClass(name, data, 0, data.length);
-      }
-    } catch (IOException ex)
-    {
-    }
-    throw new ClassNotFoundException(name);
-  }
-
-  @Override
-  public URL getResource(String name)
-  {
-    URL url = this.getParent().getResource(name);
-    if (url != null)
-      return url;
-    for (ClassLoader cl : this.loaders)
-    {
-      url = cl.getResource(name);
-      if (url != null)
-        return url;
-    }
-    // Both with and without / should generate the same result
-    if (name.endsWith("/"))
-      name = name.substring(0, name.length() - 1);
-    if (map.containsKey(name))
-      return map.get(name).get(0);
-    return null;
-  }
-
-  @Override
-  public Enumeration<URL> getResources(String name) throws IOException
-  {
-    ArrayList<URL> out = new ArrayList<>();
-    Enumeration<URL> urls = getParent().getResources(name);
-    out.addAll(Collections.list(urls));
-    for (URLClassLoader cl : this.loaders)
-    {
-      urls = cl.findResources(name);
-      out.addAll(Collections.list(urls));
-    }
-    // Both with and without / should generate the same result
-    if (name.endsWith("/"))
-      name = name.substring(0, name.length() - 1);
-    if (map.containsKey(name))
-      out.addAll(map.get(name));
-    return Collections.enumeration(out);
-  }
-
-  public void addResource(String name, URL url)
-  {
-    if (!this.map.containsKey(name))
-      this.map.put(name, new ArrayList<>());
-    this.map.get(name).add(url);
-  }
-
-  /**
-   * Recreate missing directory entries for Jars that lack indexing.
-   *
-   * Some jar files are missing the directory entries that prevents use from
-   * properly importing their contents. This procedure scans a jar file when
-   * loaded to build missing directories.
-   *
-   * @param p1
-   */
-  public void scanJar(Path p1)
-  {
-    if (!Files.exists(p1))
-      return;
-    if (Files.isDirectory(p1))
-      return;
-    try ( JarFile jf = new JarFile(p1.toFile()))
-    {
-      Enumeration<JarEntry> entries = jf.entries();
-      URI abs = p1.toAbsolutePath().toUri();
-      Set urls = new java.util.HashSet();
-      while (entries.hasMoreElements())
-      {
-        JarEntry next = entries.nextElement();
-        String name = next.getName();
-
-        // Skip over META-INF
-        if (name.startsWith("META-INF/"))
-          continue;
-
-        if (next.isDirectory())
-        {
-          // If we find a directory entry then the jar has directories already
-          return;
-        }
-
-        // Split on each separator in the name
-        int i = 0;
-        while (true)
-        {
-          i = name.indexOf("/", i);
-          if (i == -1)
-            break;
-          String name2 = name.substring(0, i);
-
-          i++;
-
-          // Already have an entry no problem
-          if (urls.contains(name2))
-            continue;
-
-          // Add a new entry for the missing directory
-          String jar = "jar:" + abs + "!/" + name2 + "/";
-          urls.add(name2);
-          this.addResource(name2, new URL(jar));
-        }
-      }
-    } catch (IOException ex)
-    {
-      // Anything goes wrong skip it
-    }
-  }
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/classloader/JPypeClassLoader.java 1.6.0-1/native/java/org/jpype/classloader/JPypeClassLoader.java
--- 1.5.0-1/native/java/org/jpype/classloader/JPypeClassLoader.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/classloader/JPypeClassLoader.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,149 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.classloader;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.TreeMap;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-
-/**
- * Specialized class loader for JPype resources.
- * <p>
- * Loader to convert the internally stored resources into java classes. This
- * prevents class load order problems when there are class dependencies.
- * <p>
- */
-public class JPypeClassLoader extends ClassLoader
-{
-
-  static private JPypeClassLoader instance;
-  private TreeMap<String, byte[]> map = new TreeMap<>();
-
-  /**
-   * Get the class loader.
-   *
-   * @return the singleton class loader.
-   */
-  public static JPypeClassLoader getInstance()
-  {
-    if (instance == null)
-    {
-      JPypeClassLoader.instance = new JPypeClassLoader(getSystemClassLoader());
-    }
-    return instance;
-  }
-
-  private JPypeClassLoader(ClassLoader parent)
-  {
-    super(parent);
-  }
-
-  /**
-   * Add a class to the class loader.
-   * <p>
-   * This can be called from within python to add a class to the Java JVM.
-   *
-   * @param name is the name of the class.
-   * @param code is the byte code.
-   */
-  public void importClass(String name, byte[] code)
-  {
-    map.put(name, code);
-  }
-
-  /**
-   * Import a jar from memory into the class loader.
-   * <p>
-   * Does not handle unknown jar entry lengths.
-   *
-   * @param bytes
-   */
-  public void importJar(byte[] bytes)
-  {
-    try (JarInputStream is = new JarInputStream(new ByteArrayInputStream(bytes)))
-    {
-      while (true)
-      {
-        JarEntry nextEntry = is.getNextJarEntry();
-        if (nextEntry == null)
-          break;
-
-        // Skip directories and other non-class resources
-        long size = nextEntry.getSize();
-        if (size == 0)
-          continue;
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        int q;
-        while ((q = is.read()) != -1)
-          baos.write(q);
-        byte[] data = baos.toByteArray();
-
-        // Store all classes we find
-        String name = nextEntry.getName();
-        importClass(name, data);
-      }
-    } catch (IOException ex)
-    {
-      throw new RuntimeException(ex);
-    }
-  }
-
-  /**
-   * Loads a class from the class loader.
-   *
-   * @param name is the name of the class with java class notation (using dots).
-   * @return the class
-   * @throws ClassNotFoundException was not found by the class loader.
-   * @throws ClassFormatError if the class byte code was invalid.
-   */
-  @Override
-  public Class findClass(String name) throws ClassNotFoundException, ClassFormatError
-  {
-    String mname = name.replace('.', '/') + ".class";
-    byte[] data = map.get(mname);
-    if (data == null)
-    {
-      // Call the default implementation, throws ClassNotFoundException
-      return super.findClass(name);
-    }
-
-    Class cls = defineClass(name, data, 0, data.length);
-    if (cls == null)
-      throw new ClassFormatError("Class load was null");
-    return cls;
-  }
-
-  /**
-   * Overload for thunk resources.
-   *
-   * @param s
-   * @return
-   */
-  @Override
-  public InputStream getResourceAsStream(String s)
-  {
-    if (this.map.containsKey(s))
-    {
-      return new ByteArrayInputStream(this.map.get(s));
-    }
-    return super.getResourceAsStream(s);
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/AttrGrammar.java 1.6.0-1/native/java/org/jpype/html/AttrGrammar.java
--- 1.5.0-1/native/java/org/jpype/html/AttrGrammar.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/AttrGrammar.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,249 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import org.jpype.html.Parser.Entity;
-import org.jpype.html.Parser.Rule;
-import org.w3c.dom.Attr;
-
-public class AttrGrammar implements Parser.Grammar
-{
-
-  static final AttrGrammar INSTANCE = new AttrGrammar();
-
-  private AttrGrammar()
-  {
-  }
-
-  @Override
-  public void start(Parser p)
-  {
-    p.state = State.FREE;
-    ((AttrParser) p).attrs.clear();
-  }
-
-  @Override
-  public Object end(Parser p)
-  {
-    return ((AttrParser) p).attrs;
-  }
-
-//<editor-fold desc="tokens">
-  enum Token implements Parser.Token
-  {
-    TEXT,
-    QUOTE("\""),
-    SQUOTE("'"),
-    EQ("="),
-    WHITESPACE(" ");
-
-    byte value;
-    String text;
-
-    Token()
-    {
-    }
-
-    Token(String s)
-    {
-      text = s;
-      if (s.length() == 1)
-        value = (byte) s.charAt(0);
-    }
-
-    @Override
-    final public boolean matches(byte b)
-    {
-      if (value == ' ')
-        return Character.isWhitespace(b);
-      if (value == 0)
-        return true;
-      return b == value;
-    }
-
-    @Override
-    public boolean runs()
-    {
-      return this == Token.TEXT;
-    }
-
-    @Override
-    public String toString()
-    {
-      if (text != null)
-        return text;
-      return "TEXT";
-    }
-  }
-
-  final static Token[] freeTokens = tokens(
-          Token.QUOTE, Token.SQUOTE, Token.EQ, Token.WHITESPACE, Token.TEXT);
-  final static Token[] qtTokens = tokens(
-          Token.QUOTE, Token.TEXT);
-  final static Token[] sqtTokens = tokens(
-          Token.SQUOTE, Token.TEXT);
-
-  final static Rule ignoreWS = new IgnoreWSRule();
-  final static Rule quoteRule = new QuoteRule();
-  final static Rule endRule = new EndQuoteRule();
-  final static Rule attrRule = new AttrRule();
-  final static Rule boolRule = new BooleanRule();
-
-  final static Rule[] freeRules = rules(attrRule, boolRule, ignoreWS, quoteRule);
-  final static Rule[] qtRules = rules(endRule);
-
-  static Token[] tokens(Token... t)
-  {
-    return t;
-  }
-
-  static Rule[] rules(Rule... t)
-  {
-    return t;
-  }
-//</editor-fold>
-//<editor-fold desc="state">
-
-  enum State implements Parser.State
-  {
-    FREE(freeTokens, freeRules),
-    IN_QUOTE(qtTokens, qtRules),
-    IN_SQUOTE(sqtTokens, qtRules);
-
-    Token[] tokens;
-    Rule[] rules;
-
-    State(Token[] tokens, Rule[] rules)
-    {
-      this.tokens = tokens;
-      this.rules = rules;
-    }
-
-    @Override
-    public Token[] getTokens()
-    {
-      return this.tokens;
-    }
-
-    @Override
-    public Rule[] getRules()
-    {
-      return this.rules;
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="rules">
-  static class IgnoreWSRule implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      if (entity.token != Token.WHITESPACE)
-        return false;
-      parser.stack.removeLast();
-      return true;
-    }
-  }
-
-  static class QuoteRule implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      if (entity.token == Token.QUOTE)
-      {
-        parser.state = State.IN_QUOTE;
-        parser.stack.removeLast();
-        return true;
-      }
-      if (entity.token == Token.SQUOTE)
-      {
-        parser.state = State.IN_SQUOTE;
-        parser.stack.removeLast();
-        return true;
-      }
-      return false;
-    }
-  }
-
-  static class EndQuoteRule implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      if (State.IN_QUOTE == parser.state && entity.token == Token.QUOTE)
-      {
-        parser.state = State.FREE;
-        parser.stack.removeLast();
-        return true;
-      }
-      if (State.IN_SQUOTE == parser.state && entity.token == Token.SQUOTE)
-      {
-        parser.state = State.FREE;
-        parser.stack.removeLast();
-        return true;
-      }
-      return false;
-    }
-  }
-
-  static class AttrRule extends Parser.MatchRule
-  {
-
-    AttrRule()
-    {
-      super(Token.TEXT, Token.EQ, Token.TEXT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      Entity e2 = (Entity) parser.stack.removeLast();
-      Entity e1 = (Entity) parser.stack.removeLast();
-      Entity e0 = (Entity) parser.stack.removeLast();
-      AttrParser aparser = (AttrParser) parser;
-      Attr attr = aparser.doc.createAttribute((String) e0.value);
-      attr.setNodeValue((String) e2.value);
-      aparser.attrs.add(attr);
-    }
-  }
-
-  static class BooleanRule extends Parser.MatchRule
-  {
-
-    BooleanRule()
-    {
-      super(Token.TEXT, Token.TEXT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      Entity e2 = (Entity) parser.stack.removeLast();
-      Entity e1 = (Entity) parser.stack.removeLast();
-      AttrParser aparser = (AttrParser) parser;
-      Attr attr = aparser.doc.createAttribute((String) e1.value);
-      attr.setNodeValue((String) e1.value);
-      aparser.attrs.add(attr);
-      parser.stack.add(e2);
-    }
-  }
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/AttrParser.java 1.6.0-1/native/java/org/jpype/html/AttrParser.java
--- 1.5.0-1/native/java/org/jpype/html/AttrParser.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/AttrParser.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,34 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-
-public class AttrParser extends Parser<List<Attr>>
-{
-
-  final Document doc;
-  final List<Attr> attrs = new ArrayList<>();
-
-  public AttrParser(Document doc)
-  {
-    super(AttrGrammar.INSTANCE);
-    this.doc = doc;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/Html.java 1.6.0-1/native/java/org/jpype/html/Html.java
--- 1.5.0-1/native/java/org/jpype/html/Html.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/Html.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,183 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import org.jpype.JPypeContext;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-
-public class Html
-{
-
-  public final static HashSet<String> VOID_ELEMENTS = new HashSet<>();
-  public final static HashSet<String> OPTIONAL_ELEMENTS = new HashSet<>();
-  public final static HashSet<String> OPTIONAL_CLOSE = new HashSet<>();
-
-  static
-  {
-    VOID_ELEMENTS.addAll(Arrays.asList(
-            "area", "base", "br", "col", "command", "embed", "hr", "img",
-            "input", "keygen", "link", "meta", "param", "source", "track", "wbr"));
-    OPTIONAL_ELEMENTS.addAll(Arrays.asList("html", "head", "body", "p", "dt",
-            "dd", "li", "option", "thead", "th", "tbody", "tr", "td", "tfoot", "colgroup"));
-    OPTIONAL_CLOSE.addAll(Arrays.asList("li:li", "dt:dd",
-            "p:address", "p:article", "p:aside", "p:blockquote", "p:details",
-            "p:div", "p:dl", "p:fieldset", "p:figcaption", "p:figure",
-            "p:footer", "p:form", "p:h1", "p:h2", "p:h3", "p:h4", "p:h5", "p:h6",
-            "p:header", "p:hgroup", "p:hr", "p:main", "p:menu",
-            "p:nav", "p:ol", "p:p", "p:pre", "p:section", "p:table", "p:ul",
-            "dd:dt", "dd:dd", "dt:dt", "dt:dd", "rt:rt", "rt:rp", "rp:rt", "rp:rp",
-            "optgroup:optgroup", "option:option", "option:optiongroup", "thread:tbody",
-            "thread:tfoot", "tbody:tfoot", "tbody:tbody", "tr:tr", "td:td", "td:th",
-            "th:td", "p:li"));
-  }
-
-  public static Parser<Document> newParser()
-  {
-    return new HtmlParser();
-  }
-
-  public static List<Attr> parseAttributes(Document doc, String str)
-  {
-    AttrParser p = new AttrParser(doc);
-    p.parse(str);
-    return p.attrs;
-  }
-
-//<editor-fold desc="decode" defaultstate="collapsed">
-  public static Map<String, Integer> ENTITIES = new HashMap<>();
-
-  static
-  {
-    try (InputStream is = JPypeContext.getInstance().getClass().getClassLoader()
-            .getResourceAsStream("org/jpype/html/entities.txt");
-            InputStreamReader isr = new InputStreamReader(is);
-            BufferedReader rd = new BufferedReader(isr))
-    {
-      while (true)
-      {
-        String line = rd.readLine();
-        if (line == null)
-          break;
-        if (line.startsWith("#"))
-          continue;
-        String[] parts = line.split("\\s+");
-        ENTITIES.put(parts[0], Integer.parseInt(parts[1]));
-      }
-    } catch (IOException ex)
-    {
-      throw new RuntimeException(ex);
-    }
-  }
-
-  public static String decode(String s)
-  {
-    if (!s.contains("&"))
-      return s;
-
-    int dead = 0;
-    byte[] b = s.getBytes(StandardCharsets.UTF_8);
-    for (int i = 0; i < b.length; ++i)
-    {
-      if (b[i] != '&')
-        continue;
-
-      int i1 = i;
-      int i2 = i + 1;
-      if (i2 == b.length)
-        break;
-      if (b[i2] == '#')
-      {
-        // Try to be robust when there is no ;
-        for (i = i2 + 1; i < b.length; ++i)
-        {
-          if (!Character.isDigit(b[i]))
-            break;
-        }
-      } else
-      {
-        for (i = i2; i < b.length; ++i)
-        {
-          if (b[i] == ';')
-            break;
-        }
-      }
-      int i3 = i;
-      int c = 0;
-      if (b[i2] == '#')
-      {
-        i2++;
-        try
-        {
-          c = Integer.parseInt(new String(b, i2, i3 - i2, StandardCharsets.UTF_8));
-        } catch (NumberFormatException ex)
-        {
-
-        }
-      } else
-      {
-        String e = new String(b, i2, i3 - i2, StandardCharsets.UTF_8);
-        Integer c2 = ENTITIES.get(e);
-        if (c2 == null)
-          throw new RuntimeException("Bad entity " + e);
-        c = c2;
-      }
-
-      // Substitute
-      if (c < 128)
-      {
-        b[i1++] = (byte) c;
-      } else if (c < 0x0800)
-      {
-        b[i1++] = (byte) (0xc0 + ((c >> 6) & 0x1f));
-        if (i1 < b.length) // lgtm [java/constant-comparison]
-          b[i1++] = (byte) (0x80 + (c & 0x3f)); // lgtm [java/index-out-of-bounds]
-      } else
-      {
-        b[i1++] = (byte) (0xe0 + ((c >> 12) & 0x0f));
-        if (i1 < b.length) // lgtm [java/constant-comparison]
-          b[i1++] = (byte) (0x80 + ((c >> 6) & 0x3f)); // lgtm [java/index-out-of-bounds]
-        if (i1 < b.length)
-          b[i1++] = (byte) (0x80 + (c & 0x3f));
-      }
-      if (i3 < b.length && b[i3] == ';')
-        i3++;
-      dead += i3 - i1;
-      for (; i1 < i3; ++i1)
-        b[i1] = 0;
-      i = i3;
-    }
-    int j = 0;
-    byte[] b2 = new byte[b.length - dead];
-    for (int i = 0; i < b.length; ++i)
-    {
-      if (b[i] != 0)
-        b2[j++] = b[i];
-    }
-    return new String(b2, StandardCharsets.UTF_8);
-  }
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/HtmlGrammar.java 1.6.0-1/native/java/org/jpype/html/HtmlGrammar.java
--- 1.5.0-1/native/java/org/jpype/html/HtmlGrammar.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/HtmlGrammar.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,669 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import java.util.LinkedList;
-import org.jpype.html.Parser.Entity;
-import org.jpype.html.Parser.Rule;
-
-public class HtmlGrammar implements Parser.Grammar
-{
-
-  final static HtmlGrammar INSTANCE = new HtmlGrammar();
-
-  private HtmlGrammar()
-  {
-  }
-
-  @Override
-  public void start(Parser p)
-  {
-    p.state = State.FREE;
-    ((HtmlParser) p).handler.startDocument();
-  }
-
-  @Override
-  public Object end(Parser p)
-  {
-    ((HtmlParser) p).handler.endDocument();
-    return ((HtmlParser) p).handler.getResult();
-  }
-
-  // BeginElement does < </ and <! via lookhead
-  static StringBuilder promote(Entity e)
-  {
-    if (e.value != null && e.value instanceof StringBuilder)
-      return (StringBuilder) e.value;
-    StringBuilder sb = new StringBuilder(e.toString());
-    e.value = sb;
-    e.token = Token.TEXT;
-    return (StringBuilder) sb;
-  }
-
-  static HtmlGrammar getGrammar(Parser p)
-  {
-    return ((HtmlGrammar) p.grammar);
-  }
-
-  static HtmlHandler getHandler(Parser p)
-  {
-    return ((HtmlParser) p).handler;
-  }
-
-  /**
-   * Send everything out to as text.
-   */
-  void flushText(Parser<?> parser)
-  {
-    if (parser.stack.isEmpty())
-      return;
-
-    Entity last = parser.stack.removeLast();
-    StringBuilder s = new StringBuilder();
-    for (Entity e : parser.stack)
-    {
-      s.append(e.toString());
-    }
-    ((HtmlParser) parser).handler.text(s.toString());
-    parser.stack.clear();
-    parser.stack.add(last);
-  }
-
-//<editor-fold desc="state">
-  enum State implements Parser.State
-  {
-    FREE(freeTokens, freeRules),
-    ELEMENT(elementTokens, elementRules),
-    DIRECTIVE(directiveTokens, directiveRules),
-    CDATA(cdataTokens, cdataRules),
-    COMMENT(commentTokens, commentRules);
-
-    Token[] tokens;
-    Rule[] rules;
-
-    State(Token[] tokens, Rule[] rules)
-    {
-      this.tokens = tokens;
-      this.rules = rules;
-    }
-
-    @Override
-    public Token[] getTokens()
-    {
-      return this.tokens;
-    }
-
-    @Override
-    public Rule[] getRules()
-    {
-      return this.rules;
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="tokens" defaultstate="collapsed">
-  enum Token implements Parser.Token
-  {
-    TEXT,
-    BANG("!"),
-    DASH("-"),
-    LT("<"),
-    GT(">"),
-    SLASH("/"),
-    AMP("&"),
-    SEMI(";"),
-    LSB("["),
-    RSB("]"),
-    CLOSE("</"),
-    QUOTE("\""),
-    SQUOTE("'"),
-    DECL_DIRECTIVE("<!");
-
-    byte value;
-    String text;
-
-    Token()
-    {
-    }
-
-    Token(String s)
-    {
-      text = s;
-      if (s.length() == 1)
-        value = (byte) s.charAt(0);
-    }
-
-    @Override
-    final public boolean matches(byte b)
-    {
-      if (value == 0)
-        return true;
-      return b == value;
-    }
-
-    @Override
-    public boolean runs()
-    {
-      return this == Token.TEXT;
-    }
-
-    @Override
-    public String toString()
-    {
-      if (text != null)
-        return text;
-      return "TEXT";
-    }
-  }
-//</editor-fold>
-//<editor-fold desc="rules">
-
-  final static Rule escaped = new Escaped();
-  final static Rule slash = new Cleanup();
-  final static Rule mergeText = new MergeText();
-  final static Rule beginElement = new BeginElement();
-  final static Rule startElement = new StartElement();
-  final static Rule completeElement = new CompleteElement();
-  final static Rule endElement = new EndElement();
-  final static Rule quote = new StartQuote();
-
-  final static Token[] freeTokens = tokens(
-          Token.LT, Token.AMP, Token.SEMI, Token.TEXT);
-  final static Token[] elementTokens = tokens(
-          Token.BANG, Token.AMP, Token.LT, Token.SEMI, Token.SLASH,
-          Token.GT, Token.QUOTE, Token.SQUOTE, Token.TEXT);
-  final static Token[] directiveTokens = tokens(
-          Token.DASH, Token.LSB, Token.RSB, Token.LT, Token.GT, Token.QUOTE,
-          Token.SQUOTE, Token.TEXT);
-  final static Token[] cdataTokens = tokens(
-          Token.LSB, Token.RSB, Token.GT, Token.TEXT);
-  final static Token[] commentTokens = tokens(
-          Token.DASH, Token.GT, Token.TEXT);
-  final static Token[] quoteTokens = tokens(
-          Token.QUOTE, Token.TEXT);
-  final static Token[] squoteTokens = tokens(
-          Token.SQUOTE, Token.TEXT);
-
-  final static Rule[] freeRules = rules(
-          beginElement, escaped, mergeText);
-  final static Rule[] elementRules = rules(
-          mergeText, quote, startElement, endElement, completeElement, escaped, slash);
-  final static Rule[] directiveRules = rules(quote,
-          new EndDirective(), mergeText);
-  final static Rule[] cdataRules = rules(
-          new EndCData());
-  final static Rule[] commentRules = rules(
-          new EndComment(), new StartComment());
-
-  private static Rule[] rules(Rule... t)
-  {
-    return t;
-  }
-
-  private static Token[] tokens(Token... t)
-  {
-    return t;
-  }
-
-//</editor-fold>
-//<editor-fold desc="inner" defaultstate="collapsed">
-  private static class Cleanup implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser<?> parser, Entity entity)
-    {
-      if (entity.token == Token.SLASH)
-      {
-        parser.lookahead = this::next;
-        return false;
-      }
-
-      if (entity.token == Token.GT && parser.stack.size() > 4)
-      {
-        // This it to help debug a rare problem
-        for (Entity e : parser.stack)
-        {
-          System.out.print(e.token);
-          System.out.print("(");
-          System.out.print(e.value);
-          System.out.print(") ");
-        }
-        System.out.println();
-        throw new RuntimeException("Need cleanup");
-      }
-      return false;
-    }
-
-    private boolean next(Parser<?> parser, Entity entity)
-    {
-      if (entity.token != Token.GT)
-      {
-        parser.stack.removeLast();
-        parser.stack.removeLast();
-        // parser.stack.getLast().token = Token.TEXT;
-        entity.value = "/" + entity.value;
-        parser.stack.add(entity);
-      }
-      return false;
-    }
-  }
-
-  private static class Escaped extends Parser.MatchRule
-  {
-
-    public Escaped()
-    {
-      super(Token.AMP, Token.TEXT, Token.SEMI);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      Entity e2 = stack.removeLast();
-      Entity e1 = stack.removeLast();
-      // Currently we do not verify the text contents
-      Entity e0 = stack.getLast();
-      promote(e0).append(e1.toString()).append(e2.toString());
-    }
-  }
-
-  static class MergeText extends Parser.MatchRule
-  {
-
-    public MergeText()
-    {
-      super(Token.TEXT, Token.TEXT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      Entity t2 = stack.removeLast();
-      Entity t1 = stack.getLast();
-      promote(t1).append(t2.toString());
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="elements" defaultstate="collapsed">
-  static class BeginElement implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      if (entity.token != Token.LT)
-        return false;
-      getGrammar(parser).flushText(parser);
-      parser.state = State.ELEMENT;
-      parser.lookahead = this::next;
-      return true;
-    }
-
-    public boolean next(Parser<?> parser, Parser.Entity entity)
-    {
-      if (entity.token == Token.SLASH)
-      {
-        parser.stack.removeLast();
-        parser.stack.getLast().token = Token.CLOSE;
-        return true;
-      }
-
-      if (entity.token == Token.BANG)
-      {
-        parser.stack.removeLast();
-        Entity last = parser.stack.getLast();
-        last.token = Token.DECL_DIRECTIVE;
-
-        parser.lookahead = new Directive();
-        parser.state = State.DIRECTIVE;
-        return true;
-      }
-      return false;
-    }
-  }
-
-  static class StartElement extends Parser.MatchRule
-  {
-
-    StartElement()
-    {
-      super(Token.LT, Token.TEXT, Token.GT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      stack.removeLast();
-      Entity e1 = stack.removeLast();
-      stack.removeLast();
-      String content = e1.value.toString();
-      getGrammar(parser).flushText(parser);
-      String[] parts = content.split("\\s+", 2);
-      if (parts.length == 1)
-        getHandler(parser).startElement(content, null);
-      else
-        getHandler(parser).startElement(parts[0], parts[1]);
-      parser.state = State.FREE;
-    }
-
-  }
-
-  static class CompleteElement extends Parser.MatchRule
-  {
-
-    CompleteElement()
-    {
-      super(Token.LT, Token.TEXT, Token.SLASH, Token.GT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      stack.removeLast(); // >
-      stack.removeLast(); // /
-      Entity e1 = stack.removeLast();
-      stack.removeLast(); // <
-      String content = e1.value.toString();
-      stack.clear();
-      int i = content.indexOf(" ");
-
-      if (i == -1)
-      {
-        getHandler(parser).startElement(content, null);
-        getHandler(parser).endElement(content);
-      } else
-      {
-        String name = content.substring(0, i);
-        String attr = content.substring(i).trim();
-        getHandler(parser).startElement(name, attr);
-        getHandler(parser).endElement(name);
-      }
-      parser.state = State.FREE;
-    }
-  }
-
-  static class EndElement extends Parser.MatchRule
-  {
-
-    EndElement()
-    {
-      super(Token.CLOSE, Token.TEXT, Token.GT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      Entity e2 = stack.removeLast();
-      Entity e1 = stack.removeLast();
-      Entity e0 = stack.removeLast();
-      String content = e1.value.toString();
-      getGrammar(parser).flushText(parser);
-      getHandler(parser).endElement(content);
-      parser.state = State.FREE;
-    }
-  }
-
-  static class EndDirective extends Parser.MatchRule
-  {
-
-    EndDirective()
-    {
-      super(Token.DECL_DIRECTIVE, Token.TEXT, Token.GT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      Entity e2 = stack.removeLast();
-      Entity e1 = stack.removeLast();
-      Entity e0 = stack.removeLast();
-      String content = e1.value.toString();
-      getGrammar(parser).flushText(parser);
-      getHandler(parser).directive(content);
-      parser.state = State.FREE;
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="comment" defaultstate="collapsed">
-  // This is a look ahead rule
-  static class Directive implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      if (entity.token == Token.LSB)
-      {
-        parser.lookahead = new CData();
-        return true;
-      }
-
-      if (entity.token == Token.DASH)
-      {
-        parser.lookahead = new Comment();
-        return true;
-      }
-
-      return false;
-    }
-
-  }
-
-  static class CData implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      if (entity.token != Token.TEXT)
-        parser.error("Expected CDATA");
-      parser.lookahead = this::next;
-      return true;
-    }
-
-    public boolean next(Parser parser, Parser.Entity entity)
-    {
-      if (entity.token != Token.LSB)
-        parser.error("Expected [");
-      parser.stack.clear();
-      parser.state = State.CDATA;
-      return true;
-    }
-
-  }
-
-  static class EndCData extends Parser.MatchRule
-  {
-
-    public EndCData()
-    {
-      super(Token.RSB, Token.RSB, Token.GT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      stack.removeLast(); // >
-      stack.removeLast(); // ]
-      stack.removeLast(); // ]
-      Entity first = stack.removeFirst();
-      StringBuilder sb = promote(first);
-      for (Entity e : stack)
-      {
-        sb.append(e.toString());
-      }
-      stack.clear();
-      ((HtmlParser) parser).handler.cdata(sb.toString());
-      parser.state = State.FREE;
-    }
-  }
-
-  static class Comment implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      if (entity.token != Token.DASH)
-        parser.error("Expected -");
-      parser.lookahead = this::next;
-      parser.stack.clear();
-      parser.state = State.COMMENT;
-      return true;
-    }
-
-    public boolean next(Parser parser, Parser.Entity entity)
-    {
-      if (entity.token == Token.DASH)
-        parser.error("Bad comment(-)");
-      if (entity.token == Token.GT)
-        parser.error("Bad comment(>)");
-      return false;
-    }
-  }
-
-  static class StartComment extends Parser.MatchRule
-  {
-
-    public StartComment()
-    {
-      super(Token.LT, Token.BANG, Token.DASH, Token.DASH);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      parser.lookahead = this::next;
-    }
-
-    public boolean next(Parser parser, Parser.Entity entity)
-    {
-      if (entity.token == Token.GT)
-        return false;
-      parser.error("Comment contains <!--");
-      return true;
-    }
-  }
-
-  /**
-   * Double dash in Comments.
-   */
-  static class EndComment extends Parser.MatchRule
-  {
-
-    public EndComment()
-    {
-      super(Token.DASH, Token.DASH, Token.GT);
-    }
-
-    @Override
-    public void execute(Parser parser)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      stack.removeLast(); // >
-      stack.removeLast(); // -
-      stack.removeLast(); // -
-      if (stack.isEmpty())
-        return;
-      Entity first = stack.removeFirst();
-      StringBuilder sb = promote(first);
-      for (Entity e : stack)
-      {
-        sb.append(e.toString());
-      }
-      stack.clear();
-      ((HtmlParser) parser).handler.comment(sb.toString());
-      parser.state = State.FREE;
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="quote">
-  static class StartQuote implements Rule
-  {
-
-    @Override
-    public boolean apply(Parser parser, Parser.Entity entity)
-    {
-      if (entity.token == Token.QUOTE
-              || entity.token == Token.SQUOTE)
-      {
-        parser.state = new Quoted(entity.token, parser.state);
-        parser.stack.removeLast();
-        parser.add(Token.TEXT, entity.toString());
-        return true;
-      }
-      return false;
-    }
-  }
-
-  static class Quoted implements Parser.State, Parser.Rule
-  {
-
-    Parser.Token[] tokens;
-    Parser.Rule[] rules;
-    private Parser.State state;
-
-    Quoted(Parser.Token token, Parser.State state)
-    {
-      this.tokens = new Parser.Token[]
-      {
-        token, Token.TEXT
-      };
-      rules = new Parser.Rule[]
-      {
-        this, mergeText
-      };
-      this.state = state;
-    }
-
-    @Override
-    public Parser.Token[] getTokens()
-    {
-      return tokens;
-    }
-
-    @Override
-    public Parser.Rule[] getRules()
-    {
-      return rules;
-    }
-
-    @Override
-    public boolean apply(Parser<?> parser, Parser.Entity entity)
-    {
-      if (entity.token != tokens[0])
-        return false;
-      parser.stack.removeLast();
-      Entity last = parser.stack.getLast();
-      promote(last).append('"');
-      parser.state = state;
-      return true;
-    }
-  }
-
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/HtmlHandler.java 1.6.0-1/native/java/org/jpype/html/HtmlHandler.java
--- 1.5.0-1/native/java/org/jpype/html/HtmlHandler.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/HtmlHandler.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,39 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-public interface HtmlHandler
-{
-
-  void cdata(String text);
-
-  void comment(String contents);
-
-  void endDocument();
-
-  void endElement(String name);
-
-  void startDocument();
-
-  void startElement(String name, String attr);
-
-  void text(String text);
-
-  public Object getResult();
-
-  public void directive(String content);
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/HtmlParser.java 1.6.0-1/native/java/org/jpype/html/HtmlParser.java
--- 1.5.0-1/native/java/org/jpype/html/HtmlParser.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/HtmlParser.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,30 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import org.w3c.dom.Document;
-
-public class HtmlParser extends Parser<Document>
-{
-
-  HtmlHandler handler = new HtmlTreeHandler();
-
-  public HtmlParser()
-  {
-    super(HtmlGrammar.INSTANCE);
-  }
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/HtmlTreeHandler.java 1.6.0-1/native/java/org/jpype/html/HtmlTreeHandler.java
--- 1.5.0-1/native/java/org/jpype/html/HtmlTreeHandler.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/HtmlTreeHandler.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,230 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import java.util.LinkedList;
-import java.util.ListIterator;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-
-/**
- * HTML document handler which creates an HTML tree.
- */
-public class HtmlTreeHandler implements HtmlHandler
-{
-
-  final Document root;
-  LinkedList<Element> elementStack = new LinkedList<>();
-  AttrParser attrParser;
-  Node current;
-  int errors = 0;
-
-  public HtmlTreeHandler()
-  {
-    try
-    {
-      DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-      root = db.newDocument();
-      current = root;
-      attrParser = new AttrParser(root);
-    } catch (ParserConfigurationException ex)
-    {
-      throw new RuntimeException(ex);
-    }
-  }
-
-  private String lastNodeName()
-  {
-    if (this.elementStack.isEmpty())
-      return "";
-    return this.elementStack.getLast().getNodeName();
-  }
-
-  @Override
-  public void startElement(String name, String attr)
-  {
-    name = name.toLowerCase().trim();
-    String attr0 = attr;
-
-    // Html has irregular end rules.
-    while (Html.OPTIONAL_ELEMENTS.contains(name))
-    {
-      String close = lastNodeName() + ":" + name;
-      if (Html.OPTIONAL_CLOSE.contains(close))
-      {
-//        System.out.print("AUTO ");
-        this.endElement(lastNodeName());
-      } else
-        break;
-    }
-
-//    System.out.println(this.elementStack.size() + " " + name + " : " + attr);
-    Element elem;
-    try
-    {
-      elem = root.createElement(name);
-    } catch (Exception ex)
-    {
-      throw new RuntimeException("Fail to create node '" + name + "'", ex);
-    }
-    if (attr != null)
-    {
-      for (Attr a : attrParser.parse(attr))
-        elem.setAttributeNode(a);
-    }
-    current.appendChild(elem);
-    if (Html.VOID_ELEMENTS.contains(name))
-      return;
-    current = elem;
-    elementStack.add(elem);
-  }
-
-  public String getPath()
-  {
-    StringBuilder path = new StringBuilder();
-    for (Element s : this.elementStack)
-    {
-      path.append("/");
-      path.append(s.getNodeName());
-      NamedNodeMap attrs = s.getAttributes();
-      if (attrs.getLength() > 0)
-      {
-        path.append('[');
-        for (int i = 0; i < attrs.getLength(); ++i)
-        {
-          Attr item = (Attr) attrs.item(i);
-          path.append(item.getName());
-          path.append('=');
-          path.append(item.getValue());
-          path.append(' ');
-        }
-        path.append(']');
-      }
-    }
-    return path.toString();
-  }
-
-  @Override
-  public void endElement(String name)
-  {
-    name = name.toLowerCase().trim();
-    if (elementStack.isEmpty())
-      throw new RuntimeException("Empty stack");
-    Element last = elementStack.getLast();
-    // Handle auto class tags
-    while (!last.getNodeName().equals(name) && Html.OPTIONAL_ELEMENTS.contains(last.getNodeName()))
-    {
-//      System.out.print("AUTO2 ");
-      endElement(last.getNodeName());
-      last = elementStack.getLast();
-    }
-//    System.out.println(this.elementStack.size() - 1 + " ~" + name);
-    if (!last.getNodeName().equals(name))
-    {
-      errors++;
-
-      // Try to deal with unclosed tags gracefully.
-      ListIterator<Element> iter = this.elementStack.listIterator(this.elementStack.size() - 1);
-      int i = 0;
-      while (iter.hasNext())
-      {
-        Element prev = iter.previous();
-        if (prev.getNodeName().equals(name))
-          break;
-        i++;
-      }
-      if (iter.hasPrevious())
-      {
-        for (int j = 0; j < i; j++)
-        {
-          System.err.println("Ignoring missing close tag " + last.getNodeName() + ", got " + name + " at " + getPath());
-          this.endElement(last.getNodeName());
-        }
-      } else if (errors > 3)
-        throw new RuntimeException("mismatch element " + name
-                + " " + last.getNodeName() + " at " + getPath());
-      else
-      {
-        System.err.println("Ignoring mismatched element " + name
-                + " " + last.getNodeName() + " at " + getPath());
-        return;
-      }
-    }
-    elementStack.removeLast();
-    if (elementStack.isEmpty())
-      current = root;
-    else
-      current = elementStack.getLast();
-  }
-
-  @Override
-  public void comment(String contents)
-  {
-    if (contents.equals(">"))
-      throw new RuntimeException();
-    current.appendChild(root.createComment(contents));
-  }
-
-  @Override
-  public void text(String text)
-  {
-//    System.out.println("  TEXT " + text);
-    if (text.length() == 0)
-      return;
-    if (text.contains("<"))
-      throw new RuntimeException("bad text `" + text + "` at " + getPath());
-    if (current == root)
-      return;
-    current.appendChild(root.createTextNode(text));
-  }
-
-  @Override
-  public void cdata(String text)
-  {
-    current.appendChild(root.createCDATASection(text));
-  }
-
-  @Override
-  public void startDocument()
-  {
-  }
-
-  @Override
-  public void endDocument()
-  {
-  }
-
-  @Override
-  public Object getResult()
-  {
-    return root;
-  }
-
-  @Override
-  public void directive(String content)
-  {
-    int i = content.indexOf(" ");
-    current.appendChild(root.createProcessingInstruction(content.substring(0, i),
-            content.substring(i).trim()));
-  }
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/HtmlWriter.java 1.6.0-1/native/java/org/jpype/html/HtmlWriter.java
--- 1.5.0-1/native/java/org/jpype/html/HtmlWriter.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/HtmlWriter.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,172 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import org.w3c.dom.Attr;
-import org.w3c.dom.CDATASection;
-import org.w3c.dom.Comment;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.w3c.dom.ProcessingInstruction;
-import org.w3c.dom.Text;
-
-public class HtmlWriter implements Closeable
-{
-
-  OutputStream os;
-  BufferedWriter writer;
-
-  public HtmlWriter(OutputStream os)
-  {
-    this.os = os;
-    this.writer = new BufferedWriter(new OutputStreamWriter(os));
-  }
-
-  public static String asString(Node node) throws IOException
-  {
-    try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
-    {
-      HtmlWriter hw = new HtmlWriter(baos);
-      hw.write(node);
-      hw.close();
-      return baos.toString();
-    }
-  }
-
-  public void write(Node n) throws IOException
-  {
-
-    if (n == null)
-    {
-      writer.write("NULL");
-      return;
-    }
-    switch (n.getNodeType())
-    {
-      case Node.PROCESSING_INSTRUCTION_NODE:
-        writeDirective((ProcessingInstruction) n);
-        break;
-      case Node.ELEMENT_NODE:
-        writeElement((Element) n);
-        break;
-      case Node.DOCUMENT_FRAGMENT_NODE:
-        writeChildren(n);
-        break;
-      case Node.TEXT_NODE:
-        writeText((Text) n);
-        break;
-      case Node.COMMENT_NODE:
-        writeComment((Comment) n);
-        break;
-      case Node.CDATA_SECTION_NODE:
-        writeCData((CDATASection) n);
-        break;
-      default:
-        throw new RuntimeException("unhandled " + n.getClass());
-    }
-  }
-
-  public void writeChildren(Node doc) throws IOException
-  {
-    NodeList children = doc.getChildNodes();
-    for (int i = 0; i < children.getLength(); ++i)
-    {
-      write(children.item(i));
-    }
-  }
-
-  public void writeDirective(ProcessingInstruction d) throws IOException
-  {
-    writer.write("<!");
-    writer.write(d.getTarget());
-    writer.write(" ");
-    writer.write(d.getData());
-    writer.write(">");
-  }
-
-  public void writeElement(Element e) throws IOException
-  {
-    String name = e.getTagName();
-    writer.write("<");
-    writer.write(name);
-    NamedNodeMap attributes = e.getAttributes();
-    if (attributes.getLength() > 0)
-    {
-      for (int i = 0; i < attributes.getLength(); ++i)
-      {
-        writer.write(" ");
-        Attr attr = (Attr) attributes.item(i);
-        writer.write(attr.getName());
-        writer.write("=\"");
-        writer.write(attr.getValue());
-        writer.write('"');
-      }
-    }
-    if (Html.VOID_ELEMENTS.contains(name))
-    {
-      writer.write(">");
-      return;
-    }
-
-    NodeList children = e.getChildNodes();
-
-    if (children.getLength() == 0)
-    {
-      writer.write("/>");
-    } else
-    {
-      writer.write(">");
-      writeChildren(e);
-      writer.write("</");
-      writer.write(name);
-      writer.write(">");
-    }
-  }
-
-  private void writeComment(Comment comment) throws IOException
-  {
-    writer.write("<!--");
-    writer.write(comment.getData());
-    writer.write("-->");
-  }
-
-  private void writeCData(CDATASection cData) throws IOException
-  {
-    writer.write("<![CDATA[");
-    writer.write(cData.getData());
-    writer.write("]]>");
-  }
-
-  private void writeText(Text text) throws IOException
-  {
-    writer.write(text.getData());
-  }
-
-  @Override
-  public void close() throws IOException
-  {
-    writer.close();
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/Parser.java 1.6.0-1/native/java/org/jpype/html/Parser.java
--- 1.5.0-1/native/java/org/jpype/html/Parser.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/Parser.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,304 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.html;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-import java.util.LinkedList;
-import java.util.ListIterator;
-
-/**
- * Generic document parser.
- *
- * @param <T>
- */
-public class Parser<T>
-{
-
-  final Grammar grammar;
-  public State state = null;
-  public Token last = null;
-  public Rule lookahead = null;
-  public LinkedList<Entity> stack = new LinkedList<>();
-
-  Parser(Grammar grammar)
-  {
-    this.grammar = grammar;
-  }
-
-  public T parse(InputStream is)
-  {
-    ByteBuffer incoming = ByteBuffer.allocate(1024);
-    ByteBuffer outgoing = ByteBuffer.allocate(1024);
-    ReadableByteChannel channel = Channels.newChannel(is);
-    stack.clear();
-    grammar.start(this);
-    try
-    {
-      while (true)
-      {
-        incoming.position(0);
-        int rc = channel.read(incoming);
-        if (rc < 0)
-          break;
-        int p = incoming.position();
-        incoming.rewind();
-        process(incoming, outgoing, rc);
-      }
-      flushTokens(outgoing);
-    } catch (IOException ex)
-    {
-      throw new RuntimeException(ex);
-    }
-    return (T) grammar.end(this);
-  }
-
-  public T parse(String str)
-  {
-    byte[] b = str.getBytes();
-    ByteBuffer incoming = ByteBuffer.wrap(b);
-    ByteBuffer outgoing = ByteBuffer.allocate(1024);
-    stack.clear();
-    grammar.start(this);
-    process(incoming, outgoing, b.length);
-    flushTokens(outgoing);
-    return (T) grammar.end(this);
-  }
-
-  private void process(ByteBuffer incoming, ByteBuffer outgoing, int rc)
-  {
-    while (incoming.position() < rc)
-    {
-      byte b = incoming.get();
-      Token match = null;
-      for (Token t : state.getTokens())
-      {
-        if (t.matches(b))
-        {
-          match = t;
-          break;
-        }
-      }
-      if (match == null)
-        this.error("Unable to parse " + (char) b);
-      else if (match.runs())
-      {
-        if (last != match)
-          flushTokens(outgoing);
-        if (!outgoing.hasRemaining())
-          flushTokens(outgoing);
-        outgoing.put(b);
-      } else
-      {
-        if (outgoing.position() > 0)
-          flushTokens(outgoing);
-        processToken(match, null);
-      }
-      last = match;
-    }
-  }
-
-  /**
-   * Send all the queue up text to a token.
-   */
-  private void flushTokens(ByteBuffer outgoing)
-  {
-    if (outgoing.position() == 0)
-      return;
-    processToken(last, new String(outgoing.array(), 0, outgoing.position()));
-    outgoing.rewind();
-  }
-
-  /**
-   * Process a token.
-   *
-   * This will add it to the stack and then match the stack with the nearest
-   * rule.
-   *
-   * @param token
-   * @param value
-   */
-  protected void processToken(Token token, String value)
-  {
-    if (token == null)
-      return;
-    Entity entity = add(token, value);
-
-    // Take the next lookahead
-    Rule rule1 = this.lookahead;
-    this.lookahead = null;
-    if (rule1 != null)
-    {
-//      System.out.println("      FORWARD " + rule1);
-      if (rule1.apply(this, entity))
-      {
-        return;
-      }
-    }
-
-    // If not handled then proceed to rules.
-    boolean done = false;
-    while (!done && !stack.isEmpty())
-    {
-      done = true;
-      for (Rule rule : state.getRules())
-      {
-//        System.out.println("      RULE " + rule);
-        if (rule.apply(this, stack.getLast()))
-        {
-          done = false;
-          break;
-        }
-      }
-    }
-  }
-
-  /**
-   * Add a token to the token stack
-   *
-   * @param token
-   * @param object
-   * @return
-   */
-  public Entity add(Token token, String object)
-  {
-    Entity entity = new Entity(token, object);
-    this.stack.add(entity);
-    return entity;
-  }
-
-  public void error(String bad_token)
-  {
-    throw new RuntimeException("bad_token");
-  }
-
-  public interface State
-  {
-
-    Token[] getTokens();
-
-    Rule[] getRules();
-  }
-
-  public interface Token
-  {
-
-    int ordinal();
-
-    public boolean matches(byte b);
-
-    public boolean runs();
-  }
-
-  public interface Rule
-  {
-
-    boolean apply(Parser<?> parser, Entity entity);
-  }
-
-  public interface Grammar
-  {
-
-    /**
-     * Should set the initial state.
-     *
-     * @param p
-     */
-    public void start(Parser p);
-
-    /**
-     * Should check the state of the stack, fail if bad, or return the final
-     * object if good.
-     *
-     * @param p
-     * @return
-     */
-    public Object end(Parser p);
-  }
-
-  /**
-   * Token or text.
-   */
-  public static class Entity
-  {
-
-    public Token token;
-    public Object value;
-
-    private Entity(Token token)
-    {
-      this.token = token;
-    }
-
-    private Entity(Token token, String value)
-    {
-      this.token = token;
-      this.value = value;
-    }
-
-    @Override
-    public String toString()
-    {
-      if (value == null)
-        return token.toString();
-      return value.toString();
-    }
-  }
-
-  //<editor-fold desc="common">
-  /**
-   * Generic matcher for multiple tokens on the stack.
-   */
-  abstract static class MatchRule implements Rule
-  {
-
-    Token[] pattern;
-
-    MatchRule(Token... tokens)
-    {
-      this.pattern = tokens;
-    }
-
-    @Override
-    public boolean apply(Parser parser, Entity entity)
-    {
-      LinkedList<Entity> stack = parser.stack;
-      int n = stack.size();
-      if (n < pattern.length)
-        return false;
-      ListIterator<Entity> iter = stack.listIterator(stack.size());
-      for (int i = 0; i < pattern.length; ++i)
-      {
-        if (!iter.hasPrevious())
-          return false;
-        Entity next = iter.previous();
-        if (next.token != pattern[pattern.length - i - 1])
-        {
-          return false;
-        }
-      }
-      execute(parser);
-      return true;
-    }
-
-    abstract public void execute(Parser parser);
-  }
-
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/html/entities.txt 1.6.0-1/native/java/org/jpype/html/entities.txt
--- 1.5.0-1/native/java/org/jpype/html/entities.txt	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/html/entities.txt	1970-01-01 00:00:00.000000000 +0000
@@ -1,256 +0,0 @@
-# Portions © International Organization for Standardization 1986:
-#     Permission to copy in any form is granted for use with
-#     conforming SGML systems and applications as defined in
-#     ISO 8879, provided this notice is included in all copies.
-nbsp     160
-iexcl    161
-cent     162
-pound    163
-curren   164
-yen      165
-brvbar   166
-sect     167
-uml      168
-copy     169
-ordf     170
-laquo    171
-not      172
-shy      173
-reg      174
-macr     175
-deg      176
-plusmn   177
-sup2     178
-sup3     179
-acute    180
-micro    181
-para     182
-middot   183
-cedil    184
-sup1     185
-ordm     186
-raquo    187
-frac14   188
-frac12   189
-frac34   190
-iquest   191
-Agrave   192
-Aacute   193
-Acirc    194
-Atilde   195
-Auml     196
-Aring    197
-AElig    198
-Ccedil   199
-Egrave   200
-Eacute   201
-Ecirc    202
-Euml     203
-Igrave   204
-Iacute   205
-Icirc    206
-Iuml     207
-ETH      208
-Ntilde   209
-Ograve   210
-Oacute   211
-Ocirc    212
-Otilde   213
-Ouml     214
-times    215
-Oslash   216
-Ugrave   217
-Uacute   218
-Ucirc    219
-Uuml     220
-Yacute   221
-THORN    222
-szlig    223
-agrave   224
-aacute   225
-acirc    226
-atilde   227
-auml     228
-aring    229
-aelig    230
-ccedil   231
-egrave   232
-eacute   233
-ecirc    234
-euml     235
-igrave   236
-iacute   237
-icirc    238
-iuml     239
-eth      240
-ntilde   241
-ograve   242
-oacute   243
-ocirc    244
-otilde   245
-ouml     246
-divide   247
-oslash   248
-ugrave   249
-uacute   250
-ucirc    251
-uuml     252
-yacute   253
-thorn    254
-yuml     255
-fnof     402
-Alpha    913
-Beta     914
-Gamma    915
-Delta    916
-Epsilon  917
-Zeta     918
-Eta      919
-Theta    920
-Iota     921
-Kappa    922
-Lambda   923
-Mu       924
-Nu       925
-Xi       926
-Omicron  927
-Pi       928
-Rho      929
-Sigma    931
-Tau      932
-Upsilon  933
-Phi      934
-Chi      935
-Psi      936
-Omega    937
-alpha    945
-beta     946
-gamma    947
-delta    948
-epsilon  949
-zeta     950
-eta      951
-theta    952
-iota     953
-kappa    954
-lambda   955
-mu       956
-nu       957
-xi       958
-omicron  959
-pi       960
-rho      961
-sigmaf   962
-sigma    963
-tau      964
-upsilon  965
-phi      966
-chi      967
-psi      968
-omega    969
-thetasym 977
-upsih    978
-piv      982
-bull     8226
-hellip   8230
-prime    8242
-Prime    8243
-oline    8254
-frasl    8260
-weierp   8472
-image    8465
-real     8476
-trade    8482
-alefsym  8501
-larr     8592
-uarr     8593
-rarr     8594
-darr     8595
-harr     8596
-crarr    8629
-lArr     8656
-uArr     8657
-rArr     8658
-dArr     8659
-hArr     8660
-forall   8704
-part     8706
-exist    8707
-empty    8709
-nabla    8711
-isin     8712
-notin    8713
-ni       8715
-prod     8719
-sum      8721
-minus    8722
-lowast   8727
-radic    8730
-prop     8733
-infin    8734
-ang      8736
-and      8743
-or       8744
-cap      8745
-cup      8746
-int      8747
-there4   8756
-sim      8764
-cong     8773
-asymp    8776
-ne       8800
-equiv    8801
-le       8804
-ge       8805
-sub      8834
-sup      8835
-nsub     8836
-sube     8838
-supe     8839
-oplus    8853
-otimes   8855
-perp     8869
-sdot     8901
-lceil    8968
-rceil    8969
-lfloor   8970
-rfloor   8971
-lang     9001
-rang     9002
-loz      9674
-spades   9824
-clubs    9827
-hearts   9829
-diams    9830
-quot     34
-amp      38
-lt       60
-gt       62
-OElig    338
-oelig    339
-Scaron   352
-scaron   353
-Yuml     376
-circ     710
-tilde    732
-ensp     8194
-emsp     8195
-thinsp   8201
-zwnj     8204
-zwj      8205
-lrm      8206
-rlm      8207
-ndash    8211
-mdash    8212
-lsquo    8216
-rsquo    8217
-sbquo    8218
-ldquo    8220
-rdquo    8221
-bdquo    8222
-dagger   8224
-Dagger   8225
-permil   8240
-lsaquo   8249
-rsaquo   8250
-euro     8364
\ No newline at end of file
diff -pruN 1.5.0-1/native/java/org/jpype/javadoc/DomUtilities.java 1.6.0-1/native/java/org/jpype/javadoc/DomUtilities.java
--- 1.5.0-1/native/java/org/jpype/javadoc/DomUtilities.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/javadoc/DomUtilities.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,263 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.javadoc;
-
-/**
- * The usual set of method required to work on DOM.
- *
- * DOM leaves a lot of basic stuff incomplete.
- */
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.w3c.dom.Text;
-
-public class DomUtilities
-{
-
-  /**
-   * Traverse all children in depth first order applying an operation.
-   *
-   * This is hardened against some level of DOM changes.
-   *
-   * @param node
-   * @param operator
-   * @param type
-   */
-  public static void traverseDFS(Node node, Consumer<Node> operator, short type)
-  {
-    Node child = node.getFirstChild();
-    while (child != null)
-    {
-      // Get a referent to what we are processing next in case the tree changes.
-      Node next = child.getNextSibling();
-
-      // Apply transforms to children first
-      if (child.getNodeType() == Node.ELEMENT_NODE)
-        traverseDFS(child, operator, type);
-
-      // Then process the outer element
-      if (child.getNodeType() == type)
-        operator.accept(child);
-
-      // Proceed
-      child = next;
-    }
-  }
-
-  /**
-   * Traverse the children of a node applying an operation.
-   *
-   * This is hardened against some level of DOM changes.
-   *
-   * @param node
-   * @param operator
-   * @param type
-   */
-  public static void traverseChildren(Node node, Consumer<Node> operator, short type)
-  {
-    Node child = node.getFirstChild();
-    while (child != null)
-    {
-      // Get the next node to process in case this one is changed or removed.
-      Node next = child.getNextSibling();
-      if (child.getNodeType() == type)
-        operator.accept(child);
-
-      // Proceed.
-      child = next;
-    }
-  }
-
-  /**
-   * Traverse all children in depth first order applying an operation.
-   *
-   * This is hardened against some level of DOM changes.
-   *
-   * @param node
-   * @param operator
-   * @param type
-   */
-  public static <T> void traverseDFS(Node node,
-          BiConsumer<Node, T> operator, short type, T data)
-  {
-    Node child = node.getFirstChild();
-    while (child != null)
-    {
-      // Get a referent to what we are processing next in case the tree changes.
-      Node next = child.getNextSibling();
-
-      // Apply transforms to children first
-      if (child.getNodeType() == Node.ELEMENT_NODE)
-        traverseDFS(child, operator, type, data);
-
-      // Then process the outer element
-      if (child.getNodeType() == type)
-        operator.accept(child, data);
-
-      // Proceed
-      child = next;
-    }
-  }
-
-  /**
-   * Traverse the children of a node applying an operation.
-   *
-   * This is hardened against some level of DOM changes.
-   *
-   * @param node
-   * @param operator
-   * @param type
-   */
-  public static <T> void traverseChildren(Node node, BiConsumer<Node, T> operator, short type, T data)
-  {
-    Node child = node.getFirstChild();
-    while (child != null)
-    {
-      // Get the next node to process in case this one is changed or removed.
-      Node next = child.getNextSibling();
-      if (child.getNodeType() == type)
-        operator.accept(child, data);
-
-      // Proceed.
-      child = next;
-    }
-  }
-
-  /**
-   * Remove all attributes from a node.
-   *
-   * @param node
-   */
-  public static void clearAttributes(Node node)
-  {
-    while (node.getAttributes().getLength() > 0)
-    {
-      Node att = node.getAttributes().item(0);
-      node.getAttributes().removeNamedItem(att.getNodeName());
-    }
-  }
-
-  /**
-   * Remove all children from a node.
-   *
-   * @param node
-   */
-  public static void clearChildren(Node node)
-  {
-    while (node.hasChildNodes())
-      node.removeChild(node.getFirstChild());
-  }
-
-  /**
-   * Determine if a block contains a new line.
-   *
-   * @param n
-   * @return
-   */
-  public static boolean containsNL(Node n)
-  {
-    Node child = n.getFirstChild();
-    while (child != null)
-    {
-      if (child.getNodeType() == Node.TEXT_NODE)
-      {
-        if (child.getNodeValue().contains("\n"))
-          return true;
-      }
-      child = child.getNextSibling();
-    }
-    return false;
-  }
-
-  /**
-   * Combine all text with neighbors in immediate children.
-   *
-   * @param node
-   */
-  public static void combineText(Node node)
-  {
-    // merge text nodes
-    Node child = node.getFirstChild();
-    while (child != null)
-    {
-      Node next = child.getNextSibling();
-      if (child.getNodeType() != Node.TEXT_NODE)
-      {
-        child = next;
-        continue;
-      }
-      if (next != null && next.getNodeType() == Node.TEXT_NODE)
-      {
-        child.setTextContent(child.getNodeValue() + next.getNodeValue());
-        child.getParentNode().removeChild(next);
-        continue;
-      }
-      child = next;
-    }
-  }
-
-  /**
-   * Merge the contents of a node with its parent.
-   *
-   * @param parent
-   * @param node
-   */
-  public static void mergeNode(Node parent, Node node)
-  {
-    while (node.hasChildNodes())
-    {
-      parent.insertBefore(node.getFirstChild(), node);
-    }
-    parent.removeChild(node);
-  }
-
-  static void transferContents(Node dest, Node source)
-  {
-    while (source.hasChildNodes())
-    {
-      dest.appendChild(source.getFirstChild());
-    }
-  }
-
-  /**
-   * Traverse a node and replaces all extra whitespace with one space.
-   *
-   * This should be applied to any element where white space is not relevant.
-   *
-   * @param node
-   */
-  public static void removeWhitespace(Node node)
-  {
-    // merge text nodes
-    NodeList children = node.getChildNodes();
-    for (int i = 0; i < children.getLength(); ++i)
-    {
-      Node child = children.item(i);
-      if (child.getNodeType() != Node.TEXT_NODE)
-        continue;
-      Text t = (Text) child;
-      String c = t.getNodeValue();
-      if (c != null)
-      {
-        c = c.replaceAll("\\s+", " ");
-        t.setNodeValue(c);
-      }
-    }
-  }
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/javadoc/Javadoc.java 1.6.0-1/native/java/org/jpype/javadoc/Javadoc.java
--- 1.5.0-1/native/java/org/jpype/javadoc/Javadoc.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/javadoc/Javadoc.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,37 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.javadoc;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.w3c.dom.Node;
-
-public class Javadoc
-{
-
-  public String description;
-  public String ctors;
-  public Map<String, String> methods = new HashMap<>();
-  public Map<String, String> fields = new HashMap<>();
-
-  // These will be removed once debugging is complete
-  public Node descriptionNode;
-  public List<Node> ctorsNode;
-  public List<Node> methodNodes;
-  public List<Node> innerNode;
-  public List<Node> fieldNodes;
-}
diff -pruN 1.5.0-1/native/java/org/jpype/javadoc/JavadocException.java 1.6.0-1/native/java/org/jpype/javadoc/JavadocException.java
--- 1.5.0-1/native/java/org/jpype/javadoc/JavadocException.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/javadoc/JavadocException.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,30 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.javadoc;
-
-import org.w3c.dom.Node;
-
-public class JavadocException extends RuntimeException
-{
-
-  public Node node;
-
-  public JavadocException(Node node, Throwable th)
-  {
-    super(th);
-    this.node = node;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/javadoc/JavadocExtractor.java 1.6.0-1/native/java/org/jpype/javadoc/JavadocExtractor.java
--- 1.5.0-1/native/java/org/jpype/javadoc/JavadocExtractor.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/javadoc/JavadocExtractor.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,239 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.javadoc;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-import org.jpype.html.Html;
-import org.jpype.html.Parser;
-import org.w3c.dom.Document;
-import org.w3c.dom.DocumentFragment;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-public class JavadocExtractor
-{
-
-  static final JavadocTransformer transformer = new JavadocTransformer();
-  static public boolean transform = true;
-  static public boolean render = true;
-  static public boolean failures = false;
-
-  /**
-   * Search the classpath for documentation.
-   *
-   * @param cls
-   * @return
-   */
-  public static Javadoc getDocumentation(Class cls)
-  {
-    try
-    {
-      try (InputStream is = getDocumentationAsStream(cls))
-      {
-        if (is != null)
-        {
-          Parser<Document> parser = Html.newParser();
-          return extractDocument(cls, parser.parse(is));
-        }
-      }
-    } catch (Exception ex)
-    {
-      System.err.println("Failed to extract javadoc for " + cls);
-      if (failures)
-        throw new RuntimeException(ex);
-    }
-    return null;
-  }
-
-  public static InputStream getDocumentationAsStream(Class cls)
-  {
-    InputStream is = null;
-    String name = cls.getName().replace('.', '/') + ".html";
-    ClassLoader cl = ClassLoader.getSystemClassLoader();
-
-    // Search the regular class path.
-    is = cl.getResourceAsStream(name);
-    if (is != null)
-      return is;
-
-    // Search for api documents
-    String name1 = "docs/api/" + name;
-    is = cl.getResourceAsStream(name1);
-    if (is != null)
-      return is;
-
-    // If we are dealing with Java 9+, the doc tree is different
-    try
-    {
-      Method meth = Class.class.getMethod("getModule");
-      String module = meth.invoke(cls).toString().substring(7);
-      String name2 = "docs/api/" + module + "/" + name;
-      is = cl.getResourceAsStream(name2);
-      if (is != null)
-        return is;
-    } catch (NoSuchMethodException | SecurityException | IllegalAccessException
-            | IllegalArgumentException | InvocationTargetException ex)
-    {
-      // do nothing if we are not JDK 9+
-    }
-    return null;
-  }
-
-  /**
-   * Extract the documentation from the dom.
-   *
-   * @param cls is the class being processed.
-   * @param doc is the DOM holding the javadoc.
-   * @return
-   */
-  public static Javadoc extractDocument(Class cls, Document doc)
-  {
-    JavadocRenderer renderer = new JavadocRenderer();
-    try
-    {
-      Javadoc documentation = new Javadoc();
-      XPath xPath = XPathFactory.newInstance().newXPath();
-      // Javadoc 8-13
-      Node n = (Node) xPath.compile("//div[@class='description']/ul/li").evaluate(doc, XPathConstants.NODE);
-      if (n == null)  // Javadoc 14+
-        n = (Node) xPath.compile("//section[@class='description']").evaluate(doc, XPathConstants.NODE);
-      Node description = toFragment(n);
-      if (description != null)
-      {
-        documentation.descriptionNode = description;
-        if (transform)
-          transformer.transformDescription(cls, description);
-        if (render)
-          documentation.description = renderer.render(description);
-      }
-
-      Node ctorRoot = ((Node) xPath.compile("//li/a[@name='constructor.detail' or @id='constructor.detail']")
-              .evaluate(doc, XPathConstants.NODE));
-      if (ctorRoot != null)
-      {
-        List<Node> set = convertNodes((NodeList) xPath.compile("./ul/li")
-                .evaluate(ctorRoot.getParentNode(), XPathConstants.NODESET));
-        documentation.ctorsNode = set;
-        StringBuilder sb = new StringBuilder();
-        for (Node ctor : set)
-        {
-          if (transform)
-            transformer.transformMember(cls, ctor);
-          if (render)
-            sb.append(renderer.render(ctor));
-        }
-        documentation.ctors = sb.toString();
-      }
-
-      Node methodRoot = ((Node) xPath.compile("//li/a[@name='method.detail' or  @id='method.detail']")
-              .evaluate(doc, XPathConstants.NODE));
-      if (methodRoot != null)
-      {
-        List<Node> set = convertNodes((NodeList) xPath.compile("./ul/li")
-                .evaluate(methodRoot.getParentNode(), XPathConstants.NODESET));
-        documentation.methodNodes = set;
-        for (Node method : set)
-        {
-          if (transform)
-            transformer.transformMember(cls, method);
-          if (render)
-          {
-            String str = renderer.render(method);
-            String name = renderer.memberName;
-            if (documentation.methods.containsKey(name))
-            {
-              String old = documentation.methods.get(name);
-              str = old + str;
-            }
-            documentation.methods.put(name, str);
-          }
-        }
-      }
-
-//      Node inner = (Node) xPath.compile("//li/a[@name='nested_class_summary']").evaluate(doc, XPathConstants.NODE);
-//      if (inner != nullList)
-//      {
-//        NodeList set = (NodeList) xPath.compile("./ul/li").evaluate(inner.getParentNode(), XPathConstants.NODESET);
-//        documentation.innerNode = convertNodes(set);
-//      }
-      Node fieldRoot = ((Node) xPath.compile("//li/a[@name='field.detail' or @id='field.detail']")
-              .evaluate(doc, XPathConstants.NODE));
-      if (fieldRoot != null)
-      {
-        List<Node> set = convertNodes((NodeList) xPath.compile("./ul/li")
-                .evaluate(fieldRoot.getParentNode(), XPathConstants.NODESET));
-        documentation.fieldNodes = set;
-        for (Node field : set)
-        {
-          if (transform)
-            transformer.transformMember(cls, field);
-          if (render)
-          {
-            String str = renderer.render(field);
-            String name = renderer.memberName;
-            documentation.fields.put(name, str);
-          }
-        }
-      }
-
-      return documentation;
-    } catch (IOException | XPathExpressionException ex)
-    {
-      throw new RuntimeException(ex);
-//      return null;
-    }
-  }
-
-  private static List<Node> convertNodes(NodeList nl) throws IOException
-  {
-    List<Node> out = new ArrayList<>();
-    for (int i = 0; i < nl.getLength(); ++i)
-    {
-      out.add(toFragment(nl.item(i)));
-    }
-    return out;
-  }
-
-  /**
-   * Convert a portion of the document into a fragment.
-   *
-   * @param node
-   * @return
-   */
-  public static Node toFragment(Node node)
-  {
-    Document doc = node.getOwnerDocument();
-    DocumentFragment out = doc.createDocumentFragment();
-    while (node.hasChildNodes())
-    {
-      out.appendChild(node.getFirstChild());
-    }
-    if (out.getFirstChild() != null && out.getFirstChild().getNodeType() == Node.TEXT_NODE)
-      out.removeChild(out.getFirstChild());
-    if (out.getLastChild() != null && out.getLastChild().getNodeType() == Node.TEXT_NODE)
-      out.removeChild(out.getLastChild());
-    return out;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/javadoc/JavadocRenderer.java 1.6.0-1/native/java/org/jpype/javadoc/JavadocRenderer.java
--- 1.5.0-1/native/java/org/jpype/javadoc/JavadocRenderer.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/javadoc/JavadocRenderer.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,411 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.javadoc;
-
-import java.nio.charset.StandardCharsets;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import java.util.Map;
-import java.util.HashMap;
-
-/**
- * Render a node as ReStructured Text.
- *
- * @author nelson85
- */
-public class JavadocRenderer
-{
-
-  static final int WIDTH = 120;
-  public StringBuilder assembly;
-  public int indentLevel = 0;
-  String memberName;
-
-  public String render(Node node)
-  {
-    try
-    {
-      indentLevel = 0;
-      assembly = new StringBuilder();
-      DomUtilities.traverseChildren(node, this::renderSections, Node.ELEMENT_NODE);
-      return assembly.toString();
-    } catch (Exception ex)
-    {
-      throw new JavadocException(node, ex);
-    }
-  }
-
-  /**
-   * Render the dom into restructured text.
-   *
-   * @param node
-   */
-  void renderSections(Node node)
-  {
-    Element e = (Element) node;
-    String name = e.getTagName();
-    if (name.equals("title"))
-    {
-      this.memberName = node.getTextContent();
-      return;
-    }
-    if (name.equals("signature"))
-    {
-      assembly.append(node.getTextContent())
-              .append("\n\n");
-      indentLevel += 4;
-      return;
-    }
-    if (name.equals("description"))
-    {
-      renderText(node, true, true);
-      return;
-    }
-    if (name.equals("details"))
-    {
-      DomUtilities.traverseChildren(node, this::renderDetails, Node.ELEMENT_NODE);
-      assembly.append("\n");
-      return;
-    }
-  }
-
-  final static Map<String, String> SECTIONS = new HashMap<>();
-
-  static
-  {
-    // Decide what sections to render
-    SECTIONS.put("returns", "Returns:");
-    SECTIONS.put("see", "See also:");
-    SECTIONS.put("since", "Since:");
-    SECTIONS.put("jls", "See Java\u2122 specification:");
-    SECTIONS.put("overrides", "Overrides:");
-    SECTIONS.put("specified", "Specified by:");
-    SECTIONS.put("version", null);
-    SECTIONS.put("typeparams", null);
-    SECTIONS.put("author", null);
-    SECTIONS.put("see", "Also see:");
-    SECTIONS.put("api_note", "API Note:");
-    SECTIONS.put("requirements", "Implementation Requirements:");
-    SECTIONS.put("impl_note", "Implementation Note:");
-  }
-
-  void renderDetails(Node node)
-  {
-    String name = node.getNodeName();
-    if (name.equals("parameters"))
-    {
-      assembly.append('\n')
-              .append(indentation(this.indentLevel))
-              .append("Parameters:\n");
-      indentLevel += 4;
-      DomUtilities.traverseChildren(node, this::renderParameter, Node.ELEMENT_NODE);
-      indentLevel -= 4;
-    } else if (name.equals("throws"))
-    {
-      assembly.append('\n')
-              .append(indentation(this.indentLevel))
-              .append("Raises:\n");
-      indentLevel += 4;
-      DomUtilities.traverseChildren(node, this::renderThrow, Node.ELEMENT_NODE);
-      indentLevel -= 4;
-    } else if (SECTIONS.containsKey(name))
-    {
-      String title = SECTIONS.get(name);
-      if (title == null)
-        return;
-      assembly.append('\n')
-              .append(indentation(this.indentLevel))
-              .append(title).append('\n');
-      indentLevel += 4;
-      renderText(node, true, true);
-      indentLevel -= 4;
-    } else
-    {
-      System.err.println("Need renderer for section " + name);
-    }
-  }
-
-  void renderParameter(Node node)
-  {
-    Element elem = (Element) node;
-    assembly.append(indentation(this.indentLevel))
-            //            .append("  ")
-            .append(elem.getAttribute("name"))
-            .append(" (")
-            .append(elem.getAttribute("type"))
-            .append("): ");
-    indentLevel += 4;
-    renderText(node, false, true);
-    indentLevel -= 4;
-  }
-
-  void renderThrow(Node node)
-  {
-    Element elem = (Element) node;
-    assembly.append(indentation(this.indentLevel))
-            //.append("  ")
-            .append(elem.getAttribute("name"))
-            .append(": ");
-    indentLevel += 4;
-    renderText(node, false, true);
-    indentLevel -= 4;
-  }
-
-  /**
-   * Render a paragraph or paragraph like element.
-   *
-   */
-  void renderText(Node node, boolean startIndent, boolean trailingNL)
-  {
-    DomUtilities.combineText(node);
-    DomUtilities.removeWhitespace(node);
-    Node child = node.getFirstChild();
-    for (; child != null; child = child.getNextSibling())
-    {
-      if (child.getNodeType() == Node.TEXT_NODE)
-      {
-        String value = child.getNodeValue();
-        if (value == null)
-          continue;
-        value = value.trim();
-        if (value.isEmpty())
-          continue;
-        formatWidth(assembly, value, WIDTH, indentLevel, startIndent);
-        if (trailingNL)
-          assembly.append("\n");
-        continue;
-      }
-      if (child.getNodeType() != Node.ELEMENT_NODE)
-        continue;
-      Element element = (Element) child;
-      String name = element.getTagName();
-      if (name.equals("p"))
-      {
-        assembly.append("\n");
-        renderText(element, true, true);
-      } else if (name.equals("div"))
-      {
-        renderText(element, true, true);
-      } else if (name.equals("center"))
-      {
-        renderText(element, true, true);
-      } else if (name.equals("br"))
-      {
-        assembly.append("\n\n");
-      } else if (name.equals("ul"))
-      {
-        renderUnordered(element);
-      } else if (name.equals("ol"))
-      {
-        renderOrdered(element);
-      } else if (name.equals("img"))
-      {
-        // punt
-      } else if (name.equals("table"))
-      {
-        // punt
-      } else if (name.equals("hr"))
-      {
-        // punt
-      } else if (name.equals("dl"))
-      {
-        renderDefinitions(element);
-      } else if (name.equals("codeblock"))
-      {
-        renderCodeBlock(element);
-      } else if (name.equals("blockquote"))
-      {
-        renderBlockQuote(element);
-      } else if (name.equals("h1"))
-      {
-        renderHeader(element);
-      } else if (name.equals("h2"))
-      {
-        renderHeader(element);
-      } else if (name.equals("h3"))
-      {
-        renderHeader(element);
-      } else if (name.equals("h4"))
-      {
-        renderHeader(element);
-      } else if (name.equals("h5"))
-      {
-        renderHeader(element);
-      } else
-      {
-        throw new RuntimeException("Need render for " + name);
-      }
-    }
-  }
-
-  void renderHeader(Node node)
-  {
-    assembly.append("\n");
-    renderText(node, true, true);
-    assembly.append(new String(new byte[node.getTextContent().length()]).replace('\0', '-'))
-            .append("\n\n");
-  }
-
-  void renderBlockQuote(Node node)
-  {
-    indentLevel += 4;
-    renderText(node, true, true);
-    indentLevel -= 4;
-  }
-
-  /**
-   * Render an unordered list.
-   *
-   * @param node
-   */
-  void renderOrdered(Node node)
-  {
-    indentLevel += 4;
-    assembly.append("\n");
-    Node child = node.getFirstChild();
-    int num = 1;
-    for (; child != null; child = child.getNextSibling())
-    {
-      if (child.getNodeType() != Node.ELEMENT_NODE)
-        continue;
-      if (child.getNodeName().equals("li"))
-      {
-        assembly.append(indentation(indentLevel - 2))
-                .append(String.format("%d.  ", num++));
-        renderText(child, false, true);
-      } else
-        throw new RuntimeException("Bad node " + child.getNodeName() + " in UL");
-    }
-    indentLevel -= 4;
-    assembly.append("\n");
-  }
-
-  /**
-   * Render an unordered list.
-   *
-   * @param node
-   */
-  void renderUnordered(Node node)
-  {
-    indentLevel += 4;
-    assembly.append("\n");
-    Node child = node.getFirstChild();
-    for (; child != null; child = child.getNextSibling())
-    {
-      if (child.getNodeType() != Node.ELEMENT_NODE)
-        continue;
-      if (child.getNodeName().equals("li"))
-      {
-        assembly.append(indentation(indentLevel - 4))
-                .append("  - ");
-        renderText(child, false, true);
-      } else
-        throw new RuntimeException("Bad node " + child.getNodeName() + " in UL");
-    }
-    indentLevel -= 4;
-    assembly.append("\n");
-  }
-
-  /**
-   * Render a definition list.
-   *
-   * @param node
-   */
-  void renderDefinitions(Node node)
-  {
-    Node child = node.getFirstChild();
-    for (; child != null; child = child.getNextSibling())
-    {
-      if (child.getNodeType() != Node.ELEMENT_NODE)
-        continue;
-      String name = child.getNodeName();
-      if (name.equals("dt"))
-      {
-        assembly.append("\n");
-        renderText(child, true, true);
-      } else if (name.equals("dd"))
-      {
-        assembly.append(indentation(indentLevel));
-        indentLevel += 4;
-        assembly.append("  ");
-        renderText(child, false, true);
-        indentLevel -= 4;
-      } else
-        throw new RuntimeException("Bad node " + name + " in DL");
-    }
-    assembly.append("\n");
-  }
-
-  void renderCodeBlock(Node node)
-  {
-    String indent = indentation(indentLevel);
-    assembly.append("\n")
-            .append(indent)
-            .append(".. code-block: java\n");
-    String text = node.getTextContent();
-    if (text.charAt(0) != '\n')
-      text = "\n" + text;
-    text = text.replaceAll("\n", "\n" + indent);
-    assembly.append(indent).append(text).append("\n");
-  }
-
-//<editor-fold desc="text-utilities" defaultstate="collapsed">
-  static final String SPACING = new String(new byte[40]).replace('\0', ' ');
-
-  static String indentation(int level)
-  {
-    if (level > 40)
-      return new String();
-    return SPACING.substring(0, level);
-  }
-
-  static void formatWidth(StringBuilder sb, String s, int width, int indent, boolean flag)
-  {
-    String sindent = indentation(indent);
-    s = s.replaceAll("\\s+", " ").trim();
-    if (s.length() < width)
-    {
-      if (flag)
-        sb.append(sindent);
-      sb.append(s);
-      return;
-    }
-    byte[] b = s.getBytes(StandardCharsets.UTF_8);
-    int start = 0;
-    int prev = 0;
-    int l = b.length;
-    int next = 0;
-    while (next < l)
-    {
-      for (next = prev + 1; next < l; ++next)
-        if (b[next] == ' ')
-          break;
-      if (next - start > width)
-      {
-        b[prev] = '\n';
-        if (flag)
-          sb.append(sindent);
-        flag = true;
-        sb.append(new String(b, start, prev - start + 1));
-        start = prev + 1;
-      }
-      prev = next;
-    }
-    sb.append(sindent);
-    sb.append(new String(b, start, l - start));
-  }
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/javadoc/JavadocTransformer.java 1.6.0-1/native/java/org/jpype/javadoc/JavadocTransformer.java
--- 1.5.0-1/native/java/org/jpype/javadoc/JavadocTransformer.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/javadoc/JavadocTransformer.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,467 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.javadoc;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.jpype.html.Html;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.Text;
-
-/**
- * Transform the document into a form suitable for ReStructured Text.
- *
- * The goal of this is to convert all inline markup into rst and leave markup by
- * section, paragraph to be used by the renderer.
- *
- * @author nelson85
- */
-public class JavadocTransformer
-{
-
-  final static Pattern ARGS_PATTERN = Pattern.compile(".*\\((.*)\\).*");
-
-  public Node transformDescription(Class cls, Node node)
-  {
-    try
-    {
-      Workspace ws = new Workspace(cls);
-      DomUtilities.traverseDFS(node, this::fixEntities, Node.TEXT_NODE);
-      DomUtilities.traverseChildren(node, this::handleDescription, Node.ELEMENT_NODE, ws);
-      DomUtilities.traverseDFS(node, this::pass1, Node.ELEMENT_NODE, ws);
-      return node;
-    } catch (Exception ex)
-    {
-      throw new JavadocException(node, ex);
-    }
-  }
-
-  /**
-   * Convert a Javadoc member description into markup for ReStructure Text
-   * rendering.
-   *
-   * This will mutilate the node.
-   *
-   * @param node
-   */
-  public Node transformMember(Class cls, Node node)
-  {
-    try
-    {
-      Workspace ws = new Workspace(cls);
-      DomUtilities.traverseDFS(node, this::fixEntities, Node.TEXT_NODE);
-      DomUtilities.traverseChildren(node, this::handleMembers, Node.ELEMENT_NODE, ws);
-      DomUtilities.traverseDFS(node, this::pass1, Node.ELEMENT_NODE, ws);
-      return node;
-    } catch (Exception ex)
-    {
-      throw new JavadocException(node, ex);
-    }
-
-  }
-
-//<editor-fold desc="members" defaultstate="description">
-  void handleDescription(Node node, Workspace data)
-  {
-    Element e = (Element) node;
-    String name = e.getTagName();
-    Document doc = e.getOwnerDocument();
-    Node parent = node.getParentNode();
-    if (name.equals("dl") && !data.hr)
-    {
-      parent.removeChild(node);
-    } else if (name.equals("br"))
-    {
-      parent.removeChild(node);
-    } else if (name.equals("hr"))
-    {
-      data.hr = true;
-      parent.removeChild(node);
-    } else if (name.equals("pre"))
-    {
-      DomUtilities.removeWhitespace(node);
-      doc.renameNode(node, null, "signature");
-    } else if (name.equals("div"))
-    {
-      doc.renameNode(node, null, "description");
-      DomUtilities.clearAttributes(node);
-    } else if (name.equals("dl"))
-    {
-      doc.renameNode(node, null, "details");
-      DomUtilities.traverseChildren(node, this::handleDetails,
-              Node.ELEMENT_NODE, data);
-    } else
-    {
-      throw new RuntimeException("Unknown item at top level " + name);
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="members" defaultstate="collapsed">
-  void handleMembers(Node node, Workspace ws)
-  {
-    Element e = (Element) node;
-    String name = e.getTagName();
-    Document doc = e.getOwnerDocument();
-
-    if (name.equals("h4"))
-    {
-      doc.renameNode(node, null, "title");
-    } else if (name.equals("pre"))
-    {
-      doc.renameNode(node, null, "signature");
-      DomUtilities.traverseDFS(node, this::pass1, Node.ELEMENT_NODE, ws);
-      // We need to get the types from here for the parameters
-      DomUtilities.removeWhitespace(node);
-      String content = node.getTextContent();
-      Matcher m = ARGS_PATTERN.matcher(content);
-      if (m.matches())
-      {
-        LinkedList<String> types = new LinkedList<>();
-        for (String s : m.group(1).split(", "))
-        {
-          String[] parts = s.split("\u00a0", 2);
-          types.add(parts[0]);
-        }
-        ws.types = types;
-      }
-    } else if (name.equals("div"))
-    {
-      doc.renameNode(node, null, "description");
-      DomUtilities.clearAttributes(node);
-    } else if (name.equals("dl"))
-    {
-      doc.renameNode(node, null, "details");
-      DomUtilities.traverseChildren(node, this::handleDetails,
-              Node.ELEMENT_NODE, ws);
-    } else
-    {
-      throw new RuntimeException("Unknown item at top level " + name);
-    }
-  }
-
-  public final static Map<String, String> DETAIL_SECTIONS;
-
-  static
-  {
-    DETAIL_SECTIONS = new HashMap<>();
-    Map<String, String> ds = DETAIL_SECTIONS;
-    ds.put("Author:", "author");
-    ds.put("Since:", "since");
-    ds.put("Parameters:", "parameters");
-    ds.put("Returns:", "returns");
-    ds.put("Overrides:", "overrides");
-    ds.put("See Also:", "see");
-    ds.put("API Note:", "api_note");
-    ds.put("Version:", "version");
-    ds.put("Type Parameters:", "typeparams");
-    ds.put("Specified by:", "specified");
-    ds.put("Throws:", "throws");
-    ds.put("Implementation Requirements:", "requirements");
-    ds.put("Implementation Note:", "impl_note");
-  }
-
-  void handleDetails(Node node, Workspace ws)
-  {
-    Element e = (Element) node;
-    String name = e.getTagName();
-    Document doc = e.getOwnerDocument();
-    Node parent = e.getParentNode();
-    if (name.equals("dt"))
-    {
-      String key = node.getTextContent().trim();
-      if (DETAIL_SECTIONS.containsKey(key))
-      {
-        doc.renameNode(node, null, DETAIL_SECTIONS.get(key));
-      } else if (key.startsWith("See "))
-      {
-        doc.renameNode(node, null, "jls");
-      } else
-      {
-        System.err.println("Bad detail key '" + key + "'");
-      }
-      ws.key = node.getNodeName();
-      ws.section = node;
-      DomUtilities.clearChildren(ws.section);
-    }
-    if (name.equals("dd"))
-    {
-      if (ws.key.equals("parameters"))
-      {
-        Node first = node.getFirstChild(); // First is <code>varname</code>
-        Node second = first.getNextSibling(); // Second is " - desc"
-        Element elem = doc.createElement("parameter");
-        elem.setAttribute("name", first.getTextContent());
-        elem.setAttribute("type", ws.types.removeFirst());
-        String value = second.getNodeValue();
-        second.setNodeValue(value.substring(3)); // Remove " - "
-        node.removeChild(first);
-        DomUtilities.transferContents(elem, node);
-        ws.section.appendChild(elem);
-        parent.removeChild(node);
-      } else if (ws.key.equals("throws"))
-      {
-        Node first = node.getFirstChild(); // First is <code><a>exc</a></code>
-        Node second = first.getNextSibling(); // Second is " - desc"
-        Element elem = doc.createElement("exception");
-        DomUtilities.traverseDFS(first, this::pass1, Node.ELEMENT_NODE, ws);
-        elem.setAttribute("name", first.getTextContent());
-        if (second != null)
-        {
-          String value = second.getNodeValue();
-          second.setNodeValue(value.substring(3)); // Remove " - "
-        }
-        node.removeChild(first);
-        DomUtilities.transferContents(elem, node);
-        ws.section.appendChild(elem);
-        parent.removeChild(node);
-      } else
-      {
-        // Normalize the node and transfer it to the section
-        DomUtilities.transferContents(ws.section, node);
-        DomUtilities.traverseDFS(ws.section, this::pass1, Node.ELEMENT_NODE, ws);
-        DomUtilities.removeWhitespace(ws.section);
-        parent.removeChild(node);
-        return;
-      }
-    }
-  }
-
-  static class Workspace
-  {
-
-    private final Class cls;
-    boolean hr = false;
-    String key;
-    Node section;
-    private LinkedList<String> types;
-
-    Workspace(Class cls)
-    {
-      this.cls = cls;
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="contents" defaultstate="collapsed">
-  /**
-   * Convert any html entities found the text.
-   *
-   * @param node
-   */
-  void fixEntities(Node node)
-  {
-    Text n = (Text) node;
-    String s = Html.decode(n.getTextContent());
-    n.setTextContent(s);
-  }
-
-  // This corresponds to any simple inline markup transformation.
-  final static String[] PASS1 = new String[]
-  {
-    "cite", "\"%s\"",
-    "code", ":code:`%s`",
-    "pre", ":code:`%s`",
-    "tt", "``%s``",
-    "i", "*%s*",
-    "em", "*%s*",
-    "strong", "**%s**",
-    "b", "**%s**",
-    "sup", " :sup:`%s` ",
-    "sub", " :sub:`%s` ",
-    "small", ":sub:`%s`",
-    "span", "%s",
-    "nop", "%s",
-    "font", "%s",
-    "var", "*%s*",
-  };
-
-  /**
-   * Get a bunch of simple substitutions.
-   *
-   * @param node
-   */
-  void pass1(Node node, Workspace ws)
-  {
-    Element e = (Element) node;
-    String name = e.getTagName();
-    Document doc = e.getOwnerDocument();
-    Node parent = e.getParentNode();
-    if (parent == null)
-      return;
-
-    // Pre is something used to mark code.
-    if (name.equals("pre"))
-    {
-      if (DomUtilities.containsNL(e))
-      {
-        doc.renameNode(node, null, "codeblock");
-        name = "codeblock";
-      } else
-      {
-        doc.renameNode(node, null, "code");
-        name = "code";
-      }
-    }
-
-    //  <pre><code> is a common javadoc idiom.
-    if (name.equals("code") && (parent.getNodeName().equals("pre")
-            || parent.getNodeName().equals("blockquote")))
-    {
-      doc.renameNode(parent, null, "codeblock");
-      DomUtilities.mergeNode(parent, node);
-      return;
-    }
-
-    if (name.equals("codeblock") && parent.getNodeName().equals("pre"))
-    {
-      if (DomUtilities.containsNL(node))
-      {
-        doc.renameNode(parent, null, "codeblock");
-      } else
-        doc.renameNode(parent, null, "code");
-      DomUtilities.mergeNode(parent, node);
-      return;
-    }
-
-    // <a ...><code> is used to reference members and classes.
-    if (name.equals("code") && parent.getNodeName().equals("a"))
-    {
-      Element eparent = (Element) parent;
-      String href = this.toReference(ws, eparent.getAttribute("href"));
-      DomUtilities.clearChildren(parent);
-      parent.appendChild(doc.createTextNode(href));
-      return;
-    }
-
-    // <code><a> is also used.
-    if (name.equals("a") && parent.getNodeName().equals("code"))
-    {
-      String href = this.toReference(ws, e.getAttribute("href"));
-      DomUtilities.clearChildren(parent);
-      doc.renameNode(parent, null, "nop");
-      parent.appendChild(doc.createTextNode(href));
-      return;
-    }
-
-    // <a> by itself is usually external references.
-    if (name.equals("a"))
-    {
-      String href = e.getAttribute("href");
-      if (href.startsWith("http:") || href.startsWith("shttp:"))
-      {
-        String content = node.getTextContent();
-        content = String.format("`%s <%s>`", content, href);
-        parent.replaceChild(doc.createTextNode(content), node);
-        return;
-      }
-      href = this.toReference(ws, href);
-      if (href == null)
-        parent.replaceChild(doc.createTextNode(node.getTextContent()), node);
-      else
-        parent.replaceChild(doc.createTextNode(href), node);
-      return;
-    }
-
-    // Apply inline transformations.
-    for (int i = 0; i < PASS1.length; i += 2)
-    {
-      if (name.equals(PASS1[i]))
-      {
-        String s2 = e.getTextContent();
-        if (s2 == null)
-          e.getParentNode().removeChild(e);
-        else
-          e.getParentNode().replaceChild(doc.createTextNode(String.format(PASS1[i + 1], s2.trim())), e);
-        return;
-      }
-    }
-
-  }
-
-  /**
-   * Convert a reference into method or class.
-   *
-   * This currently only deals with local links. External links are elsewhere.
-   *
-   * @param ws
-   * @param href
-   * @return
-   */
-  public String toReference(Workspace ws, String href)
-  {
-    try
-    {
-      Path p = Paths.get(ws.cls.getName().replace('.', '/'));
-      if (href.startsWith("#"))
-      {
-        // technically it may be a field, but we can't tell currently.
-        Path q = p.resolve(href.substring(1).trim())
-                .normalize();
-        if (q.startsWith(".."))
-          q = q.subpath(2, q.getNameCount());
-        String r = q.toString()
-                .replace('/', '.')
-                .replace('\\', '.')
-                .replaceAll("\\(.*\\)", "")
-                .replaceAll("-.*", "");
-        return String.format(":meth:`~%s`", r);
-      } else if (href.contains("#"))
-      {
-        // technically it may be a field, but we can't tell currently.
-        Path q = p.getParent()
-                .resolve(href.trim())
-                .normalize();
-        if (q.startsWith(".."))
-          q = q.subpath(2, q.getNameCount());
-        String r = q.toString()
-                .replace('/', '.')
-                .replace('\\', '.')
-                .replaceAll("\\(.*\\)", "")
-                .replaceAll("-.*", "")
-                .replaceAll(".html#", ".");
-        return String.format(":meth:`~%s`", r);
-      } else
-      {
-        Path q = p.getParent()
-                .resolve(href.trim())
-                .normalize();
-        if (q.startsWith(".."))
-          q = q.subpath(2, q.getNameCount());
-        String r = q.toString()
-                .replace('/', '.')
-                .replace('\\', '.')
-                .replaceAll("-.*", "")
-                .replaceAll(".html", "");
-        return String.format(":class:`~%s`", r);
-      }
-    } catch (Exception ex)
-    {
-      // There is a lot of ways this can go wrong.  If all else fails
-      // return null so that we can just remove the hyperlink.
-      return null;
-    }
-  }
-
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/manager/ClassDescriptor.java 1.6.0-1/native/java/org/jpype/manager/ClassDescriptor.java
--- 1.5.0-1/native/java/org/jpype/manager/ClassDescriptor.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/manager/ClassDescriptor.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,71 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.manager;
-
-import java.lang.reflect.Executable;
-import java.lang.reflect.Method;
-
-/**
- * A list of resources associated with this class.
- * <p>
- * These can be accessed within JPype using the org.jpype.manager.TypeManager.
- * <p>
- */
-public class ClassDescriptor
-{
-
-  public Class<?> cls;
-  /**
-   * JPClass pointer for this class.
-   */
-  public long classPtr;
-  /**
-   * JPMethodDispatch for the constructor.
-   */
-  public long constructorDispatch;
-  public long[] constructors;
-  /**
-   * Resources needed by the class
-   */
-  public long[] methodDispatch;
-  public Executable[] methodIndex;
-  public long[] methods;
-  public int methodCounter = 0;
-  public long[] fields;
-  public long anonymous;
-  public int functional_interface_parameter_count; 
-
-  ClassDescriptor(Class cls, long classPtr, Method method)
-  {
-    this.cls = cls;
-    this.classPtr = classPtr;
-    if (this.classPtr == 0)
-      throw new NullPointerException("Class pointer is null for " + cls);
-    if (method != null)
-      functional_interface_parameter_count = method.getParameterCount();
-    else
-      functional_interface_parameter_count = -1;
-  }
-
-  long getMethod(Method requestedMethod)
-  {
-    for (int i = 0; i < methods.length; ++i)
-      if (this.methodIndex[i].equals(requestedMethod))
-        return this.methods[i];
-    return 0;
-
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/manager/MethodResolution.java 1.6.0-1/native/java/org/jpype/manager/MethodResolution.java
--- 1.5.0-1/native/java/org/jpype/manager/MethodResolution.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/manager/MethodResolution.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,287 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.manager;
-
-import java.lang.reflect.Executable;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Sort out which methods hide other methods.
- * <p>
- * When resolving method overloads there may be times in which more than one
- * overload applies. JPype requires that the methods appear in order from most
- * to least specific. And each method overload requires a list of methods that
- * are more general. If two or more methods match and one is not more specific
- * than the other.
- *
- * @author nelson85
- */
-public class MethodResolution
-{
-
-  long ptr = 0;
-  boolean covered = false;
-  Executable executable;
-  List<MethodResolution> children = new ArrayList<>();
-
-  MethodResolution(Executable method)
-  {
-    this.executable = method;
-  }
-
-  private boolean isCovered()
-  {
-    for (MethodResolution ov : this.children)
-    {
-      if (!ov.covered)
-        return false;
-    }
-    covered = true;
-    return true;
-  }
-
-  /**
-   * Order methods from least to most specific.
-   *
-   * @param <T>
-   * @param methods
-   * @return
-   */
-  public static <T extends Executable>
-          List<MethodResolution> sortMethods(List<T> methods)
-  {
-    // Create a method resolution for each method
-    LinkedList<MethodResolution> unsorted = new LinkedList<>();
-    for (T m1 : methods)
-    {
-      unsorted.add(new MethodResolution(m1));
-    }
-
-    for (MethodResolution m1 : unsorted)
-    {
-      for (MethodResolution m2 : unsorted)
-      {
-        if (m1 == m2)
-          continue;
-
-        if (isMoreSpecificThan(m1.executable, m2.executable)
-                && !isMoreSpecificThan(m2.executable, m1.executable))
-        {
-          m1.children.add(m2);
-        }
-      }
-    }
-
-    // Execute a graph sort problem so that the most specific are always on the front
-    LinkedList<MethodResolution> out = new LinkedList<>();
-    while (!unsorted.isEmpty())
-    {
-      // Remove the first unsorted element
-      MethodResolution front = unsorted.pop();
-      // Check to see if all dependencies are already ordered
-      boolean good = front.isCovered();
-
-      // If all dependencies are included
-      if (good)
-      {
-        front.covered = true;
-        out.add(front);
-      } else
-      {
-        unsorted.add(front);
-      }
-    }
-    return out;
-  }
-
-  // Table for primitive rules
-  static Class[] of(Class... l)
-  {
-    return l;
-  }
-  static HashMap<Class, Class[]> CONVERSION = new HashMap<>();
-
-  
-  {
-    CONVERSION.put(Byte.TYPE,
-            of(Byte.TYPE, Byte.class, Short.TYPE, Short.class,
-                    Integer.TYPE, Integer.class, Long.TYPE, Long.class,
-                    Float.TYPE, Float.class, Double.TYPE, Double.class));
-    CONVERSION.put(Character.TYPE,
-            of(Character.TYPE, Character.class, Short.TYPE, Short.class,
-                    Integer.TYPE, Integer.class, Long.TYPE, Long.class,
-                    Float.TYPE, Float.class, Double.TYPE, Double.class));
-    CONVERSION.put(Short.TYPE,
-            of(Short.TYPE, Short.class,
-                    Integer.TYPE, Integer.class, Long.TYPE, Long.class,
-                    Float.TYPE, Float.class, Double.TYPE, Double.class));
-    CONVERSION.put(Integer.TYPE,
-            of(Integer.TYPE, Integer.class, Long.TYPE, Long.class,
-                    Float.TYPE, Float.class, Double.TYPE, Double.class));
-    CONVERSION.put(Long.TYPE,
-            of(Long.TYPE, Long.class,
-                    Float.TYPE, Float.class, Double.TYPE, Double.class));
-    CONVERSION.put(Float.TYPE,
-            of(Float.TYPE, Float.class, Double.TYPE, Double.class));
-    CONVERSION.put(Double.TYPE,
-            of(Double.TYPE, Double.class));
-    CONVERSION.put(Boolean.TYPE,
-            of(Boolean.TYPE, Boolean.class));
-  }
-
-  static boolean isAssignableTo(Class c1, Class c2)
-  {
-    if (!c1.isPrimitive())
-      return c2.isAssignableFrom(c1);
-    Class[] cl = CONVERSION.get(c1);
-    if (cl == null)
-      return false;
-    for (Class c3 : cl)
-      if (c2.equals(c3))
-        return true;
-    return false;
-  }
-
-  /**
-   * Determine is a executable is more specific than another.
-   * <p>
-   * This is public so that we can debug from within jpype.
-   *
-   * @param method1
-   * @param method2
-   * @return
-   */
-  public static boolean isMoreSpecificThan(Executable method1, Executable method2)
-  {
-    List<Class<?>> param1 = new ArrayList<>(Arrays.asList(method1.getParameterTypes()));
-    List<Class<?>> param2 = new ArrayList<>(Arrays.asList(method2.getParameterTypes()));
-
-    if (!Modifier.isStatic(method1.getModifiers()))
-      param1.add(0, method1.getDeclaringClass());
-    if (!Modifier.isStatic(method2.getModifiers()))
-      param2.add(0, method2.getDeclaringClass());
-
-    // Special handling is needed for varargs as it may chop or expand.
-    // we have 4 cases for a varargs methods
-    //    foo(Arg0, Arg1...) as
-    //       foo(Arg0)
-    //       foo(Arg0, Arg1)
-    //       foo(Arg0, Arg1[])
-    //       foo(Arg0, Arg1, Arg1+)
-    if (method1.isVarArgs() && method2.isVarArgs())
-    {
-      // Punt on this as there are too many different cases
-      return isMoreSpecificThan(param1, param2);
-    }
-
-    if (method1.isVarArgs())
-    {
-      int n1 = param1.size();
-      int n2 = param2.size();
-      
-      // Last element is an array
-      Class<?> cls = param1.get(n1 - 1);
-      Class<?> cls2 = cls.getComponentType();
-      
-      // Less arguments, chop the list 
-      if (n1 - 1 == n2)
-        return isMoreSpecificThan(param1.subList(0, n2), param2);
-      
-      // Same arguments
-      if (n1 == n2)
-      {
-        List<Class<?>> q = new ArrayList<>(param1);
-        q.set(n1 - 1, cls2);
-        
-        // Check both ways
-        boolean isMoreSpecific = isMoreSpecificThan(param1, param2) || isMoreSpecificThan(q, param2);
-
-	// If the varargs array is of the single-variable's type (or they are primitive-equivalent),
-	// the single-variable signature should win specificity
-	Class<?> svCls = param2.get(n2-1);
-        return isMoreSpecific && !(isAssignableTo(cls2, svCls) && isAssignableTo(svCls, cls2));
-      }
-      
-      // More arguments
-      if (n1 < n2)
-      {
-        // Grow the list
-        List<Class<?>> q = new ArrayList<>(param1);
-        q.set(n1 - 1, cls2);
-        for (int i = n1; i < n2; ++i)
-          q.add(cls2);
-        return isMoreSpecificThan(q, param2);
-      }
-    }
-
-    if (method2.isVarArgs())
-    {
-      int n1 = param1.size();
-      int n2 = param2.size();
-      
-      // Last element is an array
-      Class<?> cls = param2.get(n2 - 1);
-      Class<?> cls2 = cls.getComponentType();
-      
-      // Less arguments, chop the list
-      if (n2 - 1 == n1)
-        return isMoreSpecificThan(param1, param2.subList(0, n2));
-      
-      // Same arguments
-      if (n1 == n2)
-      {
-        List<Class<?>> q = new ArrayList<>(param2);
-        q.set(n2 - 1, cls2);
-        
-        // Compare both ways
-        return isMoreSpecificThan(param1, param2) || isMoreSpecificThan(param1, q);
-      }
-      
-      // More arguments
-      if (n2 < n1)
-      {
-        // Grow the list
-        List<Class<?>> q = new ArrayList<>(param2);
-        q.set(n2 - 1, cls2);
-        for (int i = n2; i < n1; ++i)
-          q.add(cls2);
-        return isMoreSpecificThan(param1, q);
-      }
-    }
-
-    return isMoreSpecificThan(param1, param2);
-  }
-
-  public static boolean isMoreSpecificThan(List<Class<?>> param1, List<Class<?>> param2)
-  {
-    // FIXME need to consider resolving mixing of static and non-static
-    // Methods here.
-    if (param1.size() != param2.size())
-      return false;
-
-    for (int i = 0; i < param1.size(); ++i)
-    {
-      if (!isAssignableTo(param1.get(i), param2.get(i)))
-        return false;
-    }
-    return true;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/manager/ModifierCode.java 1.6.0-1/native/java/org/jpype/manager/ModifierCode.java
--- 1.5.0-1/native/java/org/jpype/manager/ModifierCode.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/manager/ModifierCode.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,82 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.manager;
-
-import java.lang.reflect.Modifier;
-import java.util.EnumSet;
-
-/**
- * Definitions for JPype modifiers.
- * <p>
- * These pretty much match Java plus a few codes we need.
- *
- * @author nelson85
- */
-public enum ModifierCode
-{
-  // we need
-  //   fields: static, final
-  //   methods: static, final, varargs, constructor
-  //   class: interface, throwable, abstract, final
-  PUBLIC(Modifier.PUBLIC),
-  PRIVATE(Modifier.PRIVATE),
-  PROTECTED(Modifier.PROTECTED),
-  STATIC(Modifier.STATIC),
-  FINAL(Modifier.FINAL),
-  VARARGS(0x0080),
-  ENUM(0x4000),
-  ABSTRACT(0x0400),
-  // Special flags for classes required for JPype
-  SPECIAL(0x00010000),
-  THROWABLE(0x00020000),
-  SERIALIZABLE(0x00040000),
-  ANONYMOUS(0x00080000),
-  FUNCTIONAL(0x00100000),
-  CALLER_SENSITIVE(0x00200000),
-  PRIMITIVE_ARRAY(0x00400000),
-  COMPARABLE(0x00800000),
-  BUFFER(0x01000000),
-  CTOR(0x10000000),
-  BEAN_ACCESSOR(0x20000000),
-  BEAN_MUTATOR(0x40000000);
-  final public int value;
-
-  ModifierCode(int value)
-  {
-    this.value = value;
-  }
-
-  public static int get(EnumSet<ModifierCode> set)
-  {
-    int out = 0;
-    for (ModifierCode m : set)
-    {
-      out |= m.value;
-    }
-    return out;
-  }
-
-  public static EnumSet<ModifierCode> decode(long modifiers)
-  {
-    EnumSet<ModifierCode> out = EnumSet.noneOf(ModifierCode.class);
-    for (ModifierCode code : ModifierCode.values())
-    {
-      if ((modifiers & code.value) == code.value)
-        out.add(code);
-    }
-    return out;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/manager/TypeAudit.java 1.6.0-1/native/java/org/jpype/manager/TypeAudit.java
--- 1.5.0-1/native/java/org/jpype/manager/TypeAudit.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/manager/TypeAudit.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,35 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.manager;
-
-import java.lang.reflect.Method;
-
-/**
- * Auditing class for TypeManager used during testing.
- * <p>
- * This is not used during operation.
- *
- * @author nelson85
- */
-public interface TypeAudit
-{
-
-  void dump(ClassDescriptor desc);
-
-  void verifyMembers(ClassDescriptor desc);
-
-  public void failFindMethod(ClassDescriptor desc, Method method);
-}
diff -pruN 1.5.0-1/native/java/org/jpype/manager/TypeFactory.java 1.6.0-1/native/java/org/jpype/manager/TypeFactory.java
--- 1.5.0-1/native/java/org/jpype/manager/TypeFactory.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/manager/TypeFactory.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,192 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.manager;
-
-import java.lang.reflect.Executable;
-import java.lang.reflect.Field;
-
-/**
- * Interface for creating new resources used by JPype.
- * <p>
- * This calls the C++ constructors with all of the required fields for each
- * class. This pattern eliminates the need for C++ layer probing Java for
- * resources.
- * <p>
- * This is an interface for testing.
- *
- * @author nelson85
- */
-public interface TypeFactory
-{
-//<editor-fold desc="class" defaultstate="collapsed">
-
-  /**
-   * Create a new wrapper type for Python.
-   *
-   * @param context
-   * @param cls is the pointer to the JClass.
-   */
-  void newWrapper(long context, long cls);
-
-  /**
-   * Create a JPArray class.
-   *
-   * @param context JPContext object
-   * @param cls is the class type.
-   * @param name
-   * @param superClass
-   * @param componentPtr
-   * @param modifiers
-   * @return the pointer to the JPArrayClass.
-   */
-  long defineArrayClass(
-          long context,
-          Class cls,
-          String name,
-          long superClass,
-          long componentPtr,
-          int modifiers);
-
-  /**
-   * Create a class type.
-   *
-   * @param context JPContext object
-   * @param cls
-   * @param superClass
-   * @param interfaces
-   * @param modifiers
-   * @param name
-   * @return the pointer to the JPClass.
-   */
-  long defineObjectClass(
-          long context,
-          Class cls,
-          String name,
-          long superClass,
-          long[] interfaces,
-          int modifiers);
-
-  /**
-   * Define a primitive types.
-   *
-   * @param context JPContext object
-   * @param cls is the Java class for this primitive.
-   * @param boxedPtr is the JPClass for the boxed class.
-   * @param modifiers
-   * @return
-   */
-  long definePrimitive(
-          long context,
-          String name,
-          Class cls,
-          long boxedPtr,
-          int modifiers);
-
-//</editor-fold>
-//<editor-fold desc="members" defaultstate="collapsed">
-  /**
-   * Called after a class is constructed to populate the required fields and
-   * methods.
-   *
-   * @param context JPContext object
-   * @param cls is the JPClass to populate
-   * @param ctorMethod is the JPMethod for the constructor.
-   * @param methodList is a list of JPMethod for the method list.
-   * @param fieldList is a list of JPField for the field list.
-   */
-  void assignMembers(
-          long context,
-          long cls,
-          long ctorMethod,
-          long[] methodList,
-          long[] fieldList);
-
-  /**
-   * Create a Method.
-   *
-   * @param context JPContext object
-   * @param cls is the class holding this.
-   * @param name
-   * @param field
-   * @param fieldType
-   * @param modifiers
-   * @return the pointer to the JPMethod.
-   */
-  long defineField(
-          long context,
-          long cls,
-          String name,
-          Field field, // This will convert to a field id
-          long fieldType,
-          int modifiers);
-
-  /**
-   * Create a Method.
-   *
-   * @param context JPContext object
-   * @param cls is the class holding this.
-   * @param name
-   * @param method is the Java method that will be called, converts to a method
-   * id.
-   * @param overloadList
-   * @param modifiers
-   * @return the pointer to the JPMethod.
-   */
-  long defineMethod(
-          long context,
-          long cls,
-          String name,
-          Executable method,
-          long[] overloadList,
-          int modifiers);
-
-  void populateMethod(
-          long context,
-          long method,
-          long returnType,
-          long[] argumentTypes);
-
-  /**
-   * Create a Method dispatch for Python by name.
-   *
-   * @param context JPContext object
-   * @param cls is the class that owns this dispatch.
-   * @param name is the name of the dispatch.
-   * @param overloadList is the list of all methods constructed for this class.
-   * @param modifiers contains if the method is (CTOR, STATIC),
-   * @return the pointer to the JPMethodDispatch.
-   */
-  long defineMethodDispatch(
-          long context,
-          long cls,
-          String name,
-          long[] overloadList,
-          int modifiers);
-
-//</editor-fold>
-//<editor-fold desc="destroy" defaultstate="collapsed">
-  /**
-   * Destroy the resources.
-   *
-   * @param context JPContext object
-   * @param resources
-   * @param sz
-   */
-  void destroy(
-          long context,
-          long[] resources, int sz);
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/manager/TypeFactoryNative.java 1.6.0-1/native/java/org/jpype/manager/TypeFactoryNative.java
--- 1.5.0-1/native/java/org/jpype/manager/TypeFactoryNative.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/manager/TypeFactoryNative.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,105 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.manager;
-
-import java.lang.reflect.Executable;
-import java.lang.reflect.Field;
-
-/**
- * This is the interface for creating C++ object in JPype.
- * <p>
- * These methods are all native.
- * <p>
- */
-public class TypeFactoryNative implements TypeFactory
-{
-
-  public long context;
-
-  public native void newWrapper(long context, long cls);
-
-  @Override
-  public native long defineArrayClass(
-          long context,
-          Class cls,
-          String name,
-          long superClass,
-          long componentPtr,
-          int modifiers);
-
-  @Override
-  public native long defineObjectClass(
-          long context,
-          Class cls,
-          String name,
-          long superClass,
-          long[] interfaces,
-          int modifiers);
-
-  @Override
-  public native long definePrimitive(
-          long context,
-          String name,
-          Class cls,
-          long boxedPtr,
-          int modifiers);
-
-  @Override
-  public native void assignMembers(
-          long context,
-          long cls,
-          long ctorMethod,
-          long[] methodList,
-          long[] fieldList);
-
-  @Override
-  public native long defineField(
-          long context,
-          long cls,
-          String name,
-          Field field,
-          long fieldType,
-          int modifiers);
-
-  @Override
-  public native long defineMethod(
-          long context,
-          long cls,
-          String name,
-          Executable method,
-          long[] overloadList,
-          int modifiers);
-
-  @Override
-  public native void populateMethod(
-          long context,
-          long method,
-          long returnType,
-          long[] argumentTypes);
-
-  @Override
-  public native long defineMethodDispatch(
-          long context,
-          long cls,
-          String name,
-          long[] overloadList,
-          int modifiers);
-
-  @Override
-  public native void destroy(
-          long context,
-          long[] resources, int sz);
-}
diff -pruN 1.5.0-1/native/java/org/jpype/manager/TypeManager.java 1.6.0-1/native/java/org/jpype/manager/TypeManager.java
--- 1.5.0-1/native/java/org/jpype/manager/TypeManager.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/manager/TypeManager.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,999 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.manager;
-
-import java.io.Serializable;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Array;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Executable;
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.Proxy;
-import java.util.Arrays;
-import java.nio.Buffer;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.TreeSet;
-import org.jpype.JPypeContext;
-import org.jpype.JPypeUtilities;
-import org.jpype.proxy.JPypeProxy;
-
-/**
- *
- */
-public class TypeManager
-{
-
-  public long context = 0;
-  public boolean isStarted = false;
-  public boolean isShutdown = false;
-  public HashMap<Class, ClassDescriptor> classMap = new HashMap<>();
-  public TypeFactory typeFactory = null;
-  public TypeAudit audit = null;
-  private ClassDescriptor java_lang_Object;
-  // For reasons that are less than clear, this object cannot be created
-  // during shutdown
-  private Destroyer destroyer = new Destroyer();
-
-  public TypeManager()
-  {
-  }
-
-  public TypeManager(long context, TypeFactory typeFactory)
-  {
-    this.context = context;
-    this.typeFactory = typeFactory;
-  }
-
-//<editor-fold desc="interface">
-  public synchronized void init()
-  {
-    try
-    {
-      if (isStarted)
-        throw new RuntimeException("Cannot be restarted");
-      isStarted = true;
-      isShutdown = false;
-
-      // Create the required minimum classes
-      this.java_lang_Object = createClass(Object.class, true);
-
-      // Note that order is very important when creating these initial wrapper
-      // types. If something inherits from another type then the super class
-      // will be created without the special flag and the type system won't
-      // be able to handle the duplicate type properly.
-      Class[] cls =
-      {
-        Class.class, Number.class, CharSequence.class, Throwable.class,
-        Void.class, Boolean.class, Byte.class, Character.class,
-        Short.class, Integer.class, Long.class, Float.class, Double.class,
-        String.class, JPypeProxy.class,
-        Method.class, Field.class
-      };
-      for (Class c : cls)
-      {
-        createClass(c, true);
-      }
-
-      // Create the primitive types
-      // Link boxed and primitive types so that the wrappers can find them.
-      createPrimitive("void", Void.TYPE, Void.class);
-      createPrimitive("boolean", Boolean.TYPE, Boolean.class);
-      createPrimitive("byte", Byte.TYPE, Byte.class);
-      createPrimitive("char", Character.TYPE, Character.class);
-      createPrimitive("short", Short.TYPE, Short.class);
-      createPrimitive("int", Integer.TYPE, Integer.class);
-      createPrimitive("long", Long.TYPE, Long.class);
-      createPrimitive("float", Float.TYPE, Float.class);
-      createPrimitive("double", Double.TYPE, Double.class);
-    } catch (Throwable ex)
-    {
-      // We can't get debugging information at this point in the process.
-      ex.printStackTrace();
-      throw ex;
-    }
-  }
-
-  /**
-   * Find a wrapper for a class.
-   * <p>
-   * Creates one if needed. This a front end used by JPype.
-   *
-   * @param cls
-   * @return the JPClass, or 0 it one cannot be created.
-   */
-  public synchronized long findClass(Class<?> cls)
-  {
-    if (cls == null)
-      return 0;
-    if (this.isShutdown)
-      return 0;
-
-    long out;
-    if (cls.isSynthetic() && cls.getSimpleName().contains("$Lambda$"))
-    {
-      // If is it lambda, we need a special wrapper
-      // we don't want to create a class each time in that case.
-      // Thus use the parent interface for this class
-      out = getClass(cls.getInterfaces()[0]).classPtr;
-    } else if (cls.isAnonymousClass())
-    {
-      // This one is more of a burden.  It depends what whether is was
-      // anonymous extends or implements.
-      if (cls.getInterfaces().length == 1)
-        out = getClass(cls.getInterfaces()[0]).classPtr;
-      else
-      {
-        ClassDescriptor parent = getClass(cls.getSuperclass());
-        out = createAnonymous(parent);
-      }
-    } else
-    {
-      // Just a regular class
-      out = getClass(cls).classPtr;
-    }
-
-    return out;
-  }
-
-  /**
-   * Get a class by name.
-   *
-   * @param name is the class name.
-   * @return the C++ portion.
-   */
-  public long findClassByName(String name)
-  {
-    Class<?> cls = lookupByName(name);
-    if (cls == null)
-      return 0;
-    return this.findClass(cls);
-  }
-
-  public Class<?> lookupByName(String name)
-  {
-    ClassLoader classLoader = JPypeContext.getInstance().getClassLoader();
-
-    // Handle arrays
-    if (name.endsWith("[]"))
-    {
-      int dims = 0;
-      while (name.endsWith("[]"))
-      {
-        dims++;
-        name = name.substring(0, name.length() - 2);
-      }
-      Class<?> cls = lookupByName(name);
-      if (cls == null)
-        return null;
-      return Array.newInstance(cls, new int[dims]).getClass();
-    }
-
-    try
-    {
-      // Attempt direct lookup
-      return Class.forName(name, true, classLoader);
-    } catch (ClassNotFoundException ex)
-    {
-    }
-
-    // Deal with JNI style names
-    if (name.contains("/"))
-    {
-      try
-      {
-        return Class.forName(name.replaceAll("/", "."), true, classLoader);
-      } catch (ClassNotFoundException ex)
-      {
-      }
-    }
-
-    // Special case for primitives
-    if (!name.contains("."))
-    {
-      if ("boolean".equals(name))
-        return Boolean.TYPE;
-      if ("byte".equals(name))
-        return Byte.TYPE;
-      if ("char".equals(name))
-        return Character.TYPE;
-      if ("short".equals(name))
-        return Short.TYPE;
-      if ("long".equals(name))
-        return Long.TYPE;
-      if ("int".equals(name))
-        return Integer.TYPE;
-      if ("float".equals(name))
-        return Float.TYPE;
-      if ("double".equals(name))
-        return Double.TYPE;
-    }
-
-    // Attempt to find an inner class
-    String[] parts = name.split("\\.");
-    StringBuilder sb = new StringBuilder();
-    sb.append(parts[0]);
-    for (int i = 1; i < parts.length; ++i)
-    {
-      try
-      {
-        sb.append(".");
-        sb.append(parts[i]);
-        Class<?> cls = Class.forName(sb.toString());
-        for (int j = i + 1; j < parts.length; ++j)
-        {
-          sb.append("$");
-          sb.append(parts[j]);
-        }
-        return Class.forName(sb.toString());
-      } catch (ClassNotFoundException ex)
-      {
-      }
-    }
-    return null;
-  }
-
-  public synchronized void populateMethod(long wrapper, Executable method)
-  {
-    if (method == null)
-      return;
-
-    long returnType = 0;
-    if (method instanceof Method)
-    {
-      returnType = getClass(((Method) method).getReturnType()).classPtr;
-    }
-
-    Class<?>[] params = method.getParameterTypes();
-    int i = 0;
-    long[] paramPtrs;
-    if (!Modifier.isStatic(method.getModifiers()) && !(method instanceof Constructor))
-    {
-      paramPtrs = new long[params.length + 1];
-      paramPtrs[0] = getClass(method.getDeclaringClass()).classPtr;
-      i++;
-    } else
-    {
-      paramPtrs = new long[params.length];
-    }
-
-    // Copy in the parameters
-    for (Class<?> p : params)
-    {
-      paramPtrs[i] = getClass(p).classPtr;
-      i++;
-    }
-
-    try
-    {
-      typeFactory.populateMethod(context, wrapper, returnType, paramPtrs);
-    } catch (Exception ex)
-    {
-      ex.printStackTrace();
-    }
-  }
-
-  /**
-   * Returns the number of arguments an interface only unimplemented method accept.
-   *
-   * @param interfaceClass The class of the interface
-   * @return the number of arguments the only unimplemented method of the interface accept.
-   */
-  public int interfaceParameterCount(Class<?> interfaceClass)
-  {
-    ClassDescriptor classDescriptor = classMap.get(interfaceClass);
-    return classDescriptor.functional_interface_parameter_count;
-  }
-
-  /**
-   * Get a class for an object.
-   *
-   * @param object is the object to interrogate.
-   * @return the C++ portion or null if the object is null.
-   * @throws java.lang.InterruptedException
-   */
-  public long findClassForObject(Object object) throws InterruptedException
-  {
-    JPypeContext.clearInterrupt(true);
-    if (object == null)
-      return 0;
-
-    Class cls = object.getClass();
-    if (Proxy.isProxyClass(cls)
-            && (Proxy.getInvocationHandler(object) instanceof JPypeProxy))
-    {
-      return this.findClass(JPypeProxy.class);
-    }
-
-    return this.findClass(cls);
-  }
-
-  /**
-   * Called to delete all C++ resources
-   */
-  public synchronized void shutdown()
-  {
-    // First and most important, we can't operate from this
-    // point forward.
-    this.isShutdown = true;
-
-    // Destroy all the resources held in C++
-    for (ClassDescriptor entry : this.classMap.values())
-    {
-      destroyer.add(entry.constructorDispatch);
-      destroyer.add(entry.constructors);
-      destroyer.add(entry.methodDispatch);
-      destroyer.add(entry.methods);
-      destroyer.add(entry.fields);
-      destroyer.add(entry.anonymous);
-      destroyer.add(entry.classPtr);
-
-      // The same wrapper can appear more than once so blank as we go.
-      entry.constructorDispatch = 0;
-      entry.constructors = null;
-      entry.methodDispatch = null;
-      entry.methods = null;
-      entry.fields = null;
-      entry.anonymous = 0;
-      entry.classPtr = 0;
-    }
-    destroyer.flush();
-
-    // FIXME. If someone attempts to shutdown the JVM within a Python
-    // proxy, everything will crash here.  We would lose the class
-    // that is calling things and the ability to throw exceptions.
-    // Most likely this will go splat. We need to catch this
-    // from within JPype and hard fault our way to safety.
-    this.classMap.clear();
-  }
-//</editor-fold>
-//<editor-fold desc="classes" defaultstate="defaultstate">
-
-  private ClassDescriptor getClass(Class cls)
-  {
-    if (cls == null)
-      return null;
-
-    // Look up the current description
-    ClassDescriptor ptr = this.classMap.get(cls);
-    if (ptr != null)
-      return ptr;
-
-    // If we can't find it create a new class
-    return createClass(cls, false);
-  }
-
-  /**
-   * Allocate a new wrapper for a java class.
-   * <p>
-   * Boxed types require special handlers, as does java.lang.String
-   *
-   * @param cls is the Java class to wrap.
-   * @param special marks class as requiring a specialized C++ wrapper.
-   * @return a C++ wrapper handle for a jp_classtype
-   */
-  private ClassDescriptor createClass(Class<?> cls, boolean special)
-  {
-    if (cls.isArray())
-      return this.createArrayClass(cls);
-
-    return createOrdinaryClass(cls, special, true);
-  }
-
-  private ClassDescriptor createOrdinaryClass(Class<?> cls, boolean special, boolean bases)
-  {
-    // Verify the class will be loadable prior to creating the class.
-    // If we fail to do this then the class may end up crashing later when the
-    // members get populated which could leave us in a bad state.
-    cls.getMethods();
-    cls.getFields();
-
-    // Object classes are more work as we need the super information as well.
-    // Make sure all base classes are loaded
-    Class<?> superClass = cls.getSuperclass();
-    Class<?>[] interfaces = cls.getInterfaces();
-    ClassDescriptor[] parents = new ClassDescriptor[interfaces.length + 1];
-    long[] interfacesPtr = null;
-    long superClassPtr = 0;
-    superClassPtr = 0;
-    if (superClass != null)
-    {
-      parents[0] = this.getClass(superClass);
-      superClassPtr = parents[0].classPtr;
-    }
-
-    if (bases)
-    {
-      interfacesPtr = new long[interfaces.length];
-
-      // Make sure all interfaces are loaded.
-      for (int i = 0; i < interfaces.length; ++i)
-      {
-        parents[i + 1] = this.getClass(interfaces[i]);
-        interfacesPtr[i] = parents[i + 1].classPtr;
-      }
-    } else
-    {
-      interfacesPtr = new long[0];
-    }
-
-    // Set up the modifiers
-    int modifiers = cls.getModifiers() & 0xffff;
-    if (special)
-      modifiers |= ModifierCode.SPECIAL.value;
-    if (Throwable.class.isAssignableFrom(cls))
-      modifiers |= ModifierCode.THROWABLE.value;
-    if (Serializable.class.isAssignableFrom(cls))
-      modifiers |= ModifierCode.SERIALIZABLE.value;
-    if (Arrays.asList(cls.getInterfaces()).contains(Comparable.class))
-      modifiers |= ModifierCode.COMPARABLE.value;
-    if (Buffer.class.isAssignableFrom(cls))
-      modifiers |= ModifierCode.BUFFER.value | ModifierCode.SPECIAL.value;
-
-    // Check if is Functional class
-    Method method = JPypeUtilities.getFunctionalInterfaceMethod(cls);
-    if (method != null)
-      modifiers |= ModifierCode.FUNCTIONAL.value | ModifierCode.SPECIAL.value;
-
-    // FIXME watch out for anonyous and lambda here.
-    String name = cls.getCanonicalName();
-    if (name == null)
-      name = cls.getName();
-
-    // Create the JPClass
-    long classPtr = typeFactory.defineObjectClass(context, cls, name,
-            superClassPtr,
-            interfacesPtr,
-            modifiers);
-
-    // Cache the wrapper.
-    ClassDescriptor out = new ClassDescriptor(cls, classPtr, method);
-    this.classMap.put(cls, out);
-    return out;
-  }
-
-  private long createAnonymous(ClassDescriptor parent)
-  {
-    if (parent.anonymous != 0)
-      return parent.anonymous;
-
-    parent.anonymous = typeFactory.defineObjectClass(context,
-            parent.cls, parent.cls.getCanonicalName() + "$Anonymous",
-            parent.classPtr,
-            null,
-            ModifierCode.ANONYMOUS.value);
-    return parent.anonymous;
-  }
-
-  ClassDescriptor createArrayClass(Class cls)
-  {
-    // Array classes are simple, we just need the component type
-    Class componentType = cls.getComponentType();
-    long componentTypePtr = this.getClass(componentType).classPtr;
-
-    int modifiers = cls.getModifiers() & 0xffff;
-    String name = cls.getName();
-    if (!name.endsWith(";"))
-      modifiers |= ModifierCode.PRIMITIVE_ARRAY.value;
-
-    long classPtr = typeFactory
-            .defineArrayClass(context, cls,
-                    cls.getCanonicalName(),
-                    this.java_lang_Object.classPtr,
-                    componentTypePtr,
-                    modifiers);
-
-    ClassDescriptor out = new ClassDescriptor(cls, classPtr, null);
-    this.classMap.put(cls, out);
-    return out;
-  }
-
-  /**
-   * Tell JPype to make a primitive Class.
-   *
-   * @param name
-   * @param cls
-   * @param boxed
-   */
-  private void createPrimitive(String name, Class cls, Class boxed)
-  {
-    long classPtr = typeFactory.definePrimitive(context,
-            name,
-            cls,
-            this.getClass(boxed).classPtr,
-            cls.getModifiers() & 0xffff);
-    this.classMap.put(cls, new ClassDescriptor(cls, classPtr, null));
-  }
-
-//</editor-fold>
-//<editor-fold desc="members" defaultstate="collapsed">
-  public synchronized void populateMembers(Class cls)
-  {
-    ClassDescriptor desc = this.classMap.get(cls);
-    if (desc == null)
-      throw new RuntimeException("Class not loaded");
-    if (desc.fields != null)
-      return;
-    try
-    {
-      createMembers(desc);
-    } catch (Exception ex)
-    {
-      ex.printStackTrace(System.out);
-      throw ex;
-    }
-  }
-
-  private void createMembers(ClassDescriptor desc)
-  {
-    this.createFields(desc);
-    this.createConstructorDispatch(desc);
-    this.createMethodDispatches(desc);
-
-    // Verify integrity
-    if (audit != null)
-      audit.verifyMembers(desc);
-
-    // Pass this to JPype
-    this.typeFactory.assignMembers(context,
-            desc.classPtr,
-            desc.constructorDispatch,
-            desc.methodDispatch,
-            desc.fields);
-  }
-
-//<editor-fold desc="fields" defaultstate="collapsed">
-  private void createFields(ClassDescriptor desc)
-  {
-    // We only need declared fields as the wrappers for previous classes hold
-    // members declared earlier
-    LinkedList<Field> fields = filterPublic(desc.cls.getDeclaredFields());
-
-    long[] fieldPtr = new long[fields.size()];
-    int i = 0;
-    for (Field field : fields)
-    {
-      fieldPtr[i++] = this.typeFactory.defineField(context,
-              desc.classPtr,
-              field.getName(),
-              field,
-              getClass(field.getType()).classPtr,
-              field.getModifiers() & 0xffff);
-    }
-    desc.fields = fieldPtr;
-  }
-//</editor-fold>
-//<editor-fold desc="ctor" defaultstate="collapsed">
-
-  /**
-   * Load the constructors for a class.
-   *
-   * @param desc
-   */
-  public void createConstructorDispatch(ClassDescriptor desc)
-  {
-    Class cls = desc.cls;
-
-    // Get the list of declared constructors
-    LinkedList<Constructor> constructors
-            = filterPublic(cls.getDeclaredConstructors());
-
-    if (constructors.isEmpty())
-      return;
-
-    // Sort them by precedence order
-    List<MethodResolution> overloads = MethodResolution.sortMethods(constructors);
-
-    // Convert overload list to a list of overloads pointers
-    desc.constructors = this.createConstructors(desc, overloads);
-
-    // Create the dispatch for it
-    desc.constructorDispatch = typeFactory
-            .defineMethodDispatch(context,
-                    desc.classPtr,
-                    "<init>",
-                    desc.constructors,
-                    ModifierCode.PUBLIC.value | ModifierCode.CTOR.value);
-  }
-
-  /**
-   * Construct a set of constructor overloads for an OverloadResolution.
-   * <p>
-   * These will be added to the shutdown destruction list.
-   *
-   * @param desc
-   * @param overloads
-   * @return
-   */
-  private long[] createConstructors(ClassDescriptor desc,
-          List<MethodResolution> overloads)
-  {
-    int n = overloads.size();
-    long[] overloadPtrs = new long[overloads.size()];
-    for (MethodResolution ov : overloads)
-    {
-      Constructor constructor = (Constructor) ov.executable;
-
-      int i = 0;
-      long[] precedencePtrs = new long[ov.children.size()];
-      for (MethodResolution ch : ov.children)
-      {
-        precedencePtrs[i++] = ch.ptr;
-      }
-
-      int modifiers = constructor.getModifiers() & 0xffff;
-      modifiers |= ModifierCode.CTOR.value;
-      ov.ptr = typeFactory.defineMethod(context,
-              desc.classPtr,
-              constructor.toString(),
-              constructor,
-              precedencePtrs,
-              modifiers);
-      overloadPtrs[--n] = ov.ptr;
-    }
-    return overloadPtrs;
-  }
-
-//</editor-fold>
-//<editor-fold desc="methods" defaultstate="collapsed">
-  /**
-   * Load the methods for a class.
-   *
-   * @param desc
-   */
-  public void createMethodDispatches(ClassDescriptor desc)
-  {
-    Class<?> cls = desc.cls;
-
-    // Get the list of all public, non-overrided methods we will process
-    LinkedList<Method> methods = filterOverridden(cls, cls.getMethods());
-
-    // Get the list of public declared methods
-    LinkedList<Method> declaredMethods = filterOverridden(cls, cls.getDeclaredMethods());
-
-    // We only need one dispatch per name
-    TreeSet<String> resolve = new TreeSet<>();
-    for (Method method : declaredMethods)
-    {
-      resolve.add(method.getName());
-    }
-
-    // Reserve memory for our lookup table
-    desc.methods = new long[declaredMethods.size()];
-    desc.methodIndex = new Method[declaredMethods.size()];
-    desc.methodDispatch = new long[resolve.size()];
-
-    int i = 0;
-    for (String name : resolve)
-    {
-      desc.methodDispatch[i++] = this.createMethodDispatch(desc, name, methods);
-    }
-  }
-
-  private long createMethodDispatch(
-          ClassDescriptor desc,
-          String key,
-          LinkedList<Method> candidates)
-  {
-    // Find all the methods that match the key
-    LinkedList<Method> methods = new LinkedList<>();
-    Iterator<Method> iter = candidates.iterator();
-
-    int modifiers = 0;
-    while (iter.hasNext())
-    {
-      Method next = iter.next();
-      if (!next.getName().equals(key))
-        continue;
-      iter.remove();
-      methods.add(next);
-      if (Modifier.isStatic(next.getModifiers()))
-        modifiers |= ModifierCode.STATIC.value;
-      if (isBeanAccessor(next))
-        modifiers |= ModifierCode.BEAN_ACCESSOR.value;
-      if (isBeanMutator(next))
-        modifiers |= ModifierCode.BEAN_MUTATOR.value;
-    }
-
-    // Convert overload list to a list of overloads pointers
-    List<MethodResolution> overloads = MethodResolution.sortMethods(methods);
-    long[] overloadPtrs = this.createMethods(desc, overloads);
-
-    long methodContainer = typeFactory.defineMethodDispatch(context,
-            desc.classPtr,
-            key,
-            overloadPtrs,
-            modifiers);
-
-    return methodContainer;
-  }
-
-  /**
-   * Convert a list of executable overload resolutions into a executable
-   * overload list.
-   * <p>
-   * These will be added to the shutdown destruction list.
-   *
-   * @param desc
-   * @param overloads
-   * @return a list of method overload wrappers.
-   */
-  private long[] createMethods(
-          ClassDescriptor desc,
-          List<MethodResolution> overloads)
-  {
-    int n = overloads.size();
-    long[] overloadPtrs = new long[overloads.size()];
-    for (MethodResolution ov : overloads)
-    {
-      Method method = (Method) ov.executable;
-
-      // We may already have built a methodoverload for this
-      Class<?> decl = method.getDeclaringClass();
-      if (method.getDeclaringClass() != desc.cls)
-      {
-        this.populateMembers(decl);
-        ov.ptr = this.classMap.get(decl).getMethod(method);
-        if (ov.ptr == 0)
-        {
-          if (audit != null)
-            audit.failFindMethod(desc, method);
-          throw new RuntimeException("Fail");
-        }
-        overloadPtrs[--n] = ov.ptr;
-        continue;
-      }
-
-      // Determine what takes precedence
-      int i = 0;
-      long[] precedencePtrs = new long[ov.children.size()];
-      for (MethodResolution ch : ov.children)
-      {
-        precedencePtrs[i++] = ch.ptr;
-      }
-
-      int modifiers = method.getModifiers() & 0xffff;
-      if (isBeanMutator(method))
-        modifiers |= ModifierCode.BEAN_MUTATOR.value;
-      if (isBeanAccessor(method))
-        modifiers |= ModifierCode.BEAN_ACCESSOR.value;
-      if (isCallerSensitive(method))
-        modifiers |= ModifierCode.CALLER_SENSITIVE.value;
-
-      ov.ptr = typeFactory.defineMethod(context,
-              desc.classPtr,
-              method.toString(),
-              method,
-              precedencePtrs,
-              modifiers);
-      overloadPtrs[--n] = ov.ptr;
-      desc.methods[desc.methodCounter] = ov.ptr;
-      desc.methodIndex[desc.methodCounter] = method;
-      desc.methodCounter++;
-    }
-    return overloadPtrs;
-  }
-
-  static boolean hasCallerSensitive = false;
-
-  static
-  {
-    try
-    {
-      java.lang.reflect.Method method = java.lang.Class.class.getDeclaredMethod("forName", String.class);
-      for (Annotation annotation : method.getAnnotations())
-      {
-        if ("@jdk.internal.reflect.CallerSensitive()".equals(annotation.toString()))
-        {
-          hasCallerSensitive = true;
-        }
-      }
-    } catch (NoSuchMethodException | SecurityException ex)
-    {
-    }
-  }
-
-  /**
-   * Checks to see if the method is caller sensitive.
-   *
-   * As the annotation is a private internal, we must check by name.
-   *
-   * @param method is the method to be probed.
-   * @return true if caller sensitive.
-   */
-  public static boolean isCallerSensitive(Method method)
-  {
-    if (hasCallerSensitive)
-    {
-      for (Annotation annotation : method.getAnnotations())
-      {
-        if ("@jdk.internal.reflect.CallerSensitive()".equals(annotation.toString()))
-        {
-          return true;
-        }
-      }
-    } else
-    {
-      // JDK prior versions prior to 9 do not annotate methods that
-      // require special handling, thus we will just blanket those
-      // classes known to have issues.
-      Class<?> cls = method.getDeclaringClass();
-      if (cls.equals(java.lang.Class.class)
-              || cls.equals(java.lang.ClassLoader.class)
-              || cls.equals(java.sql.DriverManager.class))
-      {
-        return true;
-      }
-    }
-    return false;
-  }
-
-//</editor-fold>
-//<editor-fold desc="containers" defaultstate="collapsed">
-//</editor-fold>
-//</editor-fold>
-//<editor-fold desc="filters" defaultstate="collapsed">
-  /**
-   * Remove any methods that are not public from a list.
-   *
-   * @param <T>
-   * @param methods
-   * @return a new list containing only public members.
-   */
-  public static <T extends Member> LinkedList<T> filterPublic(T[] methods)
-  {
-    LinkedList<T> out = new LinkedList<>();
-    for (T method : methods)
-    {
-      if (!Modifier.isPublic(method.getModifiers()))
-        continue;
-      out.add(method);
-    }
-    return out;
-  }
-
-  /**
-   * Remove any methods that are not public and have been overridden from a
-   * list.
-   *
-   * @param cls
-   * @param methods
-   * @return a new list containing only public members that are not overridden.
-   */
-  public static LinkedList<Method> filterOverridden(Class<?> cls, Method[] methods)
-  {
-    LinkedList<Method> out = new LinkedList<>();
-    for (Method method : methods)
-    {
-      if (!Modifier.isPublic(method.getModifiers()) || isOverridden(cls, method))
-        continue;
-      out.add(method);
-    }
-    return out;
-  }
-
-//</editor-fold>
-//<editor-fold desc="utilities" defaultstate="collapsed">
-  /**
-   * Determines if a method is masked by another in a class.
-   *
-   * @param cls is the class to investigate.
-   * @param method is a method that applies to the class.
-   * @return true if the method is hidden by another method.
-   */
-  public static boolean isOverridden(Class<?> cls, Method method)
-  {
-    try
-    {
-      return !method.equals(cls.getMethod(method.getName(), method.getParameterTypes()));
-    } catch (NoSuchMethodException | SecurityException ex)
-    {
-      return false;
-    }
-  }
-
-  /**
-   * Bean accessor is flag is used for property module.
-   * <p>
-   * Accessors need
-   *
-   * @param method
-   * @return
-   */
-  private boolean isBeanAccessor(Method method)
-  {
-    if (Modifier.isStatic(method.getModifiers()))
-      return false;
-    if (method.getReturnType().equals(void.class))
-      return false;
-    if (method.getParameterCount() > 0)
-      return false;
-    if (method.getName().length() < 4)
-      return false;
-    return (method.getName().startsWith("get"));
-  }
-
-  /**
-   * Bean mutator is flag is used for property module.
-   *
-   * @param method
-   * @return
-   */
-  private boolean isBeanMutator(Method method)
-  {
-    if (Modifier.isStatic(method.getModifiers()))
-      return false;
-    if (!method.getReturnType().equals(void.class))
-      return false;
-    if (method.getParameterCount() != 1)
-      return false;
-    if (method.getName().length() < 4)
-      return false;
-    return (method.getName().startsWith("set"));
-  }
-
-//</editor-fold>
-//<editor-fold desc="inner" defaultstate="collapsed">
-  private class Destroyer
-  {
-
-    final int BLOCK_SIZE = 1024;
-    long[] queue = new long[BLOCK_SIZE];
-    int index = 0;
-
-    void add(long v)
-    {
-      if (v == 0)
-        return;
-      queue[index++] = v;
-      if (index == BLOCK_SIZE)
-        flush();
-    }
-
-    void add(long[] v)
-    {
-      if (v == null)
-        return;
-      if (v.length > BLOCK_SIZE / 2)
-      {
-        typeFactory.destroy(context, v, v.length);
-        return;
-      }
-      if (index + v.length > BLOCK_SIZE)
-      {
-        flush();
-      }
-      for (int j = 0; j < v.length; ++j)
-      {
-        queue[index++] = v[j];
-      }
-      if (index == BLOCK_SIZE)
-        flush();
-    }
-
-    void flush()
-    {
-      typeFactory.destroy(context, queue, index);
-      index = 0;
-    }
-  }
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/pickle/ByteBufferInputStream.java 1.6.0-1/native/java/org/jpype/pickle/ByteBufferInputStream.java
--- 1.5.0-1/native/java/org/jpype/pickle/ByteBufferInputStream.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/pickle/ByteBufferInputStream.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,112 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.pickle;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-/**
- *
- * @author Karl Einar Nelson
- */
-public class ByteBufferInputStream extends InputStream
-{
-
-  ByteBuffer bb = ByteBuffer.allocate(1024);
-  int loaded = 0;
-
-  public void put(byte[] bytes)
-  {
-    // If we have additional capacity, use it
-    if (bytes.length < bb.remaining())
-    {
-      int p = bb.position();
-      bb.position(loaded);
-      bb.put(bytes);
-      loaded = bb.position();
-      bb.position(p);
-      return;
-    }
-
-    // Okay we may need to allocate more
-    ByteBuffer bb2 = bb;
-    int r = loaded - bb.position();
-
-    // If we don't have space, make a new buffer.
-    if (r + bytes.length > bb.capacity())
-      bb = ByteBuffer.allocate(r + bytes.length);
-
-    // If we have remaining bytes, then keep them
-    if (r > 0)
-    {
-      bb.put(bb2.array(), bb2.position(), r);
-    }
-
-    // Add the new data
-    bb.put(bytes);
-    loaded = bb.position();
-    bb.position(0);
-  }
-
-  public int available()
-  {
-    int out = loaded - bb.position();
-    return out;
-  }
-
-  @Override
-  public int read() throws IOException
-  {
-    int r = loaded - bb.position();
-    if (r > 0)
-    {
-      int p = bb.get();
-      return p;
-    }
-    return -1;
-  }
-
-  @Override
-  public int read(byte[] arg0) throws IOException
-  {
-    int r = loaded - bb.position();
-    if (arg0.length <= r)
-    {
-      bb.get(arg0);
-      return arg0.length;
-    }
-
-    bb.get(arg0, 0, r);
-    return r;
-  }
-
-  @Override
-  public int read(byte[] buffer, int offset, int len) throws IOException
-  {
-    int r = loaded - bb.position();
-    if (r == 0)
-      return -1;
-
-    if (len > r)
-    {
-      len = r;
-    }
-
-    bb.get(buffer, offset, len);
-    return len;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/pickle/Decoder.java 1.6.0-1/native/java/org/jpype/pickle/Decoder.java
--- 1.5.0-1/native/java/org/jpype/pickle/Decoder.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/pickle/Decoder.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,39 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.pickle;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-
-public class Decoder
-{
-
-  ByteBufferInputStream bb;
-  ObjectInputStream ois = null;
-
-  public Decoder() throws IOException
-  {
-    bb = new ByteBufferInputStream();
-  }
-
-  public Object unpack(byte[] data) throws IOException, ClassNotFoundException
-  {
-    bb.put(data);
-    if (ois == null)
-      ois = new ObjectInputStream(bb);
-    return ois.readObject();
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/pickle/Encoder.java 1.6.0-1/native/java/org/jpype/pickle/Encoder.java
--- 1.5.0-1/native/java/org/jpype/pickle/Encoder.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/pickle/Encoder.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,46 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.pickle;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-
-/**
- *
- * @author Karl Einar Nelson
- */
-public class Encoder
-{
-
-  ByteArrayOutputStream baos;
-  ObjectOutputStream oos;
-
-  public Encoder() throws IOException
-  {
-    baos = new ByteArrayOutputStream();
-    oos = new ObjectOutputStream(baos);
-  }
-
-  public byte[] pack(Object obj) throws IOException
-  {
-    oos.writeObject(obj);
-    oos.flush();
-    byte[] out = baos.toByteArray().clone();
-    baos.reset();
-    return out;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/pkg/JPypePackage.java 1.6.0-1/native/java/org/jpype/pkg/JPypePackage.java
--- 1.5.0-1/native/java/org/jpype/pkg/JPypePackage.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/pkg/JPypePackage.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,235 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.pkg;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Modifier;
-import java.net.URI;
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Map;
-import org.jpype.JPypeContext;
-import org.jpype.JPypeKeywords;
-import org.jpype.classloader.DynamicClassLoader;
-
-/**
- * Representation of a JPackage in Java.
- *
- * This provides the dir and attributes for a JPackage and by extension jpype
- * imports. Almost all of the actual work happens in the PackageManager which
- * acts like the classloader to figure out what resource are available.
- *
- */
-public class JPypePackage
-{
-
-  // Name of the package
-  final String pkg;
-  // A mapping from Python names into Paths into the module/jar file system.
-  Map<String, URI> contents;
-  int code;
-  private final DynamicClassLoader classLoader;
-
-  public JPypePackage(String pkg)
-  {
-    this.pkg = pkg;
-    this.contents = JPypePackageManager.getContentMap(pkg);
-    this.classLoader = ((DynamicClassLoader)(JPypeContext.getInstance().getClassLoader()));
-    this.code = classLoader.getCode();
-  }
-
-  /**
-   * Get an object from the package.
-   *
-   * This is used by the importer to create the attributes for `getattro`. The
-   * type returned is polymorphic. We can potentially support any type of
-   * resource (package, classes, property files, xml, data, etc). But for now we
-   * are primarily interested in packages and classes. Packages are returned as
-   * strings as loading the package info is not guaranteed to work. Classes are
-   * returned as classes which are immediately converted into Python wrappers.
-   * We can return other resource types so long as they have either a wrapper
-   * type to place the instance into an Python object directly or a magic
-   * wrapper which will load the resource into a Python object type.
-   *
-   * This should match the acceptable types in getContents so that everything in
-   * the `dir` is also an attribute of JPackage.
-   *
-   * @param name is the name of the resource.
-   * @return the object or null if no resource is found with a matching name.
-   */
-  public Object getObject(String name)
-  {
-    // We can't use the url contents as the contents may be incomplete due
-    // to bugs in the JVM classloaders.  Instead we will have to probe.
-    String basename = pkg + "." + JPypeKeywords.unwrap(name);
-    ClassLoader cl = JPypeContext.getInstance().getClassLoader();
-    try
-    {
-      // Check if it a package
-      if (JPypePackageManager.isPackage(basename))
-      {
-        return basename;
-      }
-      
-      // Else probe for a class.
-      Class<?> cls = Class.forName(basename, false, JPypeContext.getInstance().getClassLoader());
-      if (Modifier.isPublic(cls.getModifiers()))
-        return Class.forName(basename, true, cl);
-    } catch (ClassNotFoundException ex)
-    {
-      // Continue
-    }
-    return null;
-  }
-
-  /**
-   * Get a list of contents from a Java package.
-   *
-   * This will be used when creating the package `dir`
-   *
-   * @return
-   */
-  public String[] getContents()
-  {
-    checkCache();
-    ArrayList<String> out = new ArrayList<>();
-    for (String key : contents.keySet())
-    {
-      URI uri = contents.get(key);
-      // If there is anything null, then skip it.
-      if (uri == null)
-        continue;
-      Path p = JPypePackageManager.getPath(uri);
-
-      // package are acceptable
-      if (Files.isDirectory(p))
-        out.add(key);
-
-      // classes must be public
-      else if (uri.toString().endsWith(".class"))
-      {
-        // Make sure it is public
-        if (isPublic(p))
-          out.add(key);
-      }
-    }
-    return out.toArray(new String[out.size()]);
-  }
-
-  /**
-   * Determine if a class is public.
-   *
-   * This checks if a class file contains a public class. When importing classes
-   * we do not want to instantiate a class which is not public as it may result
-   * in instantiation of static variables or unwanted class resources. The only
-   * alternative is to read the class file and get the class modifier flags.
-   * Unfortunately, the developers of Java were rather stingy on their byte
-   * allocation and thus the field we want is not in the header but rather
-   * buried after the constant pool. Further as they didn't give the actual size
-   * of the tables in bytes, but rather in entries, that means we have to parse
-   * the whole table just to get the access flags after it.
-   *
-   * @param p
-   * @return
-   */
-  static boolean isPublic(Path p)
-  {
-    try (InputStream is = Files.newInputStream(p))
-    {
-      // Allocate a three byte buffer for traversing the constant pool.
-      // The minumum entry is a byte for the type and 2 data bytes.  We
-      // will read these three bytes and then based on the type advance
-      // the read pointer to the next entry.
-      ByteBuffer buffer3 = ByteBuffer.allocate(3);
-
-      // Check the magic
-      ByteBuffer header = ByteBuffer.allocate(4 + 2 + 2 + 2);
-      is.read(header.array());
-      ((Buffer) header).rewind();
-      int magic = header.getInt();
-      if (magic != (int) 0xcafebabe)
-        return false;
-      header.getShort(); // skip major
-      header.getShort(); // skip minor
-      short cpitems = header.getShort(); // get the number of items
-
-      // Traverse the cp pool
-      for (int i = 0; i < cpitems - 1; ++i)
-      {
-        is.read(buffer3.array());
-        ((Buffer) buffer3).rewind();
-        byte type = buffer3.get(); // First byte is the type
-
-        // Now based on the entry type we will advance the pointer
-        switch (type)
-        {
-          case 1:  // Strings are variable length
-            is.skip(buffer3.getShort());
-            break;
-          case 7:
-          case 8:
-          case 16:
-          case 19:
-          case 20:
-            break;
-          case 15:
-            is.skip(1);
-            break;
-          case 3:
-          case 4:
-          case 9:
-          case 10:
-          case 11:
-          case 12:
-          case 17:
-          case 18:
-            is.skip(2);
-            break;
-          case 5:
-          case 6:
-            is.skip(6); // double and long are special as they are double entries
-            i++; // long and double take two slots
-            break;
-          default:
-            return false;
-        }
-      }
-
-      // Get the flags
-      is.read(buffer3.array());
-      ((Buffer) buffer3).rewind();
-      short flags = buffer3.getShort();
-      return (flags & 1) == 1; // it is public if bit zero is set
-    } catch (IOException ex)
-    {
-      return false; // If anything goes wrong then it won't be considered a public class.
-    }
-  }
-  
-  void checkCache()
-  {
-    int current = classLoader.getCode();
-    if (this.code == current)
-      return;
-    this.code = current;
-    this.contents = JPypePackageManager.getContentMap(pkg);
-  }
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/pkg/JPypePackageManager.java 1.6.0-1/native/java/org/jpype/pkg/JPypePackageManager.java
--- 1.5.0-1/native/java/org/jpype/pkg/JPypePackageManager.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/pkg/JPypePackageManager.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,470 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.pkg;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.ProviderNotFoundException;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import org.jpype.JPypeContext;
-import org.jpype.JPypeKeywords;
-
-/**
- * Manager for the contents of a package.
- *
- * This class uses a number of tricks to provide a way to determine what
- * packages are available in the class loader. It searches the jar path, the
- * boot path (Java 8), and the module path (Java 9+). It does not currently work
- * with alternative classloaders. This class was rumored to be unobtainium as
- * endless posts indicated that it wasn't possible to determine the contents of
- * a package in general nor to retrieve the package contents, but this appears
- * to be largely incorrect as the jar and jrt file system provide all the
- * required methods.
- *
- */
-public class JPypePackageManager
-{
-
-  final static List<FileSystem> bases = new ArrayList();
-  final static List<ModuleDirectory> modules = getModules();
-  final static FileSystemProvider jfsp = getFileSystemProvider("jar");
-  final static Map<String, String> env = new HashMap<>();
-  final static LinkedList<FileSystem> fs = new LinkedList<>();
-
-  /**
-   * Checks if a package exists.
-   *
-   * @param name is the name of the package.
-   * @return true if this is a Java package either in a jar, module, or in the
-   * boot path.
-   */
-  public static boolean isPackage(String name)
-  {
-    if (name.indexOf('.') != -1)
-      name = name.replace(".", "/");
-    if (isModulePackage(name) || isBasePackage(name) || isJarPackage(name))
-      return true;
-    return false;
-  }
-
-  /**
-   * Get the list of the contents of a package.
-   *
-   * @param packageName
-   * @return the list of all resources found.
-   */
-  public static Map<String, URI> getContentMap(String packageName)
-  {
-    Map<String, URI> out = new HashMap<>();
-    packageName = packageName.replace(".", "/");
-    // We need to merge all the file systems into one view like the classloader
-    getJarContents(out, packageName);
-    getBaseContents(out, packageName);
-    getModuleContents(out, packageName);
-    return out;
-  }
-
-  /**
-   * Convert a URI into a path.
-   *
-   * This has special magic methods to deal with jar file systems.
-   *
-   * @param uri is the location of the resource.
-   * @return the path to the uri resource.
-   */
-  static Path getPath(URI uri)
-  {
-    try
-    {
-      return Paths.get(uri);
-    } catch (java.nio.file.FileSystemNotFoundException ex)
-    {
-    }
-
-    if (uri.getScheme().equals("jar"))
-    {
-      try
-      {
-        // Limit the number of filesystems open at any one time
-        fs.add(jfsp.newFileSystem(uri, env));
-        if (fs.size() > 8)
-          fs.removeFirst().close();
-        return Paths.get(uri);
-      } catch (IOException ex)
-      {
-      }
-    }
-    throw new FileSystemNotFoundException("Unknown filesystem for " + uri);
-  }
-
-  /**
-   * Retrieve the Jar file system.
-   *
-   * @return
-   */
-  private static FileSystemProvider getFileSystemProvider(String str)
-  {
-    for (FileSystemProvider fsp : FileSystemProvider.installedProviders())
-    {
-      if (fsp.getScheme().equals(str))
-        return fsp;
-    }
-    throw new FileSystemNotFoundException("Unable to find filesystem for " + str);
-  }
-
-//<editor-fold desc="java 8" defaultstate="collapsed">
-  /**
-   * Older versions of Java do not have a file system for boot packages. Thus
-   * rather working through the classloader, we will instead probe java to get
-   * the rt.jar. Crypto is a special case as it has its own jar. All other
-   * resources are sourced through the regular jar loading method.
-   */
-  static
-  {
-    env.put("create", "true");
-
-    ClassLoader cl = ClassLoader.getSystemClassLoader();
-    URI uri = null;
-    try
-    {
-      // This is for Java 8 and earlier in which the API jars are in rt.jar
-      // and jce.jar
-      uri = cl.getResource("java/lang/String.class").toURI();
-      if (uri != null && uri.getScheme().equals("jar"))
-      {
-        FileSystem fs = jfsp.newFileSystem(uri, env);
-        if (fs != null)
-          bases.add(fs);
-      }
-      uri = cl.getResource("javax/crypto/Cipher.class").toURI();
-      if (uri != null && uri.getScheme().equals("jar"))
-      {
-        FileSystem fs = jfsp.newFileSystem(uri, env);
-        if (fs != null)
-          bases.add(fs);
-      }
-    } catch (URISyntaxException | IOException ex)
-    {
-    }
-  }
-
-  private static void getBaseContents(Map<String, URI> out, String packageName)
-  {
-    for (FileSystem b : bases)
-    {
-      collectContents(out, b.getPath(packageName));
-    }
-  }
-
-  /**
-   * Check if a name is a package in the java bootstrap classloader.
-   *
-   * @param name
-   * @return
-   */
-  private static boolean isBasePackage(String name)
-  {
-    try
-    {
-      if (name.isEmpty())
-        return false;
-      for (FileSystem jar : bases)
-      {
-        if (Files.isDirectory(jar.getPath(name)))
-          return true;
-      }
-      return false;
-    } catch (Exception ex)
-    {
-      throw new RuntimeException("Fail checking package '" + name + "'", ex);
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="java 9" defaultstate="collapsed">
-  /**
-   * Get a list of all modules.
-   *
-   * This may be many modules or just a few. Limited distributes created using
-   * jlink will only have a portion of the usual modules.
-   *
-   * @return
-   */
-  static List<ModuleDirectory> getModules()
-  {
-    ArrayList<ModuleDirectory> out = new ArrayList<>();
-    try
-    {
-      FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
-      Path modulePath = fs.getPath("modules");
-      for (Path module : Files.newDirectoryStream(modulePath))
-      {
-        out.add(new ModuleDirectory(module));
-      }
-    } catch (ProviderNotFoundException | IOException ex)
-    {
-    }
-    return out;
-  }
-
-  /**
-   * Check if a name corresponds to a package in a module.
-   *
-   * @param name
-   * @return true if it is a package.
-   */
-  private static boolean isModulePackage(String name)
-  {
-    if (modules.isEmpty())
-      return false;
-    String[] split = name.split("/");
-    String search = name;
-    if (split.length > 3)
-      search = String.join("/", Arrays.copyOfRange(split, 0, 3));
-    for (ModuleDirectory module : modules)
-    {
-      if (module.contains(search))
-      {
-        if (Files.isDirectory(module.modulePath.resolve(name)))
-          return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Retrieve the contents of a module by package name.
-   *
-   * @param out
-   * @param name
-   */
-  private static void getModuleContents(Map<String, URI> out, String name)
-  {
-    if (modules.isEmpty())
-      return;
-    String[] split = name.split("/");
-    String search = name;
-    if (split.length > 3)
-      search = String.join("/", Arrays.copyOfRange(split, 0, 3));
-    for (ModuleDirectory module : modules)
-    {
-      if (module.contains(search))
-      {
-        Path path2 = module.modulePath.resolve(name);
-        if (Files.isDirectory(path2))
-          collectContents(out, path2);
-      }
-    }
-  }
-
-  /**
-   * Modules are stored in the jrt filesystem.
-   *
-   * However, that is not a simple flat filesystem by path as the jrt files are
-   * structured by package name. Thus we will need a separate structure which is
-   * rooted at the top of each module.
-   */
-  private static class ModuleDirectory
-  {
-
-    List<String> contents = new ArrayList<>();
-    private final Path modulePath;
-
-    ModuleDirectory(Path module)
-    {
-      this.modulePath = module;
-      listPackages(contents, module, module, 0);
-    }
-
-    boolean contains(String path)
-    {
-      for (String s : contents)
-      {
-        if (s.equals(path))
-          return true;
-      }
-      return false;
-    }
-
-    private static void listPackages(List<String> o, Path base, Path p, int depth)
-    {
-      try
-      {
-        if (depth >= 3)
-          return;
-        for (Path d : Files.newDirectoryStream(p))
-        {
-          if (Files.isDirectory(d))
-          {
-            o.add(base.relativize(d).toString());
-            listPackages(o, base, d, depth + 1);
-          }
-        }
-      } catch (IOException ex)
-      {
-      }
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="jar" defaultstate="collapsed">
-  /**
-   * Checks if a name corresponds to package in a jar file or on the classpath
-   * filesystem.
-   *
-   * Classloaders provide a method to get all resources with a given name. This
-   * is needed because the same package name may appear in multiple jars or
-   * filesystems. We do not need to disambiguate it here, but just get a listing
-   * that we can use to find a resource later.
-   *
-   * @param name is the name of the package to search for.
-   * @return true if the name corresponds to a Java package.
-   */
-  private static boolean isJarPackage(String name)
-  {
-    ClassLoader cl = JPypeContext.getInstance().getClassLoader();
-    try
-    {
-      Enumeration<URL> resources = cl.getResources(name);
-      while (resources.hasMoreElements())
-      {
-        URI uri = resources.nextElement().toURI();
-        if (Files.isDirectory(getPath(uri)))
-          return true;
-      }
-    } catch (IOException | URISyntaxException ex)
-    {
-    }
-    return false;
-  }
-
-  /**
-   * Retrieve a list of packages and classes stored on a file system or in a
-   * jar.
-   *
-   * @param out is the map to store the result in.
-   * @param packageName is the name of the package
-   */
-  private static void getJarContents(Map<String, URI> out, String packageName)
-  {
-    ClassLoader cl = JPypeContext.getInstance().getClassLoader();
-    try
-    {
-      String path = packageName.replace('.', '/');
-      Enumeration<URL> resources = cl.getResources(path);
-      while (resources.hasMoreElements())
-      {
-        URI resource = resources.nextElement().toURI();
-
-        // Handle MRJAR format
-        //   MRJAR may not report every directory but instead just the overlay.
-        //   So we need to find the original and interogate it first before
-        //   checking the version specific part.  Worst case we collect the
-        //   contents twice.
-        String schemePart = resource.getSchemeSpecificPart();
-        int index = schemePart.indexOf("!");
-        if (index != -1)
-        {
-          if (schemePart.substring(index + 1).startsWith("/META-INF/versions/"))
-          {
-            int index2 = schemePart.indexOf('/', index + 20);
-            schemePart = schemePart.substring(0, index + 1) + schemePart.substring(index2);
-            URI resource2 = new URI(resource.getScheme() + ":" + schemePart);
-            Path path3 = getPath(resource2);
-            collectContents(out, path3);
-          }
-        }
-        
-        Path path2 = getPath(resource);
-        collectContents(out, path2);
-      }
-    } catch (IOException | URISyntaxException ex)
-    {
-    }
-  }
-
-//</editor-fold>
-//<editor-fold desc="utility" defaultstate="collapsed">
-  /**
-   * Collect the contents from a path.
-   *
-   * This operates on jars, modules, and filesystems to collect the names of all
-   * resources found. We skip over inner classes as those are accessed under
-   * their included classes. For now we are not screening against other private
-   * symbols.
-   *
-   * @param out is the map to store the result in.
-   * @param path2 is a path holding a directory to probe.
-   */
-  private static void collectContents(Map<String, URI> out, Path path2)
-  {
-    try
-    {
-      for (Path file : Files.newDirectoryStream(path2))
-      {
-        String filename = file.getFileName().toString();
-        if (Files.isDirectory(file))
-        {
-          // Same implementations add the path separator to the end of toString().
-          if (filename.endsWith(file.getFileSystem().getSeparator()))
-            filename = filename.substring(0, filename.length() - 1);
-          out.put(JPypeKeywords.wrap(filename), toURI(file));
-          continue;
-        }
-        // Skip inner classes
-        if (filename.contains("$"))
-          continue;
-
-        // Include class files
-        if (filename.endsWith(".class"))
-        {
-          String key = JPypeKeywords.wrap(filename.substring(0, filename.length() - 6));
-          out.put(key, toURI(file));
-        }
-
-        // We can add other types of files here and import them in JPypePackage
-        // as required.
-      }
-    } catch (IOException ex)
-    {
-    }
-  }
-
-  // Java 8 windows bug https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8131067
-  private static URI toURI(Path path)
-  {
-    URI uri = path.toUri();
-    if (uri.getScheme().equals("jar") && uri.toString().contains("%2520"))
-      uri = URI.create("jar:" + uri.getRawSchemeSpecificPart().replaceAll("%25", "%"));
-    return uri;
-  }
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/proxy/JPypeProxy.java 1.6.0-1/native/java/org/jpype/proxy/JPypeProxy.java
--- 1.5.0-1/native/java/org/jpype/proxy/JPypeProxy.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/proxy/JPypeProxy.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,103 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.proxy;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import org.jpype.JPypeContext;
-import org.jpype.manager.TypeManager;
-import org.jpype.ref.JPypeReferenceQueue;
-
-/**
- *
- * @author Karl Einar Nelson
- */
-public class JPypeProxy implements InvocationHandler
-{
-
-  private final static JPypeReferenceQueue referenceQueue = JPypeReferenceQueue.getInstance();
-  JPypeContext context;
-  public long instance;
-  public long cleanup;
-  Class<?>[] interfaces;
-  ClassLoader cl = ClassLoader.getSystemClassLoader();
-
-  public static JPypeProxy newProxy(JPypeContext context,
-          long instance,
-          long cleanup,
-          Class<?>[] interfaces)
-  {
-    JPypeProxy proxy = new JPypeProxy();
-    proxy.context = context;
-    proxy.instance = instance;
-    proxy.interfaces = interfaces;
-    proxy.cleanup = cleanup;
-    // Proxies must point to the correct class loader.  For most cases the
-    // system classloader is find.  But if the class is in a custom classloader
-    // we need to use that one instead
-    for (Class cls : interfaces)
-    {
-      ClassLoader icl = cls.getClassLoader();
-      if (icl != null && icl != proxy.cl)
-        proxy.cl = icl;
-    }
-    return proxy;
-  }
-
-  public Object newInstance()
-  {
-    Object out = Proxy.newProxyInstance(cl, interfaces, this);
-    referenceQueue.registerRef(out, instance, cleanup);
-    return out;
-  }
-
-  @Override
-  public Object invoke(Object proxy, Method method, Object[] args)
-          throws Throwable
-  {
-    try
-    {
-//      context.incrementProxy();
-      if (context.isShutdown())
-        throw new RuntimeException("Proxy called during shutdown");
-
-      // We can save a lot of effort on the C++ side by doing all the
-      // type lookup work here.
-      TypeManager typeManager = context.getTypeManager();
-      long returnType;
-      long[] parameterTypes;
-      synchronized (typeManager)
-      {
-        returnType = typeManager.findClass(method.getReturnType());
-        Class<?>[] types = method.getParameterTypes();
-        parameterTypes = new long[types.length];
-        for (int i = 0; i < types.length; ++i)
-        {
-          parameterTypes[i] = typeManager.findClass(types[i]);
-        }
-      }
-
-      return hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args);
-    } finally
-    {
-//      context.decrementProxy();
-    }
-  }
-
-  private static native Object hostInvoke(long context, String name, long pyObject,
-          long returnType, long[] argsTypes, Object[] args);
-}
diff -pruN 1.5.0-1/native/java/org/jpype/ref/JPypeReference.java 1.6.0-1/native/java/org/jpype/ref/JPypeReference.java
--- 1.5.0-1/native/java/org/jpype/ref/JPypeReference.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/ref/JPypeReference.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,55 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.ref;
-
-import java.lang.ref.PhantomReference;
-import java.lang.ref.ReferenceQueue;
-
-/**
- * (internal) Reference to a PyObject*.
- */
-class JPypeReference extends PhantomReference
-{
-
-  long hostReference;
-  long cleanup;
-  int pool;
-  int index;
-
-  public JPypeReference(ReferenceQueue arg1, Object javaObject, long host, long cleanup)
-  {
-    super(javaObject, arg1);
-    this.hostReference = host;
-    this.cleanup = cleanup;
-  }
-
-  @Override
-  public int hashCode()
-  {
-    return (int) hostReference;
-  }
-
-  @Override
-  public boolean equals(Object arg0)
-  {
-    if (!(arg0 instanceof JPypeReference))
-    {
-      return false;
-    }
-
-    return ((JPypeReference) arg0).hostReference == hostReference;
-  }
-}
diff -pruN 1.5.0-1/native/java/org/jpype/ref/JPypeReferenceNative.java 1.6.0-1/native/java/org/jpype/ref/JPypeReferenceNative.java
--- 1.5.0-1/native/java/org/jpype/ref/JPypeReferenceNative.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/ref/JPypeReferenceNative.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,33 +0,0 @@
-package org.jpype.ref;
-
-import java.lang.reflect.Method;
-
-/**
- *
- * @author nelson85
- */
-public class JPypeReferenceNative
-{
-
-  /**
-   * Native hook to delete a native resource.
-   *
-   * @param host is the address of memory in C.
-   * @param cleanup is the address the function to cleanup the memory.
-   */
-  public static native void removeHostReference(long host, long cleanup);
-
-  /**
-   * Triggered by the sentinel when a GC starts.
-   */
-  public static native void wake();
-
-  /**
-   * Initialize resources.
-   *
-   * @param self
-   * @param m
-   */
-  public static native void init(Object self, Method m);
-
-}
diff -pruN 1.5.0-1/native/java/org/jpype/ref/JPypeReferenceQueue.java 1.6.0-1/native/java/org/jpype/ref/JPypeReferenceQueue.java
--- 1.5.0-1/native/java/org/jpype/ref/JPypeReferenceQueue.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/ref/JPypeReferenceQueue.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,196 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.ref;
-
-import java.lang.ref.PhantomReference;
-import java.lang.ref.ReferenceQueue;
-
-/**
- * Reference queue holds the life of python objects to be as long as java items.
- * <p>
- * Any java class that holds a pointer into python needs to have a reference so
- * that python object does not go away if the references in python fall to zero.
- * JPype will add an extra reference to the object which is removed when the
- * python object dies.
- *
- * @author smenard
- *
- */
-final public class JPypeReferenceQueue extends ReferenceQueue
-{
-
-  private final static JPypeReferenceQueue INSTANCE = new JPypeReferenceQueue();
-  private JPypeReferenceSet hostReferences;
-  private boolean isStopped = false;
-  private Thread queueThread;
-  private Object queueStopMutex = new Object();
-  private PhantomReference sentinel = null;
-
-  public static JPypeReferenceQueue getInstance()
-  {
-    return INSTANCE;
-  }
-
-  private JPypeReferenceQueue()
-  {
-    super();
-    this.hostReferences = new JPypeReferenceSet();
-    addSentinel();
-    JPypeReferenceNative.removeHostReference(0, 0);
-    try
-    {
-      JPypeReferenceNative.init(this, getClass().getDeclaredMethod("registerRef", Object.class, Long.TYPE, Long.TYPE));
-    } catch (NoSuchMethodException | SecurityException ex)
-    {
-      throw new RuntimeException(ex);
-    }
-  }
-
-  /**
-   * (internal) Binds the lifetime of a Python object to a Java object.
-   * <p>
-   * JPype adds an extra reference to a PyObject* and then calls this method to
-   * hold that reference until the Java object is garbage collected.
-   *
-   * @param javaObject is the object to bind the lifespan to.
-   * @param host is the pointer to the host object.
-   * @param cleanup is the pointer to the function to call to delete the
-   * resource.
-   */
-  public void registerRef(Object javaObject, long host, long cleanup)
-  {
-    if (cleanup == 0)
-      return;
-    if (isStopped)
-    {
-      JPypeReferenceNative.removeHostReference(host, cleanup);
-    } else
-    {
-      JPypeReference ref = new JPypeReference(this, javaObject, host, cleanup);
-      hostReferences.add(ref);
-    }
-  }
-
-  /**
-   * Start the threading queue.
-   */
-  public void start()
-  {
-    isStopped = false;
-    queueThread = new Thread(new Worker(), "Python Reference Queue");
-    queueThread.setDaemon(true);
-    queueThread.start();
-  }
-
-  /**
-   * Stops the reference queue.
-   * <p>
-   * This is called by jpype when the jvm shuts down.
-   */
-  public void stop()
-  {
-    try
-    {
-      synchronized (queueStopMutex)
-      {
-        synchronized (this)
-        {
-          isStopped = true;
-          queueThread.interrupt();
-        }
-
-        // wait for the thread to finish ...
-        queueStopMutex.wait(10000);
-      }
-    } catch (InterruptedException ex)
-    {
-      // who cares ...
-    }
-
-    // Empty the queue.
-    hostReferences.flush();
-  }
-
-  /**
-   * Checks the status of the reference queue.
-   *
-   * @return true is the queue is running.
-   */
-  public boolean isRunning()
-  {
-    return !isStopped;
-  }
-
-  /**
-   * Get the number of items in the reference queue.
-   *
-   * @return the number of python resources held.
-   */
-  public int getQueueSize()
-  {
-    return this.hostReferences.size();
-  }
-
-//<editor-fold desc="internal" defaultstate="collapsed">
-  /**
-   * Thread to monitor the queue and delete resources.
-   */
-  private class Worker implements Runnable
-  {
-
-    @Override
-    public void run()
-    {
-      while (!isStopped)
-      {
-        try
-        {
-          // Check if a ref has been queued. and check if the thread has been
-          // stopped every 0.25 seconds
-          JPypeReference ref = (JPypeReference) remove(250);
-          if (ref == sentinel)
-          {
-            addSentinel();
-            JPypeReferenceNative.wake();
-            continue;
-          }
-          if (ref != null)
-          {
-            long hostRef = ref.hostReference;
-            long cleanup = ref.cleanup;
-            hostReferences.remove(ref);
-            JPypeReferenceNative.removeHostReference(hostRef, cleanup);
-          }
-        } catch (InterruptedException ex)
-        {
-          // don't know why ... don't really care ...
-        }
-      }
-
-      synchronized (queueStopMutex)
-      {
-        queueStopMutex.notifyAll();
-      }
-    }
-  }
-
-  final void addSentinel()
-  {
-    sentinel = new JPypeReference(this, new byte[0], 0, 0);
-  }
-
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/java/org/jpype/ref/JPypeReferenceSet.java 1.6.0-1/native/java/org/jpype/ref/JPypeReferenceSet.java
--- 1.5.0-1/native/java/org/jpype/ref/JPypeReferenceSet.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/java/org/jpype/ref/JPypeReferenceSet.java	1970-01-01 00:00:00.000000000 +0000
@@ -1,148 +0,0 @@
-/* ****************************************************************************
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  
-  See NOTICE file for details.
-**************************************************************************** */
-package org.jpype.ref;
-
-import java.util.ArrayList;
-
-/**
- *
- * @author nelson85
- */
-public class JPypeReferenceSet
-{
-
-  static final int SIZE = 256;
-  ArrayList<Pool> pools = new ArrayList<>();
-  Pool current;
-  private int items;
-
-  JPypeReferenceSet()
-  {
-  }
-
-  int size()
-  {
-    return items;
-  }
-
-  /**
-   * Add a reference to the set.
-   *
-   * This should be O(1).
-   *
-   * @param ref
-   */
-  synchronized void add(JPypeReference ref)
-  {
-    if (ref.cleanup == 0)
-      return;
-
-    this.items++;
-    if (current == null)
-    {
-      current = new Pool(pools.size());
-      pools.add(current);
-    }
-
-    if (current.add(ref))
-    {
-      // It is full
-      current = null;
-
-      // Find a free pool
-      for (Pool pool : pools)
-      {
-        if (pool.tail < SIZE)
-        {
-          current = pool;
-          return;
-        }
-      }
-    }
-  }
-
-  /**
-   * Remove a reference from the set.
-   *
-   * @param ref
-   */
-  synchronized void remove(JPypeReference ref)
-  {
-    if (ref.cleanup == 0)
-      return;
-    pools.get(ref.pool).remove(ref);
-    this.items--;
-    ref.cleanup = 0;
-    ref.pool = -1;
-  }
-
-  /**
-   * Release all resources.
-   *
-   * This is triggered by shutdown to release an current Python references that
-   * are being held.
-   *
-   */
-  void flush()
-  {
-    for (Pool pool : pools)
-    {
-      for (int i = 0; i < pool.tail; ++i)
-      {
-        JPypeReference ref = pool.entries[i];
-        long hostRef = ref.hostReference;
-        long cleanup = ref.cleanup;
-        // This is a sanity check to prevent calling a cleanup with a null
-        // pointer, it would only occur if we failed to manage a deleted
-        // item.
-        if (cleanup == 0)
-          continue;
-        ref.cleanup = 0;
-        JPypeReferenceNative.removeHostReference(hostRef, cleanup);
-      }
-      pool.tail = 0;
-    }
-  }
-
-//<editor-fold desc="internal" defaultstate="collapsed">
-  static class Pool
-  {
-
-    JPypeReference[] entries = new JPypeReference[SIZE];
-    int tail;
-    int id;
-
-    Pool(int id)
-    {
-      this.id = id;
-    }
-
-    boolean add(JPypeReference ref)
-    {
-      ref.pool = id;
-      ref.index = tail;
-      entries[tail++] = ref;
-      return (tail == entries.length);
-    }
-
-    void remove(JPypeReference ref)
-    {
-      entries[ref.index] = entries[--tail];
-      entries[ref.index].index = ref.index;
-    }
-  }
-//</editor-fold>
-}
diff -pruN 1.5.0-1/native/jni_include/jni.h 1.6.0-1/native/jni_include/jni.h
--- 1.5.0-1/native/jni_include/jni.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/jni_include/jni.h	2025-06-01 03:57:43.000000000 +0000
@@ -1179,6 +1179,11 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm,
 #define JNI_VERSION_1_6 0x00010006
 #define JNI_VERSION_1_7 0x00010007
 #define JNI_VERSION_1_8 0x00010008
+#define JNI_VERSION_9   0x00090000
+#define JNI_VERSION_10  0x000a0000
+#define JNI_VERSION_19  0x00130000
+#define JNI_VERSION_20  0x00140000
+#define JNI_VERSION_21  0x00150000
 
 #define JNI_OK          (0)         /* no error */
 #define JNI_ERR         (-1)        /* generic error */
diff -pruN 1.5.0-1/native/jpype_module/licenseheader.txt 1.6.0-1/native/jpype_module/licenseheader.txt
--- 1.5.0-1/native/jpype_module/licenseheader.txt	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/licenseheader.txt	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,19 @@
+<#if licenseFirst??>
+${licenseFirst}
+</#if>
+${licensePrefix} Licensed under the Apache License, Version 2.0 (the "License"); you may not
+${licensePrefix} use this file except in compliance with the License. You may obtain a copy of
+${licensePrefix} the License at
+${licensePrefix}
+${licensePrefix} http://www.apache.org/licenses/LICENSE-2.0
+${licensePrefix}
+${licensePrefix} Unless required by applicable law or agreed to in writing, software
+${licensePrefix} distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+${licensePrefix} WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+${licensePrefix} License for the specific language governing permissions and limitations under
+${licensePrefix} the License.
+${licensePrefix}
+${licensePrefix} See NOTICE file for details.
+<#if licenseLast??>
+${licenseLast}
+</#if>
\ No newline at end of file
diff -pruN 1.5.0-1/native/jpype_module/nb-configuration.xml 1.6.0-1/native/jpype_module/nb-configuration.xml
--- 1.5.0-1/native/jpype_module/nb-configuration.xml	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/nb-configuration.xml	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+    <!--
+This file contains additional configuration written by modules in the NetBeans IDE.
+The configuration is intended to be shared among all the users of project and
+therefore it is assumed to be part of version control checkout.
+Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+-->
+    <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+        <!--
+Properties that influence various parts of the IDE, especially code formatting and the like. 
+You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+That way multiple projects can share the same settings (useful for formatting rules for example).
+Any value defined here will override the pom.xml file value but is only applicable to the current project.
+-->
+        <org-netbeans-modules-editor-indent.text.x-toml.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-toml.CodeStyle.project.indent-shift-width>
+        <org-netbeans-modules-editor-indent.text.x-toml.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-toml.CodeStyle.project.spaces-per-tab>
+        <org-netbeans-modules-editor-indent.text.x-go.CodeStyle.project.indent-shift-width>4</org-netbeans-modules-editor-indent.text.x-go.CodeStyle.project.indent-shift-width>
+        <org-netbeans-modules-editor-indent.text.x-go.CodeStyle.project.tab-size>4</org-netbeans-modules-editor-indent.text.x-go.CodeStyle.project.tab-size>
+        <org-netbeans-modules-editor-indent.text.x-go.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.text.x-go.CodeStyle.project.expand-tabs>
+        <org-netbeans-modules-editor-indent.text.x-antlr3.CodeStyle.project.indent-shift-width>4</org-netbeans-modules-editor-indent.text.x-antlr3.CodeStyle.project.indent-shift-width>
+        <org-netbeans-modules-editor-indent.text.x-yaml.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-yaml.CodeStyle.project.indent-shift-width>
+        <org-netbeans-modules-editor-indent.text.x-antlr4.CodeStyle.project.indent-shift-width>4</org-netbeans-modules-editor-indent.text.x-antlr4.CodeStyle.project.indent-shift-width>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-line-wrap>none</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-line-wrap>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.enable-indent>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.enable-indent>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>8</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.moduleDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.moduleDeclBracePlacement>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>80</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
+        <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+        <org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>none</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+        <org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>4</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+        <org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>4</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+        <org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>8</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+        <org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>80</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+        <org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>true</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+        <org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+    </properties>
+</project-shared-configuration>
diff -pruN 1.5.0-1/native/jpype_module/nbactions.xml 1.6.0-1/native/jpype_module/nbactions.xml
--- 1.5.0-1/native/jpype_module/nbactions.xml	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/nbactions.xml	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<actions>
+        <action>
+            <actionName>run</actionName>
+            <packagings>
+                <packaging>jar</packaging>
+            </packagings>
+            <goals>
+                <goal>process-classes</goal>
+                <goal>org.codehaus.mojo:exec-maven-plugin:3.1.0:exec</goal>
+            </goals>
+            <properties>
+                <exec.vmArgs></exec.vmArgs>
+                <exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
+                <exec.appArgs></exec.appArgs>
+                <exec.mainClass>org.jpype.bridge.Bridge</exec.mainClass>
+                <exec.executable>java</exec.executable>
+                <exec.workingdir>../..</exec.workingdir>
+            </properties>
+        </action>
+        <action>
+            <actionName>debug</actionName>
+            <packagings>
+                <packaging>jar</packaging>
+            </packagings>
+            <goals>
+                <goal>process-classes</goal>
+                <goal>org.codehaus.mojo:exec-maven-plugin:3.1.0:exec</goal>
+            </goals>
+            <properties>
+                <exec.vmArgs>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address}</exec.vmArgs>
+                <exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
+                <exec.appArgs></exec.appArgs>
+                <exec.mainClass>${packageClassName}</exec.mainClass>
+                <exec.executable>java</exec.executable>
+                <jpda.listen>true</jpda.listen>
+                <exec.workingdir>../..</exec.workingdir>
+            </properties>
+        </action>
+        <action>
+            <actionName>profile</actionName>
+            <packagings>
+                <packaging>jar</packaging>
+            </packagings>
+            <goals>
+                <goal>process-classes</goal>
+                <goal>org.codehaus.mojo:exec-maven-plugin:3.1.0:exec</goal>
+            </goals>
+            <properties>
+                <exec.vmArgs></exec.vmArgs>
+                <exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
+                <exec.mainClass>${packageClassName}</exec.mainClass>
+                <exec.executable>java</exec.executable>
+                <exec.workingdir>../..</exec.workingdir>
+                <exec.appArgs></exec.appArgs>
+            </properties>
+        </action>
+    </actions>
diff -pruN 1.5.0-1/native/jpype_module/pom.xml 1.6.0-1/native/jpype_module/pom.xml
--- 1.5.0-1/native/jpype_module/pom.xml	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/pom.xml	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.jpype</groupId>
+    <artifactId>jpype</artifactId>
+    <version>1.6.0</version>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <version>6.8.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>5.17.0</version>
+            <type>jar</type>
+        </dependency>
+    </dependencies>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.release>21</maven.compiler.release>
+    </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.10.1</version>
+                <executions>
+                    <execution>
+                        <id>compile-single-class</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                        <configuration>
+                            <!-- Specify the source file to compile -->
+                            <includes>
+                                <include>exclude/org/jpype/Reflector0.java</include>
+                            </includes>
+                            <!-- Output directory for compiled class -->
+                            <outputDirectory>${project.build.directory}/classes/META-INF/versions/0</outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <name>jpype_module</name>
+</project>
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/exclude/org/jpype/Reflector0.java 1.6.0-1/native/jpype_module/src/main/java/exclude/org/jpype/Reflector0.java
--- 1.5.0-1/native/jpype_module/src/main/java/exclude/org/jpype/Reflector0.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/exclude/org/jpype/Reflector0.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,38 @@
+package org.jpype;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class Reflector0 implements JPypeReflector
+{
+
+  public Reflector0()
+  {}
+
+  /**
+   * Call a method using reflection.
+   *
+   * This method creates a stackframe so that caller sensitive methods will
+   * execute properly.
+   *
+   * @param method is the method to call.
+   * @param obj is the object to operate on, it will be null if the method is
+   * static.
+   * @param args the arguments to method.
+   * @return the object that results form the invocation.
+   * @throws java.lang.Throwable throws whatever type the called method
+   * produces.
+   */
+  public Object callMethod(Method method, Object obj, Object[] args)
+          throws Throwable
+  {
+    try
+    {
+      return method.invoke(obj, args);
+    } catch (InvocationTargetException ex)
+    {
+//      ex.printStackTrace();
+      throw ex.getCause();
+    }
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/manifest.txt 1.6.0-1/native/jpype_module/src/main/java/manifest.txt
--- 1.5.0-1/native/jpype_module/src/main/java/manifest.txt	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/manifest.txt	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Specification-Title: org.jpype
+Implementation-Title: org.jpype
+Implementation-Version: 1.5.1
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/module-info.java 1.6.0-1/native/jpype_module/src/main/java/module-info.java
--- 1.5.0-1/native/jpype_module/src/main/java/module-info.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/module-info.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,31 @@
+/*****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+
+
+module jpype {
+  requires java.xml;
+  requires java.sql;
+  requires java.management;
+  
+  exports org.jpype;
+  exports org.jpype.html;
+  exports org.jpype.javadoc;
+  exports org.jpype.manager;
+  exports org.jpype.pickle;
+  exports org.jpype.pkg;
+  exports org.jpype.proxy;
+  exports org.jpype.ref;
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeClassLoader.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeClassLoader.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeClassLoader.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeClassLoader.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,350 @@
+package org.jpype;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.net.URLDecoder;
+
+/**
+ * Class loader for JPype.
+ *
+ * This is augmented to manage directory resources, allow for late loading, and
+ * handling of resources on non-ASCII paths.
+ */
+public class JPypeClassLoader extends URLClassLoader
+{
+
+  HashMap<String, ArrayList<URL>> map = new HashMap<>();
+  int code = 0;
+
+  public JPypeClassLoader(ClassLoader parent)
+  {
+    super(initial(), parent);
+  }
+
+  /**
+   * Used to keep the cache up to date.
+   *
+   * @return
+   */
+  public int getCode()
+  {
+    return code;
+  }
+
+  /**
+   * Special routine for handling non-ascii paths.
+   *
+   * If we are loaded as the system ClassLoader, then we will use
+   * "jpype.class.path" rather than "java.class.path" during the load process.
+   * We will move it into the expected place after so no one is the wiser.
+   *
+   * @return
+   */
+  private static URL[] initial()
+  {
+    // Check to see if we have a late loaded path
+    String cp = System.getProperty("jpype.class.path");
+    if (cp == null)
+      return new URL[0];
+
+    try
+    {
+      cp = URLDecoder.decode(cp, "UTF-8");
+    } catch (UnsupportedEncodingException ex)
+    {
+      // ignored
+    }
+
+    ArrayList<URL> path = new ArrayList<>();
+    int last = 0;
+    int next = 0;
+
+    while (next != -1)
+    {
+      // Find the parts
+      next = cp.indexOf(File.pathSeparator, last);
+      String element = (next == -1) ? cp.substring(last) : cp.substring(last, next);
+      if (!element.isEmpty())
+      {
+        try
+        {
+          URL url = Paths.get(element).toUri().toURL();
+          if (url != null)
+            path.add(url);
+        } catch (MalformedURLException ex)
+        {
+          System.err.println("Malformed url in classpath skipped " + element);
+        }
+      }
+      last = next + 1;
+    }
+
+    // Replace the path
+    System.clearProperty("jpype.class.path");
+    System.setProperty("java.class.path", cp);
+    return path.toArray(new URL[0]);
+  }
+
+  // This is required to add a Java agent even if it is already in the path
+  @SuppressWarnings("unused")
+  private void appendToClassPathForInstrumentation(String path) throws Throwable
+  {
+    addURL(Paths.get(path).toAbsolutePath().toUri().toURL());
+  }
+
+  /**
+   * Add a set of jars to the classpath.
+   *
+   * @param root
+   * @param glob
+   * @throws IOException
+   */
+  public void addPaths(Path root, String glob) throws IOException
+  {
+    final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob);
+
+    Files.walkFileTree(root, new SimpleFileVisitor<Path>()
+    {
+
+      @Override
+      public FileVisitResult visitFile(Path path,
+              BasicFileAttributes attrs) throws IOException
+      {
+        if (pathMatcher.matches(root.relativize(path)))
+        {
+          URL url = path.toUri().toURL();
+          addURL(url);
+        }
+        return FileVisitResult.CONTINUE;
+      }
+
+      @Override
+      public FileVisitResult visitFileFailed(Path file, IOException exc)
+              throws IOException
+      {
+        return FileVisitResult.CONTINUE;
+      }
+    });
+
+  }
+
+  /**
+   * Add a path to the loader after the JVM is started.
+   *
+   * @param path
+   * @throws FileNotFoundException
+   */
+  public void addPath(Path path) throws FileNotFoundException
+  {
+    try
+    {
+      if (!Files.exists(path))
+        throw new FileNotFoundException(path.toString());
+      this.addURL(path.toUri().toURL());
+    } catch (MalformedURLException ex)
+    {
+      // This should never happen
+      throw new RuntimeException(ex);
+    }
+  }
+
+  /**
+   * Loads a class from the class loader.
+   *
+   * @param name is the name of the class with java class notation (using dots).
+   * @return the class
+   * @throws ClassNotFoundException was not found by the class loader.
+   * @throws ClassFormatError if the class byte code was invalid.
+   */
+  @Override
+  public Class findClass(String name) throws ClassNotFoundException, ClassFormatError
+  {
+    String aname = name.replace('.', '/') + ".class";
+    URL url = this.getResource(aname);
+    if (url == null)
+      throw new ClassNotFoundException(name);
+
+    try
+    {
+      URLConnection connection = url.openConnection();
+      try (InputStream is = connection.getInputStream())
+      {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        int bytes;
+        byte[] d = new byte[1024];
+        while ((bytes = is.read(d, 0, d.length)) != -1)
+        {
+          buffer.write(d, 0, bytes);
+        }
+
+        buffer.flush();
+        byte[] data = buffer.toByteArray();
+        return defineClass(null, data, 0, data.length);
+      }
+    } catch (IOException ex)
+    {
+    }
+    throw new ClassNotFoundException(name);
+  }
+
+  @Override
+  public URL findResource(String name)
+  {
+    // Check local first
+    URL url = super.findResource(name);
+    if (url != null)
+      return url;
+
+    // Both with and without / should generate the same result
+    if (name.endsWith("/"))
+      name = name.substring(0, name.length() - 1);
+    if (map.containsKey(name))
+      return map.get(name).get(0);
+
+    // We have some resource which must be sourced to a particular class loader
+    if (name.startsWith("org/jpype/"))
+      return getResource("META-INF/versions/0/" + name);
+    return null;
+  }
+
+  @Override
+  public Enumeration<URL> findResources(String name) throws IOException
+  {
+    ArrayList<URL> out = new ArrayList<>();
+    out.addAll(Collections.list(super.findResources(name)));
+    // Both with and without / should generate the same result
+    if (name.endsWith("/"))
+      name = name.substring(0, name.length() - 1);
+    if (map.containsKey(name))
+      out.addAll(map.get(name));
+    return Collections.enumeration(out);
+  }
+
+  /**
+   * Add a resource to the search.
+   *
+   * Many jar files lack directory support which is needed for the packaging
+   * import.
+   *
+   * @param name
+   * @param url
+   */
+  public void addResource(String name, URL url)
+  {
+    if (!this.map.containsKey(name))
+      this.map.put(name, new ArrayList<>());
+    this.map.get(name).add(url);
+  }
+
+  @Override
+  public void addURL(URL url)
+  {
+    // Mark our cache as dirty
+    code = code * 98745623 + url.hashCode();
+
+    // add to the search tree
+    super.addURL(url);
+
+    // See if it is a path
+    Path path;
+    try
+    {
+      path = Paths.get(url.toURI());
+    } catch (URISyntaxException ex)
+    {
+      return;
+    }
+
+    // Scan for missing resources
+    scanJar(path);
+  }
+
+  /**
+   * Recreate missing directory entries for Jars that lack indexing.
+   *
+   * Some jar files are missing the directory entries that prevents use from
+   * properly importing their contents. This procedure scans a jar file when
+   * loaded to build missing directories.
+   *
+   * @param path
+   */
+  void scanJar(Path path)
+  {
+    if (!Files.exists(path))
+      return;
+    if (Files.isDirectory(path))
+      return;
+
+    try (JarFile jf = new JarFile(path.toFile()))
+    {
+      Enumeration<JarEntry> entries = jf.entries();
+      URI abs = path.toAbsolutePath().toUri();
+      Set urls = new java.util.HashSet();
+      while (entries.hasMoreElements())
+      {
+        JarEntry next = entries.nextElement();
+        String name = next.getName();
+
+        // Skip over META-INF
+        if (name.startsWith("META-INF/"))
+          continue;
+
+        if (next.isDirectory())
+        {
+          // If we find a directory entry then the jar has directories already
+          return;
+        }
+
+        // Split on each separator in the name
+        int i = 0;
+        while (true)
+        {
+          i = name.indexOf("/", i);
+          if (i == -1)
+            break;
+          String name2 = name.substring(0, i);
+
+          i++;
+
+          // Already have an entry no problem
+          if (urls.contains(name2))
+            continue;
+
+          // Add a new entry for the missing directory
+          String jar = "jar:" + abs + "!/" + name2 + "/";
+          urls.add(name2);
+          this.addResource(name2, new URL(jar));
+        }
+      }
+    } catch (IOException ex)
+    {
+      // Anything goes wrong skip it
+    }
+  }
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeContext.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeContext.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeContext.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeContext.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,632 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.Buffer;
+import java.nio.ByteOrder;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.jpype.manager.TypeFactory;
+import org.jpype.manager.TypeFactoryNative;
+import org.jpype.manager.TypeManager;
+import org.jpype.pkg.JPypePackage;
+import org.jpype.pkg.JPypePackageManager;
+import org.jpype.ref.JPypeReferenceQueue;
+
+/**
+ * Context for JPype.
+ * <p>
+ * This is the part of JPype that holds all resources. After the classloader is
+ * created this class is given the address of the context object in JPype. Any
+ * resources in JPype Java layer can be contacted using the context.
+ * <p>
+ * Boot order is - create the C++ portion of the context. - start the jvm - load
+ * the bootloader - install the jar into the bootloader - install all native
+ * methods using the bootloader - create the Java portion of the context. - use
+ * the Java context to access the resources (ReferenceQueue, TypeFactory,
+ * TypeManager)
+ * <p>
+ * Once started, python calls use the context to get a frame and attach their
+ * threads. Methods called from Java will get the env and use it to get their
+ * context from which they can create a frame.
+ * <p>
+ * The C++ context will hold all the previous global variables thus allowing the
+ * C++ portion to be cleaned up properly when the JVM is shutdown or
+ * disconnected.
+ * <p>
+ * As the JPypeContext can't be tested directly from Java code, it will need to
+ * be kept light.
+ * <p>
+ * Our goal is to remove as much direct contact methods as possible from the C++
+ * layer. Previous globals in JPTypeManager move to the context as do the
+ * contents of JPJni.
+ *
+ *
+ *
+ * @author nelson85
+ */
+public class JPypeContext
+{
+
+  public final String VERSION = "1.6.0";
+
+  private static final JPypeContext INSTANCE = new JPypeContext();
+  // This is the C++ portion of the context.
+  private long context;
+  private TypeFactory typeFactory;
+  private TypeManager typeManager;
+  private JPypeClassLoader classLoader;
+  private final AtomicInteger shutdownFlag = new AtomicInteger();
+  private final List<Thread> shutdownHooks = new ArrayList<>();
+  private final List<Runnable> postHooks = new ArrayList<>();
+  public static boolean freeResources = true;
+  public JPypeReflector reflector = null;
+
+  static public JPypeContext getInstance()
+  {
+    return INSTANCE;
+  }
+
+  /**
+   * Start the JPype system.
+   *
+   * @param context is the C++ portion of the context.
+   * @param loader is the classloader holding JPype resources.
+   * @return the created context.
+   */
+  private static JPypeContext createContext(long context, ClassLoader loader, String nativeLib, boolean interrupt) throws Throwable
+  {
+    try
+    {
+      if (nativeLib != null)
+      {
+        System.load(nativeLib);
+      }
+      INSTANCE.context = context;
+      INSTANCE.classLoader = (JPypeClassLoader) loader;
+      INSTANCE.typeFactory = new TypeFactoryNative();
+      INSTANCE.typeManager = new TypeManager(context, INSTANCE.typeFactory);
+      INSTANCE.initialize(interrupt);
+
+      try
+      {
+        INSTANCE.reflector = (JPypeReflector) Class.forName("org.jpype.Reflector0", true, loader)
+                .getConstructor()
+                .newInstance();
+      } catch (ClassNotFoundException | NoSuchMethodException | SecurityException
+              | InstantiationException | IllegalAccessException
+              | IllegalArgumentException | InvocationTargetException ex)
+      {
+        throw new RuntimeException("Unable to create reflector " + ex.getMessage(), ex);
+      }
+
+      scanExistingJars();
+      return INSTANCE;
+    } catch (Throwable ex)
+    {
+      ex.printStackTrace(System.err);
+      throw ex;
+    }
+  }
+
+  private JPypeContext()
+  {
+  }
+
+  private void initialize(boolean interrupt)
+  {
+    // Okay everything is setup so lets give it a go.
+    this.typeManager.init();
+    JPypeReferenceQueue.getInstance().start();
+    if (!interrupt)
+      JPypeSignal.installHandlers();
+
+    // Install a shutdown hook to clean up Python resources.
+    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        INSTANCE.shutdown();
+      }
+    }));
+
+  }
+
+  /**
+   * Shutdown and remove all Python resources.
+   *
+   * This hook is only called after the last user thread has died. Thus the only
+   * remaining connections are proxies that were attached to the JVM shutdown
+   * hook, the reference queue, and the typemanager.
+   *
+   * This routine will try to take out the last connections in an orderly
+   * fashion. Inherently this is a very dangerous time as portions of Java have
+   * already been deactivated.
+   */
+  @SuppressWarnings(
+          {
+            "CallToThreadYield", "SleepWhileInLoop"
+          })
+  private void shutdown()
+  {
+    try
+    {
+      // Try to yield in case there is a race condition.  The user
+      // may have installed a shutdown hook, but we cannot verify
+      // the order that shutdown hook threads are executed.  Thus we will
+      // try to intentionally lose the race.
+      //
+      // This will only occur if something registered a shutdown hook through
+      // a Java API.  Those registered though the JPype API will be joined
+      // manually.
+      for (int i = 0; i < 5; i++)
+      {
+        try
+        {
+          Thread.sleep(1);
+          Thread.yield();
+        } catch (InterruptedException ex)
+        {
+        }
+      }
+
+      // Execute any used defined shutdown hooks registered with JPype.
+      if (!this.shutdownHooks.isEmpty())
+      {
+        for (Thread thread : this.shutdownHooks)
+        {
+          thread.start();
+        }
+        for (Thread thread : this.shutdownHooks)
+        {
+          try
+          {
+            thread.join();
+          } catch (InterruptedException ex)
+          {
+          }
+        }
+      }
+
+      // Disable all future calls to proxies
+      this.shutdownFlag.incrementAndGet();
+
+      // Past this point any further execution of a Python proxy would
+      // be fatal.
+      Thread t1 = Thread.currentThread();
+      Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();
+
+      for (Thread t : threads.keySet())
+      {
+        if (t1 == t || t.isDaemon())
+          continue;
+        t.interrupt();
+      }
+
+      // Inform Python no more calls are permitted
+      onShutdown(this.context);
+      Thread.yield();
+
+    } catch (Throwable th)
+    {
+    }
+
+    if (freeResources)
+    {
+      // Release all Python references
+      try
+      {
+        JPypeReferenceQueue.getInstance().stop();
+      } catch (Throwable th)
+      {
+      }
+
+      // Release any C++ resources
+      try
+      {
+        this.typeManager.shutdown();
+      } catch (Throwable th)
+      {
+      }
+    }
+
+    // Execute post hooks
+    for (Runnable run : this.postHooks)
+    {
+      run.run();
+    }
+    try
+    {
+      classLoader.close();
+    } catch (IOException ex)
+    {
+      // ignored
+    }
+
+  }
+
+  private static native void onShutdown(long ctxt);
+
+  public void addShutdownHook(Thread th)
+  {
+    this.shutdownHooks.add(th);
+  }
+
+  public boolean removeShutdownHook(Thread th)
+  {
+    if (this.shutdownHooks.contains(th))
+    {
+      this.shutdownHooks.remove(th);
+      return true;
+    } else
+      return Runtime.getRuntime().removeShutdownHook(th);
+  }
+
+  /**
+   * Get the C++ portion.
+   *
+   * @return
+   */
+  public long getContext()
+  {
+    return context;
+  }
+
+  public ClassLoader getClassLoader()
+  {
+    if (this.classLoader == null)
+      this.classLoader = new JPypeClassLoader(ClassLoader.getSystemClassLoader());
+    return this.classLoader;
+  }
+
+  public TypeFactory getTypeFactory()
+  {
+    return this.typeFactory;
+  }
+
+  public TypeManager getTypeManager()
+  {
+    return this.typeManager;
+  }
+
+  /**
+   * Add a hook to run after Python interface is shutdown.
+   *
+   * This must never have a Python method attached.
+   *
+   * @param run
+   */
+  public void _addPost(Runnable run)
+  {
+    this.postHooks.add(run);
+  }
+
+  /**
+   * Helper function for collect rectangular,
+   */
+  private static boolean collect(List l, Object o, int q, int[] shape, int d)
+  {
+    if (Array.getLength(o) != shape[q])
+      return false;
+    if (q + 1 == d)
+    {
+      l.add(o);
+      return true;
+    }
+    for (int i = 0; i < shape[q]; ++i)
+    {
+      if (!collect(l, Array.get(o, i), q + 1, shape, d))
+        return false;
+    }
+    return true;
+  }
+
+  /**
+   * Collect up a rectangular primitive array for a Python memory view.
+   *
+   * If it is a rectangular primitive array then the result will be an object
+   * array containing. - the primitive type - an int array with the shape of the
+   * array - each of the primitive arrays that will need be visited in order.
+   *
+   * This is the safest way to provide a view as we are verifying and collected
+   * thus even if something mutates the shape of the array after we have
+   * visited, we have a locked copy.
+   *
+   * @param o is the object to be tested.
+   * @return null if the object is not a rectangular primitive array.
+   */
+  public Object[] collectRectangular(Object o)
+  {
+    if (o == null || !o.getClass().isArray())
+      return null;
+    int[] shape = new int[5];
+    int d = 0;
+    ArrayList<Object> out = new ArrayList<>();
+    Object o1 = o;
+    Class c1 = o1.getClass();
+    for (int i = 0; i < 5; ++i)
+    {
+      int l = Array.getLength(o1);
+      if (l == 0)
+        return null;
+      shape[d++] = l;
+      o1 = Array.get(o1, 0);
+      if (o1 == null)
+        return null;
+      c1 = c1.getComponentType();
+      if (!c1.isArray())
+        break;
+    }
+    if (!c1.isPrimitive())
+      return null;
+    out.add(c1);
+    shape = Arrays.copyOfRange(shape, 0, d);
+    out.add(shape);
+    int total = 1;
+    for (int i = 0; i < d - 1; i++)
+      total *= shape[i];
+    out.ensureCapacity(total + 2);
+    if (d == 5)
+      return null;
+    if (!collect(out, o, 0, shape, d))
+      return null;
+    return out.toArray();
+  }
+
+  private Object unpack(int size, Object parts)
+  {
+    Object e0 = Array.get(parts, 0);
+    Class c = e0.getClass();
+    int segments = Array.getLength(parts) / size;
+    Object a2 = Array.newInstance(c, size);
+    Object a1 = Array.newInstance(a2.getClass(), segments);
+    int k = 0;
+    for (int i = 0; i < segments; i++)
+    {
+      for (int j = 0; j < size; j++, k++)
+      {
+        Object o = Array.get(parts, k);
+        Array.set(a2, j, o);
+      }
+      Array.set(a1, i, a2);
+      if (i < segments - 1)
+        a2 = Array.newInstance(c, size);
+    }
+    return a1;
+  }
+
+  private Object assemble(int[] dims, Object parts)
+  {
+    int n = dims.length;
+    if (n == 1)
+      return Array.get(parts, 0);
+    if (n == 2)
+      return Array.get(unpack(dims[0], parts), 0);
+    for (int i = 0; i < n - 2; ++i)
+    {
+      parts = unpack(dims[n - i - 2], parts);
+    }
+    return parts;
+  }
+
+  public boolean isShutdown()
+  {
+    return shutdownFlag.get() > 0;
+  }
+
+  /**
+   * Clear the current interrupt.
+   *
+   * @param x is true if an exception should be thrown.
+   * @throws InterruptedException
+   */
+  public static void clearInterrupt(boolean x) throws InterruptedException
+  {
+    try
+    {
+      Thread th = Thread.currentThread();
+
+      // Only relevant if this is the main thread for signal handling
+      if (th != JPypeSignal.main)
+        return;
+
+      // Unconditionally clear the interrupt flag if we are called from 
+      // C++.  This happens when a field get() or method call() is 
+      // invoked.
+      if (!x)
+        JPypeSignal.acknowledgePy();
+
+      // Check if this thread is interrupted
+      if (th.isInterrupted())
+      {
+        // Clear the flag in C++
+        JPypeSignal.acknowledgePy();
+
+        // Clear the flag in Java
+        Thread.sleep(1);
+      }
+    } catch (InterruptedException ex)
+    {
+      if (x)
+        throw ex;
+    }
+  }
+
+  public long getExcClass(Throwable th)
+  {
+    if (th instanceof PyExceptionProxy)
+      return ((PyExceptionProxy) th).cls;
+    return 0;
+  }
+
+  public long getExcValue(Throwable th)
+  {
+    if (th instanceof PyExceptionProxy)
+      return ((PyExceptionProxy) th).value;
+    return 0;
+  }
+
+  private Exception createException(long l0, long l1)
+  {
+    return new PyExceptionProxy(l0, l1);
+  }
+
+  private boolean order(Buffer b)
+  {
+    if (b instanceof java.nio.ByteBuffer)
+      return ((java.nio.ByteBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
+    if (b instanceof java.nio.ShortBuffer)
+      return ((java.nio.ShortBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
+    if (b instanceof java.nio.CharBuffer)
+      return ((java.nio.CharBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
+    if (b instanceof java.nio.IntBuffer)
+      return ((java.nio.IntBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
+    if (b instanceof java.nio.LongBuffer)
+      return ((java.nio.LongBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
+    if (b instanceof java.nio.FloatBuffer)
+      return ((java.nio.FloatBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
+    if (b instanceof java.nio.DoubleBuffer)
+      return ((java.nio.DoubleBuffer) b).order() == ByteOrder.LITTLE_ENDIAN;
+    return true;
+  }
+
+  public boolean isPackage(String s)
+  {
+    s = JPypeKeywords.safepkg(s);
+    return JPypePackageManager.isPackage(s);
+  }
+
+  public JPypePackage getPackage(String s)
+  {
+    s = JPypeKeywords.safepkg(s);
+    if (!JPypePackageManager.isPackage(s))
+      return null;
+    return new JPypePackage(s);
+  }
+
+  /**
+   * Utility to probe functional interfaces.
+   *
+   * @param cls
+   * @return
+   */
+  public static String getFunctional(Class cls)
+  {
+    Method m = JPypeUtilities.getFunctionalInterfaceMethod(cls);
+    return m != null ? m.getName() : null;
+  }
+
+  /**
+   * Utility function for extracting the unique portion of a stack trace.
+   *
+   * This is a bit different that the Java method which works from the back. We
+   * will be using fake stacktraces from Python at some point so finding the
+   * first common is a better approach.
+   *
+   * @param th is the throwable.
+   * @param enclosing is the throwsble that holds this or null if top level.
+   * @return the unique frames as an object array with 4 objects per frame.
+   */
+  public Object[] getStackTrace(Throwable th, Throwable enclosing)
+  {
+    StackTraceElement[] trace = th.getStackTrace();
+    if (trace == null || enclosing == null)
+      return toFrames(trace);
+    StackTraceElement[] te = enclosing.getStackTrace();
+    if (te == null)
+      return toFrames(trace);
+    for (int i = 0; i < trace.length; ++i)
+    {
+      if (trace[i].equals(te[0]))
+      {
+        return toFrames(Arrays.copyOfRange(trace, 0, i));
+      }
+    }
+    return toFrames(trace);
+  }
+
+  private Object[] toFrames(StackTraceElement[] stackTrace)
+  {
+    if (stackTrace == null)
+      return null;
+    Object[] out = new Object[4 * stackTrace.length];
+    int i = 0;
+    for (StackTraceElement fr : stackTrace)
+    {
+      out[i++] = fr.getClassName();
+      out[i++] = fr.getMethodName();
+      out[i++] = fr.getFileName();
+      out[i++] = fr.getLineNumber();
+    }
+    return out;
+
+  }
+
+  public void newWrapper(long l)
+  {
+    // We can only go through this point single file.
+    synchronized (this.typeFactory)
+    {
+      this.typeFactory.newWrapper(context, l);
+    }
+  }
+
+  private static void scanExistingJars()
+  {
+    // Scan existing jars for missing directory entries
+    String[] paths = System.getProperty("java.class.path").split(File.pathSeparator);
+    for (String path : paths)
+    {
+      INSTANCE.classLoader.scanJar(Paths.get(path));
+    }
+  }
+
+  private static long getTotalMemory()
+  {
+    return Runtime.getRuntime().totalMemory();
+  }
+
+  private static long getFreeMemory()
+  {
+    return Runtime.getRuntime().freeMemory();
+  }
+
+  private static long getMaxMemory()
+  {
+    return Runtime.getRuntime().maxMemory();
+  }
+
+  private static long getUsedMemory()
+  {
+    return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+  }
+
+  private static long getHeapMemory()
+  {
+    java.lang.management.MemoryMXBean memoryBean = java.lang.management.ManagementFactory.getMemoryMXBean();
+    return memoryBean.getHeapMemoryUsage().getUsed();
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeKeywords.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeKeywords.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeKeywords.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeKeywords.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,62 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ *
+ * @author nelson85
+ */
+public class JPypeKeywords
+{
+
+  final public static Set<String> keywords = new HashSet<>();
+
+  public static void setKeywords(String[] s)
+  {
+    keywords.addAll(Arrays.asList(s));
+  }
+
+  public static String wrap(String name)
+  {
+    if (keywords.contains(name))
+      return name + "_";
+    return name;
+  }
+
+  public static String unwrap(String name)
+  {
+    if (!name.endsWith("_"))
+      return name;
+    String name2 = name.substring(0, name.length() - 1);
+    if (keywords.contains(name2))
+      return name2;;
+    return name;
+  }
+
+  static String safepkg(String s)
+  {
+    if (!s.contains("_"))
+      return s;
+    String[] parts = s.split("\\.");
+    for (int i = 0; i < parts.length; ++i)
+      parts[i] = unwrap(parts[i]);
+    return String.join(".", parts);
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeReflector.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeReflector.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeReflector.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeReflector.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,28 @@
+package org.jpype;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ * @author nelson85
+ */
+public interface JPypeReflector
+{
+
+  /**
+   * Call a method using reflection.
+   *
+   * This method creates a stackframe so that caller sensitive methods will
+   * execute properly.
+   *
+   * @param method is the method to call.
+   * @param obj is the object to operate on, it will be null if the method is
+   * static.
+   * @param args the arguments to method.
+   * @return the object that results form the invocation.
+   * @throws java.lang.Throwable throws whatever type the called method
+   * produces.
+   */
+  public Object callMethod(Method method, Object obj, Object[] args)
+          throws Throwable;
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeSignal.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeSignal.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeSignal.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeSignal.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,67 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Java wants to make this action nearly impossible.
+ *
+ * Thus the have warnings against it that cannot be disabled. So we will skin
+ * this cat another way.
+ */
+public class JPypeSignal
+{
+
+  static Thread main;
+
+  static Object getSignalHandler(Class signalHandlerClazz, int signal) throws ClassNotFoundException
+  {
+    return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]
+    {
+      signalHandlerClazz
+    }, (proxy, method, args) ->
+    {
+      main.interrupt();
+      interruptPy(signal);
+      return null;
+    });
+  }
+
+  static void installHandlers()
+  {
+    try
+    {
+      Class Signal = Class.forName("sun.misc.Signal");
+      Class SignalHandler = Class.forName("sun.misc.SignalHandler");
+      main = Thread.currentThread();
+      Method method = Signal.getMethod("handle", Signal, SignalHandler);
+      Object intr = Signal.getDeclaredConstructor(String.class).newInstance("INT");
+      method.invoke(null, intr, getSignalHandler(SignalHandler, 2));
+      Object intrTerm = Signal.getDeclaredConstructor(String.class).newInstance("TERM");
+      method.invoke(null, intrTerm, getSignalHandler(SignalHandler, 15));
+    } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException | InstantiationException | ClassNotFoundException | NoSuchMethodException | SecurityException ex)
+    {
+      // If we don't get the signal handler run without it.  (ANDROID)
+    }
+  }
+
+  native static void interruptPy(int signal);
+
+  native static void acknowledgePy();
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeUtilities.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeUtilities.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/JPypeUtilities.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/JPypeUtilities.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,110 @@
+package org.jpype;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleProxies;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+public class JPypeUtilities
+{
+
+  // a functional interface can only re-declare a public non-final method from Object
+  // this should end up being an array of equals, hashCode and toString
+  private static final Method[] OBJECT_METHODS
+          = Arrays.stream(Object.class.getMethods())
+                  .filter(m -> !Modifier.isFinal(m.getModifiers()))
+                  .toArray(Method[]::new);
+
+  private static final Predicate<Class> isSealed;
+
+  static
+  {
+    Predicate<Class> result = null;
+    try
+    {
+      Method m = Class.class.getMethod("isSealed");
+      MethodHandle handle = MethodHandles.publicLookup().unreflect(m);
+      result = MethodHandleProxies.asInterfaceInstance(Predicate.class, handle);
+    } catch (IllegalAccessException e)
+    {
+      // it's a public method so this should never occur
+      throw new IllegalAccessError(e.getMessage());
+    } catch (NoSuchMethodException e)
+    {
+      // if isSealed doesn't exist then neither do sealed classes
+      result = c -> false;
+    }
+    isSealed = result;
+  }
+
+  public static Path getJarPath(Class c)
+  {
+    try
+    {
+      return Paths.get(c.getProtectionDomain().getCodeSource().getLocation()
+              .toURI()).getParent();
+    } catch (URISyntaxException ex)
+    {
+      return null;
+    }
+  }
+
+  public static Method getFunctionalInterfaceMethod(Class cls)
+  {
+    if (!cls.isInterface() || cls.isAnnotation() || isSealed.test(cls))
+      return null;
+
+    Method result = null;
+    for (Method m : cls.getMethods())
+    {
+      if (Modifier.isAbstract(m.getModifiers()))
+      {
+        if (isObjectMethodOverride(m))
+          continue;
+
+        if (result != null && !equals(m, result))
+          return null;
+
+        if (result == null || cls.equals(m.getDeclaringClass()))
+          result = m;
+      }
+    }
+    return result;
+  }
+
+  private static boolean isObjectMethodOverride(Method m)
+  {
+    for (Method objectMethod : OBJECT_METHODS)
+    {
+      if (equals(m, objectMethod))
+        return true;
+    }
+    return false;
+  }
+
+  private static boolean equals(Method a, Method b)
+  {
+    // this should be the fastest possible short circuit
+    if (a.getParameterCount() != b.getParameterCount())
+      return false;
+
+    if (!a.getName().equals(b.getName()))
+      return false;
+
+    // if the return types are different it wouldn't compile
+    // parameters must be exactly the same and may not be an extended class
+    if (!Arrays.equals(a.getParameterTypes(), b.getParameterTypes()))
+      return false;
+
+    // if declared exceptions were different it wouldn't compile
+    // if it did compile then it is an override
+    return true;
+  }
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/PyExceptionProxy.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/PyExceptionProxy.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/PyExceptionProxy.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/PyExceptionProxy.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,19 @@
+package org.jpype;
+
+/**
+ *
+ * @author nelson85
+ */
+public class PyExceptionProxy extends RuntimeException
+{
+
+  long cls;
+  long value;
+
+  public PyExceptionProxy(long l0, long l1)
+  {
+    cls = l0;
+    value = l1;
+  }
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrGrammar.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrGrammar.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrGrammar.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrGrammar.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,249 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import org.jpype.html.Parser.Entity;
+import org.jpype.html.Parser.Rule;
+import org.w3c.dom.Attr;
+
+public class AttrGrammar implements Parser.Grammar
+{
+
+  static final AttrGrammar INSTANCE = new AttrGrammar();
+
+  private AttrGrammar()
+  {
+  }
+
+  @Override
+  public void start(Parser p)
+  {
+    p.state = State.FREE;
+    ((AttrParser) p).attrs.clear();
+  }
+
+  @Override
+  public Object end(Parser p)
+  {
+    return ((AttrParser) p).attrs;
+  }
+
+//<editor-fold desc="tokens">
+  enum Token implements Parser.Token
+  {
+    TEXT,
+    QUOTE("\""),
+    SQUOTE("'"),
+    EQ("="),
+    WHITESPACE(" ");
+
+    byte value;
+    String text;
+
+    Token()
+    {
+    }
+
+    Token(String s)
+    {
+      text = s;
+      if (s.length() == 1)
+        value = (byte) s.charAt(0);
+    }
+
+    @Override
+    final public boolean matches(byte b)
+    {
+      if (value == ' ')
+        return Character.isWhitespace(b);
+      if (value == 0)
+        return true;
+      return b == value;
+    }
+
+    @Override
+    public boolean runs()
+    {
+      return this == Token.TEXT;
+    }
+
+    @Override
+    public String toString()
+    {
+      if (text != null)
+        return text;
+      return "TEXT";
+    }
+  }
+
+  final static Token[] freeTokens = tokens(
+          Token.QUOTE, Token.SQUOTE, Token.EQ, Token.WHITESPACE, Token.TEXT);
+  final static Token[] qtTokens = tokens(
+          Token.QUOTE, Token.TEXT);
+  final static Token[] sqtTokens = tokens(
+          Token.SQUOTE, Token.TEXT);
+
+  final static Rule ignoreWS = new IgnoreWSRule();
+  final static Rule quoteRule = new QuoteRule();
+  final static Rule endRule = new EndQuoteRule();
+  final static Rule attrRule = new AttrRule();
+  final static Rule boolRule = new BooleanRule();
+
+  final static Rule[] freeRules = rules(attrRule, boolRule, ignoreWS, quoteRule);
+  final static Rule[] qtRules = rules(endRule);
+
+  static Token[] tokens(Token... t)
+  {
+    return t;
+  }
+
+  static Rule[] rules(Rule... t)
+  {
+    return t;
+  }
+//</editor-fold>
+//<editor-fold desc="state">
+
+  enum State implements Parser.State
+  {
+    FREE(freeTokens, freeRules),
+    IN_QUOTE(qtTokens, qtRules),
+    IN_SQUOTE(sqtTokens, qtRules);
+
+    Token[] tokens;
+    Rule[] rules;
+
+    State(Token[] tokens, Rule[] rules)
+    {
+      this.tokens = tokens;
+      this.rules = rules;
+    }
+
+    @Override
+    public Token[] getTokens()
+    {
+      return this.tokens;
+    }
+
+    @Override
+    public Rule[] getRules()
+    {
+      return this.rules;
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="rules">
+  static class IgnoreWSRule implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      if (entity.token != Token.WHITESPACE)
+        return false;
+      parser.stack.removeLast();
+      return true;
+    }
+  }
+
+  static class QuoteRule implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      if (entity.token == Token.QUOTE)
+      {
+        parser.state = State.IN_QUOTE;
+        parser.stack.removeLast();
+        return true;
+      }
+      if (entity.token == Token.SQUOTE)
+      {
+        parser.state = State.IN_SQUOTE;
+        parser.stack.removeLast();
+        return true;
+      }
+      return false;
+    }
+  }
+
+  static class EndQuoteRule implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      if (State.IN_QUOTE == parser.state && entity.token == Token.QUOTE)
+      {
+        parser.state = State.FREE;
+        parser.stack.removeLast();
+        return true;
+      }
+      if (State.IN_SQUOTE == parser.state && entity.token == Token.SQUOTE)
+      {
+        parser.state = State.FREE;
+        parser.stack.removeLast();
+        return true;
+      }
+      return false;
+    }
+  }
+
+  static class AttrRule extends Parser.MatchRule
+  {
+
+    AttrRule()
+    {
+      super(Token.TEXT, Token.EQ, Token.TEXT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      Entity e2 = (Entity) parser.stack.removeLast();
+      Entity e1 = (Entity) parser.stack.removeLast();
+      Entity e0 = (Entity) parser.stack.removeLast();
+      AttrParser aparser = (AttrParser) parser;
+      Attr attr = aparser.doc.createAttribute((String) e0.value);
+      attr.setNodeValue((String) e2.value);
+      aparser.attrs.add(attr);
+    }
+  }
+
+  static class BooleanRule extends Parser.MatchRule
+  {
+
+    BooleanRule()
+    {
+      super(Token.TEXT, Token.TEXT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      Entity e2 = (Entity) parser.stack.removeLast();
+      Entity e1 = (Entity) parser.stack.removeLast();
+      AttrParser aparser = (AttrParser) parser;
+      Attr attr = aparser.doc.createAttribute((String) e1.value);
+      attr.setNodeValue((String) e1.value);
+      aparser.attrs.add(attr);
+      parser.stack.add(e2);
+    }
+  }
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrParser.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrParser.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrParser.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/AttrParser.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,34 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+
+public class AttrParser extends Parser<List<Attr>>
+{
+
+  final Document doc;
+  final List<Attr> attrs = new ArrayList<>();
+
+  public AttrParser(Document doc)
+  {
+    super(AttrGrammar.INSTANCE);
+    this.doc = doc;
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/Html.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/Html.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/Html.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/Html.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,181 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import org.jpype.JPypeContext;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+
+public class Html
+{
+
+  public final static HashSet<String> VOID_ELEMENTS = new HashSet<>();
+  public final static HashSet<String> OPTIONAL_ELEMENTS = new HashSet<>();
+  public final static HashSet<String> OPTIONAL_CLOSE = new HashSet<>();
+
+  static
+  {
+    VOID_ELEMENTS.addAll(Arrays.asList(
+            "area", "base", "br", "col", "command", "embed", "hr", "img",
+            "input", "keygen", "link", "meta", "param", "source", "track", "wbr"));
+    OPTIONAL_ELEMENTS.addAll(Arrays.asList("html", "head", "body", "p", "dt",
+            "dd", "li", "option", "thead", "th", "tbody", "tr", "td", "tfoot", "colgroup"));
+    OPTIONAL_CLOSE.addAll(Arrays.asList("li:li", "dt:dd",
+            "p:address", "p:article", "p:aside", "p:blockquote", "p:details",
+            "p:div", "p:dl", "p:fieldset", "p:figcaption", "p:figure",
+            "p:footer", "p:form", "p:h1", "p:h2", "p:h3", "p:h4", "p:h5", "p:h6",
+            "p:header", "p:hgroup", "p:hr", "p:main", "p:menu",
+            "p:nav", "p:ol", "p:p", "p:pre", "p:section", "p:table", "p:ul",
+            "dd:dt", "dd:dd", "dt:dt", "dt:dd", "rt:rt", "rt:rp", "rp:rt", "rp:rp",
+            "optgroup:optgroup", "option:option", "option:optiongroup", "thread:tbody",
+            "thread:tfoot", "tbody:tfoot", "tbody:tbody", "tr:tr", "td:td", "td:th",
+            "th:td", "p:li"));
+  }
+
+  public static Parser<Document> newParser()
+  {
+    return new HtmlParser();
+  }
+
+  public static List<Attr> parseAttributes(Document doc, String str)
+  {
+    AttrParser p = new AttrParser(doc);
+    p.parse(str);
+    return p.attrs;
+  }
+
+//<editor-fold desc="decode" defaultstate="collapsed">
+  public static Map<String, Integer> ENTITIES = new HashMap<>();
+
+  static
+  {
+    ClassLoader cl = ClassLoader.getSystemClassLoader();
+    try (InputStream is = cl.getResourceAsStream("org/jpype/html/entities.txt"); InputStreamReader isr = new InputStreamReader(is); BufferedReader rd = new BufferedReader(isr))
+    {
+      while (true)
+      {
+        String line = rd.readLine();
+        if (line == null)
+          break;
+        if (line.startsWith("#"))
+          continue;
+        String[] parts = line.split("\\s+");
+        ENTITIES.put(parts[0], Integer.parseInt(parts[1]));
+      }
+    } catch (IOException ex)
+    {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  public static String decode(String s)
+  {
+    if (!s.contains("&"))
+      return s;
+
+    int dead = 0;
+    byte[] b = s.getBytes(StandardCharsets.UTF_8);
+    for (int i = 0; i < b.length; ++i)
+    {
+      if (b[i] != '&')
+        continue;
+
+      int i1 = i;
+      int i2 = i + 1;
+      if (i2 == b.length)
+        break;
+      if (b[i2] == '#')
+      {
+        // Try to be robust when there is no ;
+        for (i = i2 + 1; i < b.length; ++i)
+        {
+          if (!Character.isDigit(b[i]))
+            break;
+        }
+      } else
+      {
+        for (i = i2; i < b.length; ++i)
+        {
+          if (b[i] == ';')
+            break;
+        }
+      }
+      int i3 = i;
+      int c = 0;
+      if (b[i2] == '#')
+      {
+        i2++;
+        try
+        {
+          c = Integer.parseInt(new String(b, i2, i3 - i2, StandardCharsets.UTF_8));
+        } catch (NumberFormatException ex)
+        {
+
+        }
+      } else
+      {
+        String e = new String(b, i2, i3 - i2, StandardCharsets.UTF_8);
+        Integer c2 = ENTITIES.get(e);
+        if (c2 == null)
+          throw new RuntimeException("Bad entity " + e);
+        c = c2;
+      }
+
+      // Substitute
+      if (c < 128)
+      {
+        b[i1++] = (byte) c;
+      } else if (c < 0x0800)
+      {
+        b[i1++] = (byte) (0xc0 + ((c >> 6) & 0x1f));
+        if (i1 < b.length) // lgtm [java/constant-comparison]
+          b[i1++] = (byte) (0x80 + (c & 0x3f)); // lgtm [java/index-out-of-bounds]
+      } else
+      {
+        b[i1++] = (byte) (0xe0 + ((c >> 12) & 0x0f));
+        if (i1 < b.length) // lgtm [java/constant-comparison]
+          b[i1++] = (byte) (0x80 + ((c >> 6) & 0x3f)); // lgtm [java/index-out-of-bounds]
+        if (i1 < b.length)
+          b[i1++] = (byte) (0x80 + (c & 0x3f));
+      }
+      if (i3 < b.length && b[i3] == ';')
+        i3++;
+      dead += i3 - i1;
+      for (; i1 < i3; ++i1)
+        b[i1] = 0;
+      i = i3;
+    }
+    int j = 0;
+    byte[] b2 = new byte[b.length - dead];
+    for (int i = 0; i < b.length; ++i)
+    {
+      if (b[i] != 0)
+        b2[j++] = b[i];
+    }
+    return new String(b2, StandardCharsets.UTF_8);
+  }
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlGrammar.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlGrammar.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlGrammar.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlGrammar.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,669 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import java.util.LinkedList;
+import org.jpype.html.Parser.Entity;
+import org.jpype.html.Parser.Rule;
+
+public class HtmlGrammar implements Parser.Grammar
+{
+
+  final static HtmlGrammar INSTANCE = new HtmlGrammar();
+
+  private HtmlGrammar()
+  {
+  }
+
+  @Override
+  public void start(Parser p)
+  {
+    p.state = State.FREE;
+    ((HtmlParser) p).handler.startDocument();
+  }
+
+  @Override
+  public Object end(Parser p)
+  {
+    ((HtmlParser) p).handler.endDocument();
+    return ((HtmlParser) p).handler.getResult();
+  }
+
+  // BeginElement does < </ and <! via lookhead
+  static StringBuilder promote(Entity e)
+  {
+    if (e.value != null && e.value instanceof StringBuilder)
+      return (StringBuilder) e.value;
+    StringBuilder sb = new StringBuilder(e.toString());
+    e.value = sb;
+    e.token = Token.TEXT;
+    return (StringBuilder) sb;
+  }
+
+  static HtmlGrammar getGrammar(Parser p)
+  {
+    return ((HtmlGrammar) p.grammar);
+  }
+
+  static HtmlHandler getHandler(Parser p)
+  {
+    return ((HtmlParser) p).handler;
+  }
+
+  /**
+   * Send everything out to as text.
+   */
+  void flushText(Parser<?> parser)
+  {
+    if (parser.stack.isEmpty())
+      return;
+
+    Entity last = parser.stack.removeLast();
+    StringBuilder s = new StringBuilder();
+    for (Entity e : parser.stack)
+    {
+      s.append(e.toString());
+    }
+    ((HtmlParser) parser).handler.text(s.toString());
+    parser.stack.clear();
+    parser.stack.add(last);
+  }
+
+//<editor-fold desc="state">
+  enum State implements Parser.State
+  {
+    FREE(freeTokens, freeRules),
+    ELEMENT(elementTokens, elementRules),
+    DIRECTIVE(directiveTokens, directiveRules),
+    CDATA(cdataTokens, cdataRules),
+    COMMENT(commentTokens, commentRules);
+
+    Token[] tokens;
+    Rule[] rules;
+
+    State(Token[] tokens, Rule[] rules)
+    {
+      this.tokens = tokens;
+      this.rules = rules;
+    }
+
+    @Override
+    public Token[] getTokens()
+    {
+      return this.tokens;
+    }
+
+    @Override
+    public Rule[] getRules()
+    {
+      return this.rules;
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="tokens" defaultstate="collapsed">
+  enum Token implements Parser.Token
+  {
+    TEXT,
+    BANG("!"),
+    DASH("-"),
+    LT("<"),
+    GT(">"),
+    SLASH("/"),
+    AMP("&"),
+    SEMI(";"),
+    LSB("["),
+    RSB("]"),
+    CLOSE("</"),
+    QUOTE("\""),
+    SQUOTE("'"),
+    DECL_DIRECTIVE("<!");
+
+    byte value;
+    String text;
+
+    Token()
+    {
+    }
+
+    Token(String s)
+    {
+      text = s;
+      if (s.length() == 1)
+        value = (byte) s.charAt(0);
+    }
+
+    @Override
+    final public boolean matches(byte b)
+    {
+      if (value == 0)
+        return true;
+      return b == value;
+    }
+
+    @Override
+    public boolean runs()
+    {
+      return this == Token.TEXT;
+    }
+
+    @Override
+    public String toString()
+    {
+      if (text != null)
+        return text;
+      return "TEXT";
+    }
+  }
+//</editor-fold>
+//<editor-fold desc="rules">
+
+  final static Rule escaped = new Escaped();
+  final static Rule slash = new Cleanup();
+  final static Rule mergeText = new MergeText();
+  final static Rule beginElement = new BeginElement();
+  final static Rule startElement = new StartElement();
+  final static Rule completeElement = new CompleteElement();
+  final static Rule endElement = new EndElement();
+  final static Rule quote = new StartQuote();
+
+  final static Token[] freeTokens = tokens(
+          Token.LT, Token.AMP, Token.SEMI, Token.TEXT);
+  final static Token[] elementTokens = tokens(
+          Token.BANG, Token.AMP, Token.LT, Token.SEMI, Token.SLASH,
+          Token.GT, Token.QUOTE, Token.SQUOTE, Token.TEXT);
+  final static Token[] directiveTokens = tokens(
+          Token.DASH, Token.LSB, Token.RSB, Token.LT, Token.GT, Token.QUOTE,
+          Token.SQUOTE, Token.TEXT);
+  final static Token[] cdataTokens = tokens(
+          Token.LSB, Token.RSB, Token.GT, Token.TEXT);
+  final static Token[] commentTokens = tokens(
+          Token.DASH, Token.GT, Token.TEXT);
+  final static Token[] quoteTokens = tokens(
+          Token.QUOTE, Token.TEXT);
+  final static Token[] squoteTokens = tokens(
+          Token.SQUOTE, Token.TEXT);
+
+  final static Rule[] freeRules = rules(
+          beginElement, escaped, mergeText);
+  final static Rule[] elementRules = rules(
+          mergeText, quote, startElement, endElement, completeElement, escaped, slash);
+  final static Rule[] directiveRules = rules(quote,
+          new EndDirective(), mergeText);
+  final static Rule[] cdataRules = rules(
+          new EndCData());
+  final static Rule[] commentRules = rules(
+          new EndComment(), new StartComment());
+
+  private static Rule[] rules(Rule... t)
+  {
+    return t;
+  }
+
+  private static Token[] tokens(Token... t)
+  {
+    return t;
+  }
+
+//</editor-fold>
+//<editor-fold desc="inner" defaultstate="collapsed">
+  private static class Cleanup implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser<?> parser, Entity entity)
+    {
+      if (entity.token == Token.SLASH)
+      {
+        parser.lookahead = this::next;
+        return false;
+      }
+
+      if (entity.token == Token.GT && parser.stack.size() > 4)
+      {
+        // This it to help debug a rare problem
+        for (Entity e : parser.stack)
+        {
+          System.out.print(e.token);
+          System.out.print("(");
+          System.out.print(e.value);
+          System.out.print(") ");
+        }
+        System.out.println();
+        throw new RuntimeException("Need cleanup");
+      }
+      return false;
+    }
+
+    private boolean next(Parser<?> parser, Entity entity)
+    {
+      if (entity.token != Token.GT)
+      {
+        parser.stack.removeLast();
+        parser.stack.removeLast();
+        // parser.stack.getLast().token = Token.TEXT;
+        entity.value = "/" + entity.value;
+        parser.stack.add(entity);
+      }
+      return false;
+    }
+  }
+
+  private static class Escaped extends Parser.MatchRule
+  {
+
+    public Escaped()
+    {
+      super(Token.AMP, Token.TEXT, Token.SEMI);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      Entity e2 = stack.removeLast();
+      Entity e1 = stack.removeLast();
+      // Currently we do not verify the text contents
+      Entity e0 = stack.getLast();
+      promote(e0).append(e1.toString()).append(e2.toString());
+    }
+  }
+
+  static class MergeText extends Parser.MatchRule
+  {
+
+    public MergeText()
+    {
+      super(Token.TEXT, Token.TEXT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      Entity t2 = stack.removeLast();
+      Entity t1 = stack.getLast();
+      promote(t1).append(t2.toString());
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="elements" defaultstate="collapsed">
+  static class BeginElement implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      if (entity.token != Token.LT)
+        return false;
+      getGrammar(parser).flushText(parser);
+      parser.state = State.ELEMENT;
+      parser.lookahead = this::next;
+      return true;
+    }
+
+    public boolean next(Parser<?> parser, Parser.Entity entity)
+    {
+      if (entity.token == Token.SLASH)
+      {
+        parser.stack.removeLast();
+        parser.stack.getLast().token = Token.CLOSE;
+        return true;
+      }
+
+      if (entity.token == Token.BANG)
+      {
+        parser.stack.removeLast();
+        Entity last = parser.stack.getLast();
+        last.token = Token.DECL_DIRECTIVE;
+
+        parser.lookahead = new Directive();
+        parser.state = State.DIRECTIVE;
+        return true;
+      }
+      return false;
+    }
+  }
+
+  static class StartElement extends Parser.MatchRule
+  {
+
+    StartElement()
+    {
+      super(Token.LT, Token.TEXT, Token.GT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      stack.removeLast();
+      Entity e1 = stack.removeLast();
+      stack.removeLast();
+      String content = e1.value.toString();
+      getGrammar(parser).flushText(parser);
+      String[] parts = content.split("\\s+", 2);
+      if (parts.length == 1)
+        getHandler(parser).startElement(content, null);
+      else
+        getHandler(parser).startElement(parts[0], parts[1]);
+      parser.state = State.FREE;
+    }
+
+  }
+
+  static class CompleteElement extends Parser.MatchRule
+  {
+
+    CompleteElement()
+    {
+      super(Token.LT, Token.TEXT, Token.SLASH, Token.GT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      stack.removeLast(); // >
+      stack.removeLast(); // /
+      Entity e1 = stack.removeLast();
+      stack.removeLast(); // <
+      String content = e1.value.toString();
+      stack.clear();
+      int i = content.indexOf(" ");
+
+      if (i == -1)
+      {
+        getHandler(parser).startElement(content, null);
+        getHandler(parser).endElement(content);
+      } else
+      {
+        String name = content.substring(0, i);
+        String attr = content.substring(i).trim();
+        getHandler(parser).startElement(name, attr);
+        getHandler(parser).endElement(name);
+      }
+      parser.state = State.FREE;
+    }
+  }
+
+  static class EndElement extends Parser.MatchRule
+  {
+
+    EndElement()
+    {
+      super(Token.CLOSE, Token.TEXT, Token.GT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      Entity e2 = stack.removeLast();
+      Entity e1 = stack.removeLast();
+      Entity e0 = stack.removeLast();
+      String content = e1.value.toString();
+      getGrammar(parser).flushText(parser);
+      getHandler(parser).endElement(content);
+      parser.state = State.FREE;
+    }
+  }
+
+  static class EndDirective extends Parser.MatchRule
+  {
+
+    EndDirective()
+    {
+      super(Token.DECL_DIRECTIVE, Token.TEXT, Token.GT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      Entity e2 = stack.removeLast();
+      Entity e1 = stack.removeLast();
+      Entity e0 = stack.removeLast();
+      String content = e1.value.toString();
+      getGrammar(parser).flushText(parser);
+      getHandler(parser).directive(content);
+      parser.state = State.FREE;
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="comment" defaultstate="collapsed">
+  // This is a look ahead rule
+  static class Directive implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      if (entity.token == Token.LSB)
+      {
+        parser.lookahead = new CData();
+        return true;
+      }
+
+      if (entity.token == Token.DASH)
+      {
+        parser.lookahead = new Comment();
+        return true;
+      }
+
+      return false;
+    }
+
+  }
+
+  static class CData implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      if (entity.token != Token.TEXT)
+        parser.error("Expected CDATA");
+      parser.lookahead = this::next;
+      return true;
+    }
+
+    public boolean next(Parser parser, Parser.Entity entity)
+    {
+      if (entity.token != Token.LSB)
+        parser.error("Expected [");
+      parser.stack.clear();
+      parser.state = State.CDATA;
+      return true;
+    }
+
+  }
+
+  static class EndCData extends Parser.MatchRule
+  {
+
+    public EndCData()
+    {
+      super(Token.RSB, Token.RSB, Token.GT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      stack.removeLast(); // >
+      stack.removeLast(); // ]
+      stack.removeLast(); // ]
+      Entity first = stack.removeFirst();
+      StringBuilder sb = promote(first);
+      for (Entity e : stack)
+      {
+        sb.append(e.toString());
+      }
+      stack.clear();
+      ((HtmlParser) parser).handler.cdata(sb.toString());
+      parser.state = State.FREE;
+    }
+  }
+
+  static class Comment implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      if (entity.token != Token.DASH)
+        parser.error("Expected -");
+      parser.lookahead = this::next;
+      parser.stack.clear();
+      parser.state = State.COMMENT;
+      return true;
+    }
+
+    public boolean next(Parser parser, Parser.Entity entity)
+    {
+      if (entity.token == Token.DASH)
+        parser.error("Bad comment(-)");
+      if (entity.token == Token.GT)
+        parser.error("Bad comment(>)");
+      return false;
+    }
+  }
+
+  static class StartComment extends Parser.MatchRule
+  {
+
+    public StartComment()
+    {
+      super(Token.LT, Token.BANG, Token.DASH, Token.DASH);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      parser.lookahead = this::next;
+    }
+
+    public boolean next(Parser parser, Parser.Entity entity)
+    {
+      if (entity.token == Token.GT)
+        return false;
+      parser.error("Comment contains <!--");
+      return true;
+    }
+  }
+
+  /**
+   * Double dash in Comments.
+   */
+  static class EndComment extends Parser.MatchRule
+  {
+
+    public EndComment()
+    {
+      super(Token.DASH, Token.DASH, Token.GT);
+    }
+
+    @Override
+    public void execute(Parser parser)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      stack.removeLast(); // >
+      stack.removeLast(); // -
+      stack.removeLast(); // -
+      if (stack.isEmpty())
+        return;
+      Entity first = stack.removeFirst();
+      StringBuilder sb = promote(first);
+      for (Entity e : stack)
+      {
+        sb.append(e.toString());
+      }
+      stack.clear();
+      ((HtmlParser) parser).handler.comment(sb.toString());
+      parser.state = State.FREE;
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="quote">
+  static class StartQuote implements Rule
+  {
+
+    @Override
+    public boolean apply(Parser parser, Parser.Entity entity)
+    {
+      if (entity.token == Token.QUOTE
+              || entity.token == Token.SQUOTE)
+      {
+        parser.state = new Quoted(entity.token, parser.state);
+        parser.stack.removeLast();
+        parser.add(Token.TEXT, entity.toString());
+        return true;
+      }
+      return false;
+    }
+  }
+
+  static class Quoted implements Parser.State, Parser.Rule
+  {
+
+    Parser.Token[] tokens;
+    Parser.Rule[] rules;
+    private Parser.State state;
+
+    Quoted(Parser.Token token, Parser.State state)
+    {
+      this.tokens = new Parser.Token[]
+      {
+        token, Token.TEXT
+      };
+      rules = new Parser.Rule[]
+      {
+        this, mergeText
+      };
+      this.state = state;
+    }
+
+    @Override
+    public Parser.Token[] getTokens()
+    {
+      return tokens;
+    }
+
+    @Override
+    public Parser.Rule[] getRules()
+    {
+      return rules;
+    }
+
+    @Override
+    public boolean apply(Parser<?> parser, Parser.Entity entity)
+    {
+      if (entity.token != tokens[0])
+        return false;
+      parser.stack.removeLast();
+      Entity last = parser.stack.getLast();
+      promote(last).append('"');
+      parser.state = state;
+      return true;
+    }
+  }
+
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlHandler.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlHandler.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlHandler.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlHandler.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,39 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+public interface HtmlHandler
+{
+
+  void cdata(String text);
+
+  void comment(String contents);
+
+  void endDocument();
+
+  void endElement(String name);
+
+  void startDocument();
+
+  void startElement(String name, String attr);
+
+  void text(String text);
+
+  public Object getResult();
+
+  public void directive(String content);
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlParser.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlParser.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlParser.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlParser.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,30 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import org.w3c.dom.Document;
+
+public class HtmlParser extends Parser<Document>
+{
+
+  HtmlHandler handler = new HtmlTreeHandler();
+
+  public HtmlParser()
+  {
+    super(HtmlGrammar.INSTANCE);
+  }
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlTreeHandler.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlTreeHandler.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlTreeHandler.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlTreeHandler.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,230 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+/**
+ * HTML document handler which creates an HTML tree.
+ */
+public class HtmlTreeHandler implements HtmlHandler
+{
+
+  final Document root;
+  LinkedList<Element> elementStack = new LinkedList<>();
+  AttrParser attrParser;
+  Node current;
+  int errors = 0;
+
+  public HtmlTreeHandler()
+  {
+    try
+    {
+      DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+      root = db.newDocument();
+      current = root;
+      attrParser = new AttrParser(root);
+    } catch (ParserConfigurationException ex)
+    {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  private String lastNodeName()
+  {
+    if (this.elementStack.isEmpty())
+      return "";
+    return this.elementStack.getLast().getNodeName();
+  }
+
+  @Override
+  public void startElement(String name, String attr)
+  {
+    name = name.toLowerCase().trim();
+    String attr0 = attr;
+
+    // Html has irregular end rules.
+    while (Html.OPTIONAL_ELEMENTS.contains(name))
+    {
+      String close = lastNodeName() + ":" + name;
+      if (Html.OPTIONAL_CLOSE.contains(close))
+      {
+//        System.out.print("AUTO ");
+        this.endElement(lastNodeName());
+      } else
+        break;
+    }
+
+//    System.out.println(this.elementStack.size() + " " + name + " : " + attr);
+    Element elem;
+    try
+    {
+      elem = root.createElement(name);
+    } catch (Exception ex)
+    {
+      throw new RuntimeException("Fail to create node '" + name + "'", ex);
+    }
+    if (attr != null)
+    {
+      for (Attr a : attrParser.parse(attr))
+        elem.setAttributeNode(a);
+    }
+    current.appendChild(elem);
+    if (Html.VOID_ELEMENTS.contains(name))
+      return;
+    current = elem;
+    elementStack.add(elem);
+  }
+
+  public String getPath()
+  {
+    StringBuilder path = new StringBuilder();
+    for (Element s : this.elementStack)
+    {
+      path.append("/");
+      path.append(s.getNodeName());
+      NamedNodeMap attrs = s.getAttributes();
+      if (attrs.getLength() > 0)
+      {
+        path.append('[');
+        for (int i = 0; i < attrs.getLength(); ++i)
+        {
+          Attr item = (Attr) attrs.item(i);
+          path.append(item.getName());
+          path.append('=');
+          path.append(item.getValue());
+          path.append(' ');
+        }
+        path.append(']');
+      }
+    }
+    return path.toString();
+  }
+
+  @Override
+  public void endElement(String name)
+  {
+    name = name.toLowerCase().trim();
+    if (elementStack.isEmpty())
+      throw new RuntimeException("Empty stack");
+    Element last = elementStack.getLast();
+    // Handle auto class tags
+    while (!last.getNodeName().equals(name) && Html.OPTIONAL_ELEMENTS.contains(last.getNodeName()))
+    {
+//      System.out.print("AUTO2 ");
+      endElement(last.getNodeName());
+      last = elementStack.getLast();
+    }
+//    System.out.println(this.elementStack.size() - 1 + " ~" + name);
+    if (!last.getNodeName().equals(name))
+    {
+      errors++;
+
+      // Try to deal with unclosed tags gracefully.
+      ListIterator<Element> iter = this.elementStack.listIterator(this.elementStack.size() - 1);
+      int i = 0;
+      while (iter.hasNext())
+      {
+        Element prev = iter.previous();
+        if (prev.getNodeName().equals(name))
+          break;
+        i++;
+      }
+      if (iter.hasPrevious())
+      {
+        for (int j = 0; j < i; j++)
+        {
+          System.err.println("Ignoring missing close tag " + last.getNodeName() + ", got " + name + " at " + getPath());
+          this.endElement(last.getNodeName());
+        }
+      } else if (errors > 3)
+        throw new RuntimeException("mismatch element " + name
+                + " " + last.getNodeName() + " at " + getPath());
+      else
+      {
+        System.err.println("Ignoring mismatched element " + name
+                + " " + last.getNodeName() + " at " + getPath());
+        return;
+      }
+    }
+    elementStack.removeLast();
+    if (elementStack.isEmpty())
+      current = root;
+    else
+      current = elementStack.getLast();
+  }
+
+  @Override
+  public void comment(String contents)
+  {
+    if (contents.equals(">"))
+      throw new RuntimeException();
+    current.appendChild(root.createComment(contents));
+  }
+
+  @Override
+  public void text(String text)
+  {
+//    System.out.println("  TEXT " + text);
+    if (text.length() == 0)
+      return;
+    if (text.contains("<"))
+      throw new RuntimeException("bad text `" + text + "` at " + getPath());
+    if (current == root)
+      return;
+    current.appendChild(root.createTextNode(text));
+  }
+
+  @Override
+  public void cdata(String text)
+  {
+    current.appendChild(root.createCDATASection(text));
+  }
+
+  @Override
+  public void startDocument()
+  {
+  }
+
+  @Override
+  public void endDocument()
+  {
+  }
+
+  @Override
+  public Object getResult()
+  {
+    return root;
+  }
+
+  @Override
+  public void directive(String content)
+  {
+    int i = content.indexOf(" ");
+    current.appendChild(root.createProcessingInstruction(content.substring(0, i),
+            content.substring(i).trim()));
+  }
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlWriter.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlWriter.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlWriter.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/HtmlWriter.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,172 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import org.w3c.dom.Attr;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+
+public class HtmlWriter implements Closeable
+{
+
+  OutputStream os;
+  BufferedWriter writer;
+
+  public HtmlWriter(OutputStream os)
+  {
+    this.os = os;
+    this.writer = new BufferedWriter(new OutputStreamWriter(os));
+  }
+
+  public static String asString(Node node) throws IOException
+  {
+    try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
+    {
+      HtmlWriter hw = new HtmlWriter(baos);
+      hw.write(node);
+      hw.close();
+      return baos.toString();
+    }
+  }
+
+  public void write(Node n) throws IOException
+  {
+
+    if (n == null)
+    {
+      writer.write("NULL");
+      return;
+    }
+    switch (n.getNodeType())
+    {
+      case Node.PROCESSING_INSTRUCTION_NODE:
+        writeDirective((ProcessingInstruction) n);
+        break;
+      case Node.ELEMENT_NODE:
+        writeElement((Element) n);
+        break;
+      case Node.DOCUMENT_FRAGMENT_NODE:
+        writeChildren(n);
+        break;
+      case Node.TEXT_NODE:
+        writeText((Text) n);
+        break;
+      case Node.COMMENT_NODE:
+        writeComment((Comment) n);
+        break;
+      case Node.CDATA_SECTION_NODE:
+        writeCData((CDATASection) n);
+        break;
+      default:
+        throw new RuntimeException("unhandled " + n.getClass());
+    }
+  }
+
+  public void writeChildren(Node doc) throws IOException
+  {
+    NodeList children = doc.getChildNodes();
+    for (int i = 0; i < children.getLength(); ++i)
+    {
+      write(children.item(i));
+    }
+  }
+
+  public void writeDirective(ProcessingInstruction d) throws IOException
+  {
+    writer.write("<!");
+    writer.write(d.getTarget());
+    writer.write(" ");
+    writer.write(d.getData());
+    writer.write(">");
+  }
+
+  public void writeElement(Element e) throws IOException
+  {
+    String name = e.getTagName();
+    writer.write("<");
+    writer.write(name);
+    NamedNodeMap attributes = e.getAttributes();
+    if (attributes.getLength() > 0)
+    {
+      for (int i = 0; i < attributes.getLength(); ++i)
+      {
+        writer.write(" ");
+        Attr attr = (Attr) attributes.item(i);
+        writer.write(attr.getName());
+        writer.write("=\"");
+        writer.write(attr.getValue());
+        writer.write('"');
+      }
+    }
+    if (Html.VOID_ELEMENTS.contains(name))
+    {
+      writer.write(">");
+      return;
+    }
+
+    NodeList children = e.getChildNodes();
+
+    if (children.getLength() == 0)
+    {
+      writer.write("/>");
+    } else
+    {
+      writer.write(">");
+      writeChildren(e);
+      writer.write("</");
+      writer.write(name);
+      writer.write(">");
+    }
+  }
+
+  private void writeComment(Comment comment) throws IOException
+  {
+    writer.write("<!--");
+    writer.write(comment.getData());
+    writer.write("-->");
+  }
+
+  private void writeCData(CDATASection cData) throws IOException
+  {
+    writer.write("<![CDATA[");
+    writer.write(cData.getData());
+    writer.write("]]>");
+  }
+
+  private void writeText(Text text) throws IOException
+  {
+    writer.write(text.getData());
+  }
+
+  @Override
+  public void close() throws IOException
+  {
+    writer.close();
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/Parser.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/Parser.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/Parser.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/Parser.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,304 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.html;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+/**
+ * Generic document parser.
+ *
+ * @param <T>
+ */
+public class Parser<T>
+{
+
+  final Grammar grammar;
+  public State state = null;
+  public Token last = null;
+  public Rule lookahead = null;
+  public LinkedList<Entity> stack = new LinkedList<>();
+
+  Parser(Grammar grammar)
+  {
+    this.grammar = grammar;
+  }
+
+  public T parse(InputStream is)
+  {
+    ByteBuffer incoming = ByteBuffer.allocate(1024);
+    ByteBuffer outgoing = ByteBuffer.allocate(1024);
+    ReadableByteChannel channel = Channels.newChannel(is);
+    stack.clear();
+    grammar.start(this);
+    try
+    {
+      while (true)
+      {
+        incoming.position(0);
+        int rc = channel.read(incoming);
+        if (rc < 0)
+          break;
+        int p = incoming.position();
+        incoming.rewind();
+        process(incoming, outgoing, rc);
+      }
+      flushTokens(outgoing);
+    } catch (IOException ex)
+    {
+      throw new RuntimeException(ex);
+    }
+    return (T) grammar.end(this);
+  }
+
+  public T parse(String str)
+  {
+    byte[] b = str.getBytes();
+    ByteBuffer incoming = ByteBuffer.wrap(b);
+    ByteBuffer outgoing = ByteBuffer.allocate(1024);
+    stack.clear();
+    grammar.start(this);
+    process(incoming, outgoing, b.length);
+    flushTokens(outgoing);
+    return (T) grammar.end(this);
+  }
+
+  private void process(ByteBuffer incoming, ByteBuffer outgoing, int rc)
+  {
+    while (incoming.position() < rc)
+    {
+      byte b = incoming.get();
+      Token match = null;
+      for (Token t : state.getTokens())
+      {
+        if (t.matches(b))
+        {
+          match = t;
+          break;
+        }
+      }
+      if (match == null)
+        this.error("Unable to parse " + (char) b);
+      else if (match.runs())
+      {
+        if (last != match)
+          flushTokens(outgoing);
+        if (!outgoing.hasRemaining())
+          flushTokens(outgoing);
+        outgoing.put(b);
+      } else
+      {
+        if (outgoing.position() > 0)
+          flushTokens(outgoing);
+        processToken(match, null);
+      }
+      last = match;
+    }
+  }
+
+  /**
+   * Send all the queue up text to a token.
+   */
+  private void flushTokens(ByteBuffer outgoing)
+  {
+    if (outgoing.position() == 0)
+      return;
+    processToken(last, new String(outgoing.array(), 0, outgoing.position()));
+    outgoing.rewind();
+  }
+
+  /**
+   * Process a token.
+   *
+   * This will add it to the stack and then match the stack with the nearest
+   * rule.
+   *
+   * @param token
+   * @param value
+   */
+  protected void processToken(Token token, String value)
+  {
+    if (token == null)
+      return;
+    Entity entity = add(token, value);
+
+    // Take the next lookahead
+    Rule rule1 = this.lookahead;
+    this.lookahead = null;
+    if (rule1 != null)
+    {
+//      System.out.println("      FORWARD " + rule1);
+      if (rule1.apply(this, entity))
+      {
+        return;
+      }
+    }
+
+    // If not handled then proceed to rules.
+    boolean done = false;
+    while (!done && !stack.isEmpty())
+    {
+      done = true;
+      for (Rule rule : state.getRules())
+      {
+//        System.out.println("      RULE " + rule);
+        if (rule.apply(this, stack.getLast()))
+        {
+          done = false;
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Add a token to the token stack
+   *
+   * @param token
+   * @param object
+   * @return
+   */
+  public Entity add(Token token, String object)
+  {
+    Entity entity = new Entity(token, object);
+    this.stack.add(entity);
+    return entity;
+  }
+
+  public void error(String bad_token)
+  {
+    throw new RuntimeException("bad_token");
+  }
+
+  public interface State
+  {
+
+    Token[] getTokens();
+
+    Rule[] getRules();
+  }
+
+  public interface Token
+  {
+
+    int ordinal();
+
+    public boolean matches(byte b);
+
+    public boolean runs();
+  }
+
+  public interface Rule
+  {
+
+    boolean apply(Parser<?> parser, Entity entity);
+  }
+
+  public interface Grammar
+  {
+
+    /**
+     * Should set the initial state.
+     *
+     * @param p
+     */
+    public void start(Parser p);
+
+    /**
+     * Should check the state of the stack, fail if bad, or return the final
+     * object if good.
+     *
+     * @param p
+     * @return
+     */
+    public Object end(Parser p);
+  }
+
+  /**
+   * Token or text.
+   */
+  public static class Entity
+  {
+
+    public Token token;
+    public Object value;
+
+    private Entity(Token token)
+    {
+      this.token = token;
+    }
+
+    private Entity(Token token, String value)
+    {
+      this.token = token;
+      this.value = value;
+    }
+
+    @Override
+    public String toString()
+    {
+      if (value == null)
+        return token.toString();
+      return value.toString();
+    }
+  }
+
+  //<editor-fold desc="common">
+  /**
+   * Generic matcher for multiple tokens on the stack.
+   */
+  abstract static class MatchRule implements Rule
+  {
+
+    Token[] pattern;
+
+    MatchRule(Token... tokens)
+    {
+      this.pattern = tokens;
+    }
+
+    @Override
+    public boolean apply(Parser parser, Entity entity)
+    {
+      LinkedList<Entity> stack = parser.stack;
+      int n = stack.size();
+      if (n < pattern.length)
+        return false;
+      ListIterator<Entity> iter = stack.listIterator(stack.size());
+      for (int i = 0; i < pattern.length; ++i)
+      {
+        if (!iter.hasPrevious())
+          return false;
+        Entity next = iter.previous();
+        if (next.token != pattern[pattern.length - i - 1])
+        {
+          return false;
+        }
+      }
+      execute(parser);
+      return true;
+    }
+
+    abstract public void execute(Parser parser);
+  }
+
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/entities.txt 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/entities.txt
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/html/entities.txt	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/html/entities.txt	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,256 @@
+# Portions © International Organization for Standardization 1986:
+#     Permission to copy in any form is granted for use with
+#     conforming SGML systems and applications as defined in
+#     ISO 8879, provided this notice is included in all copies.
+nbsp     160
+iexcl    161
+cent     162
+pound    163
+curren   164
+yen      165
+brvbar   166
+sect     167
+uml      168
+copy     169
+ordf     170
+laquo    171
+not      172
+shy      173
+reg      174
+macr     175
+deg      176
+plusmn   177
+sup2     178
+sup3     179
+acute    180
+micro    181
+para     182
+middot   183
+cedil    184
+sup1     185
+ordm     186
+raquo    187
+frac14   188
+frac12   189
+frac34   190
+iquest   191
+Agrave   192
+Aacute   193
+Acirc    194
+Atilde   195
+Auml     196
+Aring    197
+AElig    198
+Ccedil   199
+Egrave   200
+Eacute   201
+Ecirc    202
+Euml     203
+Igrave   204
+Iacute   205
+Icirc    206
+Iuml     207
+ETH      208
+Ntilde   209
+Ograve   210
+Oacute   211
+Ocirc    212
+Otilde   213
+Ouml     214
+times    215
+Oslash   216
+Ugrave   217
+Uacute   218
+Ucirc    219
+Uuml     220
+Yacute   221
+THORN    222
+szlig    223
+agrave   224
+aacute   225
+acirc    226
+atilde   227
+auml     228
+aring    229
+aelig    230
+ccedil   231
+egrave   232
+eacute   233
+ecirc    234
+euml     235
+igrave   236
+iacute   237
+icirc    238
+iuml     239
+eth      240
+ntilde   241
+ograve   242
+oacute   243
+ocirc    244
+otilde   245
+ouml     246
+divide   247
+oslash   248
+ugrave   249
+uacute   250
+ucirc    251
+uuml     252
+yacute   253
+thorn    254
+yuml     255
+fnof     402
+Alpha    913
+Beta     914
+Gamma    915
+Delta    916
+Epsilon  917
+Zeta     918
+Eta      919
+Theta    920
+Iota     921
+Kappa    922
+Lambda   923
+Mu       924
+Nu       925
+Xi       926
+Omicron  927
+Pi       928
+Rho      929
+Sigma    931
+Tau      932
+Upsilon  933
+Phi      934
+Chi      935
+Psi      936
+Omega    937
+alpha    945
+beta     946
+gamma    947
+delta    948
+epsilon  949
+zeta     950
+eta      951
+theta    952
+iota     953
+kappa    954
+lambda   955
+mu       956
+nu       957
+xi       958
+omicron  959
+pi       960
+rho      961
+sigmaf   962
+sigma    963
+tau      964
+upsilon  965
+phi      966
+chi      967
+psi      968
+omega    969
+thetasym 977
+upsih    978
+piv      982
+bull     8226
+hellip   8230
+prime    8242
+Prime    8243
+oline    8254
+frasl    8260
+weierp   8472
+image    8465
+real     8476
+trade    8482
+alefsym  8501
+larr     8592
+uarr     8593
+rarr     8594
+darr     8595
+harr     8596
+crarr    8629
+lArr     8656
+uArr     8657
+rArr     8658
+dArr     8659
+hArr     8660
+forall   8704
+part     8706
+exist    8707
+empty    8709
+nabla    8711
+isin     8712
+notin    8713
+ni       8715
+prod     8719
+sum      8721
+minus    8722
+lowast   8727
+radic    8730
+prop     8733
+infin    8734
+ang      8736
+and      8743
+or       8744
+cap      8745
+cup      8746
+int      8747
+there4   8756
+sim      8764
+cong     8773
+asymp    8776
+ne       8800
+equiv    8801
+le       8804
+ge       8805
+sub      8834
+sup      8835
+nsub     8836
+sube     8838
+supe     8839
+oplus    8853
+otimes   8855
+perp     8869
+sdot     8901
+lceil    8968
+rceil    8969
+lfloor   8970
+rfloor   8971
+lang     9001
+rang     9002
+loz      9674
+spades   9824
+clubs    9827
+hearts   9829
+diams    9830
+quot     34
+amp      38
+lt       60
+gt       62
+OElig    338
+oelig    339
+Scaron   352
+scaron   353
+Yuml     376
+circ     710
+tilde    732
+ensp     8194
+emsp     8195
+thinsp   8201
+zwnj     8204
+zwj      8205
+lrm      8206
+rlm      8207
+ndash    8211
+mdash    8212
+lsquo    8216
+rsquo    8217
+sbquo    8218
+ldquo    8220
+rdquo    8221
+bdquo    8222
+dagger   8224
+Dagger   8225
+permil   8240
+lsaquo   8249
+rsaquo   8250
+euro     8364
\ No newline at end of file
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/DomUtilities.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/DomUtilities.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/DomUtilities.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/DomUtilities.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,263 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.javadoc;
+
+/**
+ * The usual set of method required to work on DOM.
+ *
+ * DOM leaves a lot of basic stuff incomplete.
+ */
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+public class DomUtilities
+{
+
+  /**
+   * Traverse all children in depth first order applying an operation.
+   *
+   * This is hardened against some level of DOM changes.
+   *
+   * @param node
+   * @param operator
+   * @param type
+   */
+  public static void traverseDFS(Node node, Consumer<Node> operator, short type)
+  {
+    Node child = node.getFirstChild();
+    while (child != null)
+    {
+      // Get a referent to what we are processing next in case the tree changes.
+      Node next = child.getNextSibling();
+
+      // Apply transforms to children first
+      if (child.getNodeType() == Node.ELEMENT_NODE)
+        traverseDFS(child, operator, type);
+
+      // Then process the outer element
+      if (child.getNodeType() == type)
+        operator.accept(child);
+
+      // Proceed
+      child = next;
+    }
+  }
+
+  /**
+   * Traverse the children of a node applying an operation.
+   *
+   * This is hardened against some level of DOM changes.
+   *
+   * @param node
+   * @param operator
+   * @param type
+   */
+  public static void traverseChildren(Node node, Consumer<Node> operator, short type)
+  {
+    Node child = node.getFirstChild();
+    while (child != null)
+    {
+      // Get the next node to process in case this one is changed or removed.
+      Node next = child.getNextSibling();
+      if (child.getNodeType() == type)
+        operator.accept(child);
+
+      // Proceed.
+      child = next;
+    }
+  }
+
+  /**
+   * Traverse all children in depth first order applying an operation.
+   *
+   * This is hardened against some level of DOM changes.
+   *
+   * @param node
+   * @param operator
+   * @param type
+   */
+  public static <T> void traverseDFS(Node node,
+          BiConsumer<Node, T> operator, short type, T data)
+  {
+    Node child = node.getFirstChild();
+    while (child != null)
+    {
+      // Get a referent to what we are processing next in case the tree changes.
+      Node next = child.getNextSibling();
+
+      // Apply transforms to children first
+      if (child.getNodeType() == Node.ELEMENT_NODE)
+        traverseDFS(child, operator, type, data);
+
+      // Then process the outer element
+      if (child.getNodeType() == type)
+        operator.accept(child, data);
+
+      // Proceed
+      child = next;
+    }
+  }
+
+  /**
+   * Traverse the children of a node applying an operation.
+   *
+   * This is hardened against some level of DOM changes.
+   *
+   * @param node
+   * @param operator
+   * @param type
+   */
+  public static <T> void traverseChildren(Node node, BiConsumer<Node, T> operator, short type, T data)
+  {
+    Node child = node.getFirstChild();
+    while (child != null)
+    {
+      // Get the next node to process in case this one is changed or removed.
+      Node next = child.getNextSibling();
+      if (child.getNodeType() == type)
+        operator.accept(child, data);
+
+      // Proceed.
+      child = next;
+    }
+  }
+
+  /**
+   * Remove all attributes from a node.
+   *
+   * @param node
+   */
+  public static void clearAttributes(Node node)
+  {
+    while (node.getAttributes().getLength() > 0)
+    {
+      Node att = node.getAttributes().item(0);
+      node.getAttributes().removeNamedItem(att.getNodeName());
+    }
+  }
+
+  /**
+   * Remove all children from a node.
+   *
+   * @param node
+   */
+  public static void clearChildren(Node node)
+  {
+    while (node.hasChildNodes())
+      node.removeChild(node.getFirstChild());
+  }
+
+  /**
+   * Determine if a block contains a new line.
+   *
+   * @param n
+   * @return
+   */
+  public static boolean containsNL(Node n)
+  {
+    Node child = n.getFirstChild();
+    while (child != null)
+    {
+      if (child.getNodeType() == Node.TEXT_NODE)
+      {
+        if (child.getNodeValue().contains("\n"))
+          return true;
+      }
+      child = child.getNextSibling();
+    }
+    return false;
+  }
+
+  /**
+   * Combine all text with neighbors in immediate children.
+   *
+   * @param node
+   */
+  public static void combineText(Node node)
+  {
+    // merge text nodes
+    Node child = node.getFirstChild();
+    while (child != null)
+    {
+      Node next = child.getNextSibling();
+      if (child.getNodeType() != Node.TEXT_NODE)
+      {
+        child = next;
+        continue;
+      }
+      if (next != null && next.getNodeType() == Node.TEXT_NODE)
+      {
+        child.setTextContent(child.getNodeValue() + next.getNodeValue());
+        child.getParentNode().removeChild(next);
+        continue;
+      }
+      child = next;
+    }
+  }
+
+  /**
+   * Merge the contents of a node with its parent.
+   *
+   * @param parent
+   * @param node
+   */
+  public static void mergeNode(Node parent, Node node)
+  {
+    while (node.hasChildNodes())
+    {
+      parent.insertBefore(node.getFirstChild(), node);
+    }
+    parent.removeChild(node);
+  }
+
+  static void transferContents(Node dest, Node source)
+  {
+    while (source.hasChildNodes())
+    {
+      dest.appendChild(source.getFirstChild());
+    }
+  }
+
+  /**
+   * Traverse a node and replaces all extra whitespace with one space.
+   *
+   * This should be applied to any element where white space is not relevant.
+   *
+   * @param node
+   */
+  public static void removeWhitespace(Node node)
+  {
+    // merge text nodes
+    NodeList children = node.getChildNodes();
+    for (int i = 0; i < children.getLength(); ++i)
+    {
+      Node child = children.item(i);
+      if (child.getNodeType() != Node.TEXT_NODE)
+        continue;
+      Text t = (Text) child;
+      String c = t.getNodeValue();
+      if (c != null)
+      {
+        c = c.replaceAll("\\s+", " ");
+        t.setNodeValue(c);
+      }
+    }
+  }
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/Javadoc.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/Javadoc.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/Javadoc.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/Javadoc.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,37 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.javadoc;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.w3c.dom.Node;
+
+public class Javadoc
+{
+
+  public String description;
+  public String ctors;
+  public Map<String, String> methods = new HashMap<>();
+  public Map<String, String> fields = new HashMap<>();
+
+  // These will be removed once debugging is complete
+  public Node descriptionNode;
+  public List<Node> ctorsNode;
+  public List<Node> methodNodes;
+  public List<Node> innerNode;
+  public List<Node> fieldNodes;
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocException.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocException.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocException.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocException.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,30 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.javadoc;
+
+import org.w3c.dom.Node;
+
+public class JavadocException extends RuntimeException
+{
+
+  public Node node;
+
+  public JavadocException(Node node, Throwable th)
+  {
+    super(th);
+    this.node = node;
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocExtractor.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocExtractor.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocExtractor.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocExtractor.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,275 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.javadoc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.jpype.html.Html;
+import org.jpype.html.Parser;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class JavadocExtractor
+{
+
+  static final JavadocTransformer transformer = new JavadocTransformer();
+  static public boolean transform = true;
+  static public boolean render = true;
+  static public boolean failures = false;
+
+  /**
+   * Search the classpath for documentation.
+   *
+   * @param cls
+   * @return
+   */
+  public static Javadoc getDocumentation(Class cls)
+  {
+    try
+    {
+      try (InputStream is = getDocumentationAsStream(cls))
+      {
+        if (is != null)
+        {
+          Parser<Document> parser = Html.newParser();
+          return extractDocument(cls, parser.parse(is));
+        }
+      }
+    } catch (Exception ex)
+    {
+      System.err.println("Failed to extract javadoc for " + cls + ", caught " + ex);
+      if (failures)
+        throw new RuntimeException(ex);
+    }
+    return null;
+  }
+
+  public static InputStream getDocumentationAsStream(Class cls)
+  {
+    InputStream is = null;
+    String name = cls.getName().replace('.', '/') + ".html";
+    ClassLoader cl = ClassLoader.getSystemClassLoader();
+
+    // Search the regular class path.
+    is = cl.getResourceAsStream(name);
+    if (is != null)
+      return is;
+
+    // Search for api documents
+    String name1 = "docs/api/" + name;
+    is = cl.getResourceAsStream(name1);
+    if (is != null)
+      return is;
+
+    // If we are dealing with Java 9+, the doc tree is different
+    try
+    {
+      Method meth = Class.class.getMethod("getModule");
+      String module = meth.invoke(cls).toString().substring(7);
+      String name2 = "docs/api/" + module + "/" + name;
+      is = cl.getResourceAsStream(name2);
+      if (is != null)
+        return is;
+    } catch (NoSuchMethodException | SecurityException | IllegalAccessException
+            | IllegalArgumentException | InvocationTargetException ex)
+    {
+      // do nothing if we are not JDK 9+
+    }
+    return null;
+  }
+
+  /**
+   * Extract the documentation from the dom.
+   *
+   * @param cls is the class being processed.
+   * @param doc is the DOM holding the javadoc.
+   * @return
+   */
+  public static Javadoc extractDocument(Class cls, Document doc)
+  {
+    JavadocRenderer renderer = new JavadocRenderer();
+    try
+    {
+      Javadoc documentation = new Javadoc();
+      XPath xPath = XPathFactory.newInstance().newXPath();
+      // Javadoc 8-13
+      Node n = (Node) xPath.compile("//div[@class='description']/ul/li").evaluate(doc, XPathConstants.NODE);
+      if (n == null)
+      { // Javadoc 14+
+        n = (Node) xPath.compile("//section[@class='description']").evaluate(doc, XPathConstants.NODE);
+      }
+      if (n == null)
+      { // Javadoc 17+
+        n = (Node) xPath.compile("//section[@class='class-description']").evaluate(doc, XPathConstants.NODE);
+      }
+      Node description = toFragment(n);
+      if (description != null)
+      {
+        documentation.descriptionNode = description;
+        if (transform)
+          transformer.transformDescription(cls, description);
+        if (render)
+          documentation.description = renderer.render(description);
+      }
+
+      Node ctorRoot = ((Node) xPath.compile("//li/a[@name='constructor.detail' or @id='constructor.detail']") // Javadoc before 17
+              .evaluate(doc, XPathConstants.NODE));
+      if (ctorRoot == null)
+      { // Javadoc 17+
+        ctorRoot = ((Node) xPath.compile("//section[@class='constructor-details']/ul")
+                .evaluate(doc, XPathConstants.NODE));
+      }
+      if (ctorRoot != null)
+      {
+        List<Node> set = convertNodes((NodeList) xPath.compile("./li/section") // Javadoc 17+
+                .evaluate(ctorRoot, XPathConstants.NODESET));
+        if (set.isEmpty())
+        {  // Javadoc before 17
+          set = convertNodes((NodeList) xPath.compile("./ul/li")
+                  .evaluate(ctorRoot.getParentNode(), XPathConstants.NODESET));
+        }
+        documentation.ctorsNode = set;
+        StringBuilder sb = new StringBuilder();
+        for (Node ctor : set)
+        {
+          if (transform)
+            transformer.transformMember(cls, ctor);
+          if (render)
+            sb.append(renderer.render(ctor));
+        }
+        documentation.ctors = sb.toString();
+      }
+
+      Node methodRoot = ((Node) xPath.compile("//li/a[@name='method.detail' or  @id='method.detail']") // Javadoc before 17
+              .evaluate(doc, XPathConstants.NODE));
+      if (methodRoot == null)
+      { // Javadoc 17+
+        methodRoot = ((Node) xPath.compile("//section[@class='method-details']/ul")
+                .evaluate(doc, XPathConstants.NODE));
+      }
+      if (methodRoot != null)
+      {
+        List<Node> set = convertNodes((NodeList) xPath.compile("./li/section") // Javadoc 17+
+                .evaluate(methodRoot, XPathConstants.NODESET));
+        if (set.isEmpty())
+        {  // Javadoc before 17
+          set = convertNodes((NodeList) xPath.compile("./ul/li")
+                  .evaluate(methodRoot.getParentNode(), XPathConstants.NODESET));
+        }
+        documentation.methodNodes = set;
+        for (Node method : set)
+        {
+          if (transform)
+            transformer.transformMember(cls, method);
+          if (render)
+          {
+            String str = renderer.render(method);
+            String name = renderer.memberName;
+            if (documentation.methods.containsKey(name))
+            {
+              String old = documentation.methods.get(name);
+              str = old + str;
+            }
+            documentation.methods.put(name, str);
+          }
+        }
+      }
+
+//      Node inner = (Node) xPath.compile("//li/a[@name='nested_class_summary']").evaluate(doc, XPathConstants.NODE);
+//      if (inner != nullList)
+//      {
+//        NodeList set = (NodeList) xPath.compile("./ul/li").evaluate(inner.getParentNode(), XPathConstants.NODESET);
+//        documentation.innerNode = convertNodes(set);
+//      }
+      Node fieldRoot = ((Node) xPath.compile("//li/a[@name='field.detail' or @id='field.detail']") // Javadoc before 17
+              .evaluate(doc, XPathConstants.NODE));
+      if (fieldRoot == null)
+      { // Javadoc 17+
+        fieldRoot = ((Node) xPath.compile("//section[@class='field-details']/ul")
+                .evaluate(doc, XPathConstants.NODE));
+      }
+      if (fieldRoot != null)
+      {
+        List<Node> set = convertNodes((NodeList) xPath.compile("./li/section") // Javadoc 17+
+                .evaluate(fieldRoot, XPathConstants.NODESET));
+        if (set.isEmpty())
+        {  // Javadoc before 17
+          set = convertNodes((NodeList) xPath.compile("./ul/li")
+                  .evaluate(fieldRoot.getParentNode(), XPathConstants.NODESET));
+        }
+        documentation.fieldNodes = set;
+        for (Node field : set)
+        {
+          if (transform)
+            transformer.transformMember(cls, field);
+          if (render)
+          {
+            String str = renderer.render(field);
+            String name = renderer.memberName;
+            documentation.fields.put(name, str);
+          }
+        }
+      }
+
+      return documentation;
+    } catch (IOException | XPathExpressionException ex)
+    {
+      throw new RuntimeException(ex);
+//      return null;
+    }
+  }
+
+  private static List<Node> convertNodes(NodeList nl) throws IOException
+  {
+    List<Node> out = new ArrayList<>();
+    for (int i = 0; i < nl.getLength(); ++i)
+    {
+      out.add(toFragment(nl.item(i)));
+    }
+    return out;
+  }
+
+  /**
+   * Convert a portion of the document into a fragment.
+   *
+   * @param node
+   * @return
+   */
+  public static Node toFragment(Node node)
+  {
+    Document doc = node.getOwnerDocument();
+    DocumentFragment out = doc.createDocumentFragment();
+    while (node.hasChildNodes())
+    {
+      out.appendChild(node.getFirstChild());
+    }
+    if (out.getFirstChild() != null && out.getFirstChild().getNodeType() == Node.TEXT_NODE)
+      out.removeChild(out.getFirstChild());
+    if (out.getLastChild() != null && out.getLastChild().getNodeType() == Node.TEXT_NODE)
+      out.removeChild(out.getLastChild());
+    return out;
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocRenderer.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocRenderer.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocRenderer.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocRenderer.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,411 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.javadoc;
+
+import java.nio.charset.StandardCharsets;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Render a node as ReStructured Text.
+ *
+ * @author nelson85
+ */
+public class JavadocRenderer
+{
+
+  static final int WIDTH = 120;
+  public StringBuilder assembly;
+  public int indentLevel = 0;
+  String memberName;
+
+  public String render(Node node)
+  {
+    try
+    {
+      indentLevel = 0;
+      assembly = new StringBuilder();
+      DomUtilities.traverseChildren(node, this::renderSections, Node.ELEMENT_NODE);
+      return assembly.toString();
+    } catch (Exception ex)
+    {
+      throw new JavadocException(node, ex);
+    }
+  }
+
+  /**
+   * Render the dom into restructured text.
+   *
+   * @param node
+   */
+  void renderSections(Node node)
+  {
+    Element e = (Element) node;
+    String name = e.getTagName();
+    if (name.equals("title"))
+    {
+      this.memberName = node.getTextContent();
+      return;
+    }
+    if (name.equals("signature"))
+    {
+      assembly.append(node.getTextContent())
+              .append("\n\n");
+      indentLevel += 4;
+      return;
+    }
+    if (name.equals("description"))
+    {
+      renderText(node, true, true);
+      return;
+    }
+    if (name.equals("details"))
+    {
+      DomUtilities.traverseChildren(node, this::renderDetails, Node.ELEMENT_NODE);
+      assembly.append("\n");
+      return;
+    }
+  }
+
+  final static Map<String, String> SECTIONS = new HashMap<>();
+
+  static
+  {
+    // Decide what sections to render
+    SECTIONS.put("returns", "Returns:");
+    SECTIONS.put("see", "See also:");
+    SECTIONS.put("since", "Since:");
+    SECTIONS.put("jls", "See Java\u2122 specification:");
+    SECTIONS.put("overrides", "Overrides:");
+    SECTIONS.put("specified", "Specified by:");
+    SECTIONS.put("version", null);
+    SECTIONS.put("typeparams", null);
+    SECTIONS.put("author", null);
+    SECTIONS.put("see", "Also see:");
+    SECTIONS.put("api_note", "API Note:");
+    SECTIONS.put("requirements", "Implementation Requirements:");
+    SECTIONS.put("impl_note", "Implementation Note:");
+  }
+
+  void renderDetails(Node node)
+  {
+    String name = node.getNodeName();
+    if (name.equals("parameters"))
+    {
+      assembly.append('\n')
+              .append(indentation(this.indentLevel))
+              .append("Parameters:\n");
+      indentLevel += 4;
+      DomUtilities.traverseChildren(node, this::renderParameter, Node.ELEMENT_NODE);
+      indentLevel -= 4;
+    } else if (name.equals("throws"))
+    {
+      assembly.append('\n')
+              .append(indentation(this.indentLevel))
+              .append("Raises:\n");
+      indentLevel += 4;
+      DomUtilities.traverseChildren(node, this::renderThrow, Node.ELEMENT_NODE);
+      indentLevel -= 4;
+    } else if (SECTIONS.containsKey(name))
+    {
+      String title = SECTIONS.get(name);
+      if (title == null)
+        return;
+      assembly.append('\n')
+              .append(indentation(this.indentLevel))
+              .append(title).append('\n');
+      indentLevel += 4;
+      renderText(node, true, true);
+      indentLevel -= 4;
+    } else
+    {
+      System.err.println("Need renderer for section " + name);
+    }
+  }
+
+  void renderParameter(Node node)
+  {
+    Element elem = (Element) node;
+    assembly.append(indentation(this.indentLevel))
+            //            .append("  ")
+            .append(elem.getAttribute("name"))
+            .append(" (")
+            .append(elem.getAttribute("type"))
+            .append("): ");
+    indentLevel += 4;
+    renderText(node, false, true);
+    indentLevel -= 4;
+  }
+
+  void renderThrow(Node node)
+  {
+    Element elem = (Element) node;
+    assembly.append(indentation(this.indentLevel))
+            //.append("  ")
+            .append(elem.getAttribute("name"))
+            .append(": ");
+    indentLevel += 4;
+    renderText(node, false, true);
+    indentLevel -= 4;
+  }
+
+  /**
+   * Render a paragraph or paragraph like element.
+   *
+   */
+  void renderText(Node node, boolean startIndent, boolean trailingNL)
+  {
+    DomUtilities.combineText(node);
+    DomUtilities.removeWhitespace(node);
+    Node child = node.getFirstChild();
+    for (; child != null; child = child.getNextSibling())
+    {
+      if (child.getNodeType() == Node.TEXT_NODE)
+      {
+        String value = child.getNodeValue();
+        if (value == null)
+          continue;
+        value = value.trim();
+        if (value.isEmpty())
+          continue;
+        formatWidth(assembly, value, WIDTH, indentLevel, startIndent);
+        if (trailingNL)
+          assembly.append("\n");
+        continue;
+      }
+      if (child.getNodeType() != Node.ELEMENT_NODE)
+        continue;
+      Element element = (Element) child;
+      String name = element.getTagName();
+      if (name.equals("p"))
+      {
+        assembly.append("\n");
+        renderText(element, true, true);
+      } else if (name.equals("div"))
+      {
+        renderText(element, true, true);
+      } else if (name.equals("center"))
+      {
+        renderText(element, true, true);
+      } else if (name.equals("br"))
+      {
+        assembly.append("\n\n");
+      } else if (name.equals("ul"))
+      {
+        renderUnordered(element);
+      } else if (name.equals("ol"))
+      {
+        renderOrdered(element);
+      } else if (name.equals("img"))
+      {
+        // punt
+      } else if (name.equals("table"))
+      {
+        // punt
+      } else if (name.equals("hr"))
+      {
+        // punt
+      } else if (name.equals("dl"))
+      {
+        renderDefinitions(element);
+      } else if (name.equals("codeblock"))
+      {
+        renderCodeBlock(element);
+      } else if (name.equals("blockquote"))
+      {
+        renderBlockQuote(element);
+      } else if (name.equals("h1"))
+      {
+        renderHeader(element);
+      } else if (name.equals("h2"))
+      {
+        renderHeader(element);
+      } else if (name.equals("h3"))
+      {
+        renderHeader(element);
+      } else if (name.equals("h4"))
+      {
+        renderHeader(element);
+      } else if (name.equals("h5"))
+      {
+        renderHeader(element);
+      } else
+      {
+        throw new RuntimeException("Need render for " + name);
+      }
+    }
+  }
+
+  void renderHeader(Node node)
+  {
+    assembly.append("\n");
+    renderText(node, true, true);
+    assembly.append(new String(new byte[node.getTextContent().length()]).replace('\0', '-'))
+            .append("\n\n");
+  }
+
+  void renderBlockQuote(Node node)
+  {
+    indentLevel += 4;
+    renderText(node, true, true);
+    indentLevel -= 4;
+  }
+
+  /**
+   * Render an unordered list.
+   *
+   * @param node
+   */
+  void renderOrdered(Node node)
+  {
+    indentLevel += 4;
+    assembly.append("\n");
+    Node child = node.getFirstChild();
+    int num = 1;
+    for (; child != null; child = child.getNextSibling())
+    {
+      if (child.getNodeType() != Node.ELEMENT_NODE)
+        continue;
+      if (child.getNodeName().equals("li"))
+      {
+        assembly.append(indentation(indentLevel - 2))
+                .append(String.format("%d.  ", num++));
+        renderText(child, false, true);
+      } else
+        throw new RuntimeException("Bad node " + child.getNodeName() + " in UL");
+    }
+    indentLevel -= 4;
+    assembly.append("\n");
+  }
+
+  /**
+   * Render an unordered list.
+   *
+   * @param node
+   */
+  void renderUnordered(Node node)
+  {
+    indentLevel += 4;
+    assembly.append("\n");
+    Node child = node.getFirstChild();
+    for (; child != null; child = child.getNextSibling())
+    {
+      if (child.getNodeType() != Node.ELEMENT_NODE)
+        continue;
+      if (child.getNodeName().equals("li"))
+      {
+        assembly.append(indentation(indentLevel - 4))
+                .append("  - ");
+        renderText(child, false, true);
+      } else
+        throw new RuntimeException("Bad node " + child.getNodeName() + " in UL");
+    }
+    indentLevel -= 4;
+    assembly.append("\n");
+  }
+
+  /**
+   * Render a definition list.
+   *
+   * @param node
+   */
+  void renderDefinitions(Node node)
+  {
+    Node child = node.getFirstChild();
+    for (; child != null; child = child.getNextSibling())
+    {
+      if (child.getNodeType() != Node.ELEMENT_NODE)
+        continue;
+      String name = child.getNodeName();
+      if (name.equals("dt"))
+      {
+        assembly.append("\n");
+        renderText(child, true, true);
+      } else if (name.equals("dd"))
+      {
+        assembly.append(indentation(indentLevel));
+        indentLevel += 4;
+        assembly.append("  ");
+        renderText(child, false, true);
+        indentLevel -= 4;
+      } else
+        throw new RuntimeException("Bad node " + name + " in DL");
+    }
+    assembly.append("\n");
+  }
+
+  void renderCodeBlock(Node node)
+  {
+    String indent = indentation(indentLevel);
+    assembly.append("\n")
+            .append(indent)
+            .append(".. code-block: java\n");
+    String text = node.getTextContent();
+    if (text.charAt(0) != '\n')
+      text = "\n" + text;
+    text = text.replaceAll("\n", "\n" + indent);
+    assembly.append(indent).append(text).append("\n");
+  }
+
+//<editor-fold desc="text-utilities" defaultstate="collapsed">
+  static final String SPACING = new String(new byte[40]).replace('\0', ' ');
+
+  static String indentation(int level)
+  {
+    if (level > 40)
+      return new String();
+    return SPACING.substring(0, level);
+  }
+
+  static void formatWidth(StringBuilder sb, String s, int width, int indent, boolean flag)
+  {
+    String sindent = indentation(indent);
+    s = s.replaceAll("\\s+", " ").trim();
+    if (s.length() < width)
+    {
+      if (flag)
+        sb.append(sindent);
+      sb.append(s);
+      return;
+    }
+    byte[] b = s.getBytes(StandardCharsets.UTF_8);
+    int start = 0;
+    int prev = 0;
+    int l = b.length;
+    int next = 0;
+    while (next < l)
+    {
+      for (next = prev + 1; next < l; ++next)
+        if (b[next] == ' ')
+          break;
+      if (next - start > width)
+      {
+        b[prev] = '\n';
+        if (flag)
+          sb.append(sindent);
+        flag = true;
+        sb.append(new String(b, start, prev - start + 1));
+        start = prev + 1;
+      }
+      prev = next;
+    }
+    sb.append(sindent);
+    sb.append(new String(b, start, l - start));
+  }
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocTransformer.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocTransformer.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocTransformer.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/javadoc/JavadocTransformer.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,471 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.javadoc;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.jpype.html.Html;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+/**
+ * Transform the document into a form suitable for ReStructured Text.
+ *
+ * The goal of this is to convert all inline markup into rst and leave markup by
+ * section, paragraph to be used by the renderer.
+ *
+ * @author nelson85
+ */
+public class JavadocTransformer
+{
+
+  final static Pattern ARGS_PATTERN = Pattern.compile(".*\\((.*)\\).*");
+
+  public Node transformDescription(Class cls, Node node)
+  {
+    try
+    {
+      Workspace ws = new Workspace(cls);
+      DomUtilities.traverseDFS(node, this::fixEntities, Node.TEXT_NODE);
+      DomUtilities.traverseChildren(node, this::handleDescription, Node.ELEMENT_NODE, ws);
+      DomUtilities.traverseDFS(node, this::pass1, Node.ELEMENT_NODE, ws);
+      return node;
+    } catch (Exception ex)
+    {
+      throw new JavadocException(node, ex);
+    }
+  }
+
+  /**
+   * Convert a Javadoc member description into markup for ReStructure Text
+   * rendering.
+   *
+   * This will mutilate the node.
+   *
+   * @param node
+   */
+  public Node transformMember(Class cls, Node node)
+  {
+    try
+    {
+      Workspace ws = new Workspace(cls);
+      DomUtilities.traverseDFS(node, this::fixEntities, Node.TEXT_NODE);
+      DomUtilities.traverseChildren(node, this::handleMembers, Node.ELEMENT_NODE, ws);
+      DomUtilities.traverseDFS(node, this::pass1, Node.ELEMENT_NODE, ws);
+      return node;
+    } catch (Exception ex)
+    {
+      throw new JavadocException(node, ex);
+    }
+
+  }
+
+//<editor-fold desc="members" defaultstate="description">
+  void handleDescription(Node node, Workspace data)
+  {
+    Element e = (Element) node;
+    String name = e.getTagName();
+    Document doc = e.getOwnerDocument();
+    Node parent = node.getParentNode();
+    if (name.equals("dl") && !data.hr)
+    {
+      parent.removeChild(node);
+    } else if (name.equals("br"))
+    {
+      parent.removeChild(node);
+    } else if (name.equals("hr"))
+    {
+      data.hr = true;
+      parent.removeChild(node);
+    } else if (name.equals("pre")
+            || // Javadoc pre-17
+            (name.equals("div") && e.getAttribute("class").equals("type-signature"))) // Javadoc 17+
+    {
+      DomUtilities.removeWhitespace(node);
+      doc.renameNode(node, null, "signature");
+    } else if (name.equals("div"))
+    {
+      doc.renameNode(node, null, "description");
+      DomUtilities.clearAttributes(node);
+    } else if (name.equals("dl"))
+    {
+      doc.renameNode(node, null, "details");
+      DomUtilities.traverseChildren(node, this::handleDetails,
+              Node.ELEMENT_NODE, data);
+    } else
+    {
+      throw new RuntimeException("Unknown item at top level " + name);
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="members" defaultstate="collapsed">
+  void handleMembers(Node node, Workspace ws)
+  {
+    Element e = (Element) node;
+    String name = e.getTagName();
+    Document doc = e.getOwnerDocument();
+
+    if (name.equals("h4") || name.equals("h3")) // h4 for Javadoc pre-17, h3 for Javadoc 17+
+    {
+      doc.renameNode(node, null, "title");
+    } else if (name.equals("pre")
+            || // Javadoc pre-17
+            (name.equals("div") && (e.getAttribute("class").equals("member-signature")))) // Javadoc 17+
+    {
+      doc.renameNode(node, null, "signature");
+      DomUtilities.traverseDFS(node, this::pass1, Node.ELEMENT_NODE, ws);
+      // We need to get the types from here for the parameters
+      DomUtilities.removeWhitespace(node);
+      String content = node.getTextContent();
+      Matcher m = ARGS_PATTERN.matcher(content);
+      if (m.matches())
+      {
+        LinkedList<String> types = new LinkedList<>();
+        for (String s : m.group(1).split(", "))
+        {
+          String[] parts = s.split("\u00a0", 2);
+          types.add(parts[0]);
+        }
+        ws.types = types;
+      }
+    } else if (name.equals("div"))
+    {
+      doc.renameNode(node, null, "description");
+      DomUtilities.clearAttributes(node);
+    } else if (name.equals("dl"))
+    {
+      doc.renameNode(node, null, "details");
+      DomUtilities.traverseChildren(node, this::handleDetails,
+              Node.ELEMENT_NODE, ws);
+    } else
+    {
+      throw new RuntimeException("Unknown item at top level " + name);
+    }
+  }
+
+  public final static Map<String, String> DETAIL_SECTIONS;
+
+  static
+  {
+    DETAIL_SECTIONS = new HashMap<>();
+    Map<String, String> ds = DETAIL_SECTIONS;
+    ds.put("Author:", "author");
+    ds.put("Since:", "since");
+    ds.put("Parameters:", "parameters");
+    ds.put("Returns:", "returns");
+    ds.put("Overrides:", "overrides");
+    ds.put("See Also:", "see");
+    ds.put("API Note:", "api_note");
+    ds.put("Version:", "version");
+    ds.put("Type Parameters:", "typeparams");
+    ds.put("Specified by:", "specified");
+    ds.put("Throws:", "throws");
+    ds.put("Implementation Requirements:", "requirements");
+    ds.put("Implementation Note:", "impl_note");
+  }
+
+  void handleDetails(Node node, Workspace ws)
+  {
+    Element e = (Element) node;
+    String name = e.getTagName();
+    Document doc = e.getOwnerDocument();
+    Node parent = e.getParentNode();
+    if (name.equals("dt"))
+    {
+      String key = node.getTextContent().trim();
+      if (DETAIL_SECTIONS.containsKey(key))
+      {
+        doc.renameNode(node, null, DETAIL_SECTIONS.get(key));
+      } else if (key.startsWith("See "))
+      {
+        doc.renameNode(node, null, "jls");
+      } else
+      {
+        System.err.println("Bad detail key '" + key + "'");
+      }
+      ws.key = node.getNodeName();
+      ws.section = node;
+      DomUtilities.clearChildren(ws.section);
+    }
+    if (name.equals("dd"))
+    {
+      if (ws.key.equals("parameters"))
+      {
+        Node first = node.getFirstChild(); // First is <code>varname</code>
+        Node second = first.getNextSibling(); // Second is " - desc"
+        Element elem = doc.createElement("parameter");
+        elem.setAttribute("name", first.getTextContent());
+        elem.setAttribute("type", ws.types.removeFirst());
+        String value = second.getNodeValue();
+        second.setNodeValue(value.substring(3)); // Remove " - "
+        node.removeChild(first);
+        DomUtilities.transferContents(elem, node);
+        ws.section.appendChild(elem);
+        parent.removeChild(node);
+      } else if (ws.key.equals("throws"))
+      {
+        Node first = node.getFirstChild(); // First is <code><a>exc</a></code>
+        Node second = first.getNextSibling(); // Second is " - desc"
+        Element elem = doc.createElement("exception");
+        DomUtilities.traverseDFS(first, this::pass1, Node.ELEMENT_NODE, ws);
+        elem.setAttribute("name", first.getTextContent());
+        if (second != null)
+        {
+          String value = second.getNodeValue();
+          second.setNodeValue(value.substring(3)); // Remove " - "
+        }
+        node.removeChild(first);
+        DomUtilities.transferContents(elem, node);
+        ws.section.appendChild(elem);
+        parent.removeChild(node);
+      } else
+      {
+        // Normalize the node and transfer it to the section
+        DomUtilities.transferContents(ws.section, node);
+        DomUtilities.traverseDFS(ws.section, this::pass1, Node.ELEMENT_NODE, ws);
+        DomUtilities.removeWhitespace(ws.section);
+        parent.removeChild(node);
+        return;
+      }
+    }
+  }
+
+  static class Workspace
+  {
+
+    private final Class cls;
+    boolean hr = false;
+    String key;
+    Node section;
+    private LinkedList<String> types;
+
+    Workspace(Class cls)
+    {
+      this.cls = cls;
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="contents" defaultstate="collapsed">
+  /**
+   * Convert any html entities found the text.
+   *
+   * @param node
+   */
+  void fixEntities(Node node)
+  {
+    Text n = (Text) node;
+    String s = Html.decode(n.getTextContent());
+    n.setTextContent(s);
+  }
+
+  // This corresponds to any simple inline markup transformation.
+  final static String[] PASS1 = new String[]
+  {
+    "cite", "\"%s\"",
+    "code", ":code:`%s`",
+    "pre", ":code:`%s`",
+    "tt", "``%s``",
+    "i", "*%s*",
+    "em", "*%s*",
+    "strong", "**%s**",
+    "b", "**%s**",
+    "sup", " :sup:`%s` ",
+    "sub", " :sub:`%s` ",
+    "small", ":sub:`%s`",
+    "span", "%s",
+    "nop", "%s",
+    "font", "%s",
+    "var", "*%s*",
+  };
+
+  /**
+   * Get a bunch of simple substitutions.
+   *
+   * @param node
+   */
+  void pass1(Node node, Workspace ws)
+  {
+    Element e = (Element) node;
+    String name = e.getTagName();
+    Document doc = e.getOwnerDocument();
+    Node parent = e.getParentNode();
+    if (parent == null)
+      return;
+
+    // Pre is something used to mark code.
+    if (name.equals("pre"))
+    {
+      if (DomUtilities.containsNL(e))
+      {
+        doc.renameNode(node, null, "codeblock");
+        name = "codeblock";
+      } else
+      {
+        doc.renameNode(node, null, "code");
+        name = "code";
+      }
+    }
+
+    //  <pre><code> is a common javadoc idiom.
+    if (name.equals("code") && (parent.getNodeName().equals("pre")
+            || parent.getNodeName().equals("blockquote")))
+    {
+      doc.renameNode(parent, null, "codeblock");
+      DomUtilities.mergeNode(parent, node);
+      return;
+    }
+
+    if (name.equals("codeblock") && parent.getNodeName().equals("pre"))
+    {
+      if (DomUtilities.containsNL(node))
+      {
+        doc.renameNode(parent, null, "codeblock");
+      } else
+        doc.renameNode(parent, null, "code");
+      DomUtilities.mergeNode(parent, node);
+      return;
+    }
+
+    // <a ...><code> is used to reference members and classes.
+    if (name.equals("code") && parent.getNodeName().equals("a"))
+    {
+      Element eparent = (Element) parent;
+      String href = this.toReference(ws, eparent.getAttribute("href"));
+      DomUtilities.clearChildren(parent);
+      parent.appendChild(doc.createTextNode(href));
+      return;
+    }
+
+    // <code><a> is also used.
+    if (name.equals("a") && parent.getNodeName().equals("code"))
+    {
+      String href = this.toReference(ws, e.getAttribute("href"));
+      DomUtilities.clearChildren(parent);
+      doc.renameNode(parent, null, "nop");
+      parent.appendChild(doc.createTextNode(href));
+      return;
+    }
+
+    // <a> by itself is usually external references.
+    if (name.equals("a"))
+    {
+      String href = e.getAttribute("href");
+      if (href.startsWith("http:") || href.startsWith("shttp:"))
+      {
+        String content = node.getTextContent();
+        content = String.format("`%s <%s>`", content, href);
+        parent.replaceChild(doc.createTextNode(content), node);
+        return;
+      }
+      href = this.toReference(ws, href);
+      if (href == null)
+        parent.replaceChild(doc.createTextNode(node.getTextContent()), node);
+      else
+        parent.replaceChild(doc.createTextNode(href), node);
+      return;
+    }
+
+    // Apply inline transformations.
+    for (int i = 0; i < PASS1.length; i += 2)
+    {
+      if (name.equals(PASS1[i]))
+      {
+        String s2 = e.getTextContent();
+        if (s2 == null)
+          e.getParentNode().removeChild(e);
+        else
+          e.getParentNode().replaceChild(doc.createTextNode(String.format(PASS1[i + 1], s2.trim())), e);
+        return;
+      }
+    }
+
+  }
+
+  /**
+   * Convert a reference into method or class.
+   *
+   * This currently only deals with local links. External links are elsewhere.
+   *
+   * @param ws
+   * @param href
+   * @return
+   */
+  public String toReference(Workspace ws, String href)
+  {
+    try
+    {
+      Path p = Paths.get(ws.cls.getName().replace('.', '/'));
+      if (href.startsWith("#"))
+      {
+        // technically it may be a field, but we can't tell currently.
+        Path q = p.resolve(href.substring(1).trim())
+                .normalize();
+        if (q.startsWith(".."))
+          q = q.subpath(2, q.getNameCount());
+        String r = q.toString()
+                .replace('/', '.')
+                .replace('\\', '.')
+                .replaceAll("\\(.*\\)", "")
+                .replaceAll("-.*", "");
+        return String.format(":meth:`~%s`", r);
+      } else if (href.contains("#"))
+      {
+        // technically it may be a field, but we can't tell currently.
+        Path q = p.getParent()
+                .resolve(href.trim())
+                .normalize();
+        if (q.startsWith(".."))
+          q = q.subpath(2, q.getNameCount());
+        String r = q.toString()
+                .replace('/', '.')
+                .replace('\\', '.')
+                .replaceAll("\\(.*\\)", "")
+                .replaceAll("-.*", "")
+                .replaceAll(".html#", ".");
+        return String.format(":meth:`~%s`", r);
+      } else
+      {
+        Path q = p.getParent()
+                .resolve(href.trim())
+                .normalize();
+        if (q.startsWith(".."))
+          q = q.subpath(2, q.getNameCount());
+        String r = q.toString()
+                .replace('/', '.')
+                .replace('\\', '.')
+                .replaceAll("-.*", "")
+                .replaceAll(".html", "");
+        return String.format(":class:`~%s`", r);
+      }
+    } catch (Exception ex)
+    {
+      // There is a lot of ways this can go wrong.  If all else fails
+      // return null so that we can just remove the hyperlink.
+      return null;
+    }
+  }
+
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/ClassDescriptor.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/ClassDescriptor.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/ClassDescriptor.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/ClassDescriptor.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,71 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.manager;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+
+/**
+ * A list of resources associated with this class.
+ * <p>
+ * These can be accessed within JPype using the org.jpype.manager.TypeManager.
+ * <p>
+ */
+public class ClassDescriptor
+{
+
+  public Class<?> cls;
+  /**
+   * JPClass pointer for this class.
+   */
+  public long classPtr;
+  /**
+   * JPMethodDispatch for the constructor.
+   */
+  public long constructorDispatch;
+  public long[] constructors;
+  /**
+   * Resources needed by the class
+   */
+  public long[] methodDispatch;
+  public Executable[] methodIndex;
+  public long[] methods;
+  public int methodCounter = 0;
+  public long[] fields;
+  public long anonymous;
+  public int functional_interface_parameter_count;
+
+  ClassDescriptor(Class cls, long classPtr, Method method)
+  {
+    this.cls = cls;
+    this.classPtr = classPtr;
+    if (this.classPtr == 0)
+      throw new NullPointerException("Class pointer is null for " + cls);
+    if (method != null)
+      functional_interface_parameter_count = method.getParameterCount();
+    else
+      functional_interface_parameter_count = -1;
+  }
+
+  long getMethod(Method requestedMethod)
+  {
+    for (int i = 0; i < methods.length; ++i)
+      if (this.methodIndex[i].equals(requestedMethod))
+        return this.methods[i];
+    return 0;
+
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/MethodResolution.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/MethodResolution.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/MethodResolution.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/MethodResolution.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,287 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.manager;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Sort out which methods hide other methods.
+ * <p>
+ * When resolving method overloads there may be times in which more than one
+ * overload applies. JPype requires that the methods appear in order from most
+ * to least specific. And each method overload requires a list of methods that
+ * are more general. If two or more methods match and one is not more specific
+ * than the other.
+ *
+ * @author nelson85
+ */
+public class MethodResolution
+{
+
+  long ptr = 0;
+  boolean covered = false;
+  Executable executable;
+  List<MethodResolution> children = new ArrayList<>();
+
+  MethodResolution(Executable method)
+  {
+    this.executable = method;
+  }
+
+  private boolean isCovered()
+  {
+    for (MethodResolution ov : this.children)
+    {
+      if (!ov.covered)
+        return false;
+    }
+    covered = true;
+    return true;
+  }
+
+  /**
+   * Order methods from least to most specific.
+   *
+   * @param <T>
+   * @param methods
+   * @return
+   */
+  public static <T extends Executable>
+          List<MethodResolution> sortMethods(List<T> methods)
+  {
+    // Create a method resolution for each method
+    LinkedList<MethodResolution> unsorted = new LinkedList<>();
+    for (T m1 : methods)
+    {
+      unsorted.add(new MethodResolution(m1));
+    }
+
+    for (MethodResolution m1 : unsorted)
+    {
+      for (MethodResolution m2 : unsorted)
+      {
+        if (m1 == m2)
+          continue;
+
+        if (isMoreSpecificThan(m1.executable, m2.executable)
+                && !isMoreSpecificThan(m2.executable, m1.executable))
+        {
+          m1.children.add(m2);
+        }
+      }
+    }
+
+    // Execute a graph sort problem so that the most specific are always on the front
+    LinkedList<MethodResolution> out = new LinkedList<>();
+    while (!unsorted.isEmpty())
+    {
+      // Remove the first unsorted element
+      MethodResolution front = unsorted.pop();
+      // Check to see if all dependencies are already ordered
+      boolean good = front.isCovered();
+
+      // If all dependencies are included
+      if (good)
+      {
+        front.covered = true;
+        out.add(front);
+      } else
+      {
+        unsorted.add(front);
+      }
+    }
+    return out;
+  }
+
+  // Table for primitive rules
+  static Class[] of(Class... l)
+  {
+    return l;
+  }
+  static HashMap<Class, Class[]> CONVERSION = new HashMap<>();
+
+  
+  {
+    CONVERSION.put(Byte.TYPE,
+            of(Byte.TYPE, Byte.class, Short.TYPE, Short.class,
+                    Integer.TYPE, Integer.class, Long.TYPE, Long.class,
+                    Float.TYPE, Float.class, Double.TYPE, Double.class));
+    CONVERSION.put(Character.TYPE,
+            of(Character.TYPE, Character.class, Short.TYPE, Short.class,
+                    Integer.TYPE, Integer.class, Long.TYPE, Long.class,
+                    Float.TYPE, Float.class, Double.TYPE, Double.class));
+    CONVERSION.put(Short.TYPE,
+            of(Short.TYPE, Short.class,
+                    Integer.TYPE, Integer.class, Long.TYPE, Long.class,
+                    Float.TYPE, Float.class, Double.TYPE, Double.class));
+    CONVERSION.put(Integer.TYPE,
+            of(Integer.TYPE, Integer.class, Long.TYPE, Long.class,
+                    Float.TYPE, Float.class, Double.TYPE, Double.class));
+    CONVERSION.put(Long.TYPE,
+            of(Long.TYPE, Long.class,
+                    Float.TYPE, Float.class, Double.TYPE, Double.class));
+    CONVERSION.put(Float.TYPE,
+            of(Float.TYPE, Float.class, Double.TYPE, Double.class));
+    CONVERSION.put(Double.TYPE,
+            of(Double.TYPE, Double.class));
+    CONVERSION.put(Boolean.TYPE,
+            of(Boolean.TYPE, Boolean.class));
+  }
+
+  static boolean isAssignableTo(Class c1, Class c2)
+  {
+    if (!c1.isPrimitive())
+      return c2.isAssignableFrom(c1);
+    Class[] cl = CONVERSION.get(c1);
+    if (cl == null)
+      return false;
+    for (Class c3 : cl)
+      if (c2.equals(c3))
+        return true;
+    return false;
+  }
+
+  /**
+   * Determine is a executable is more specific than another.
+   * <p>
+   * This is public so that we can debug from within jpype.
+   *
+   * @param method1
+   * @param method2
+   * @return
+   */
+  public static boolean isMoreSpecificThan(Executable method1, Executable method2)
+  {
+    List<Class<?>> param1 = new ArrayList<>(Arrays.asList(method1.getParameterTypes()));
+    List<Class<?>> param2 = new ArrayList<>(Arrays.asList(method2.getParameterTypes()));
+
+    if (!Modifier.isStatic(method1.getModifiers()))
+      param1.add(0, method1.getDeclaringClass());
+    if (!Modifier.isStatic(method2.getModifiers()))
+      param2.add(0, method2.getDeclaringClass());
+
+    // Special handling is needed for varargs as it may chop or expand.
+    // we have 4 cases for a varargs methods
+    //    foo(Arg0, Arg1...) as
+    //       foo(Arg0)
+    //       foo(Arg0, Arg1)
+    //       foo(Arg0, Arg1[])
+    //       foo(Arg0, Arg1, Arg1+)
+    if (method1.isVarArgs() && method2.isVarArgs())
+    {
+      // Punt on this as there are too many different cases
+      return isMoreSpecificThan(param1, param2);
+    }
+
+    if (method1.isVarArgs())
+    {
+      int n1 = param1.size();
+      int n2 = param2.size();
+
+      // Last element is an array
+      Class<?> cls = param1.get(n1 - 1);
+      Class<?> cls2 = cls.getComponentType();
+
+      // Less arguments, chop the list 
+      if (n1 - 1 == n2)
+        return isMoreSpecificThan(param1.subList(0, n2), param2);
+
+      // Same arguments
+      if (n1 == n2)
+      {
+        List<Class<?>> q = new ArrayList<>(param1);
+        q.set(n1 - 1, cls2);
+
+        // Check both ways
+        boolean isMoreSpecific = isMoreSpecificThan(param1, param2) || isMoreSpecificThan(q, param2);
+
+        // If the varargs array is of the single-variable's type (or they are primitive-equivalent),
+        // the single-variable signature should win specificity
+        Class<?> svCls = param2.get(n2 - 1);
+        return isMoreSpecific && !(isAssignableTo(cls2, svCls) && isAssignableTo(svCls, cls2));
+      }
+
+      // More arguments
+      if (n1 < n2)
+      {
+        // Grow the list
+        List<Class<?>> q = new ArrayList<>(param1);
+        q.set(n1 - 1, cls2);
+        for (int i = n1; i < n2; ++i)
+          q.add(cls2);
+        return isMoreSpecificThan(q, param2);
+      }
+    }
+
+    if (method2.isVarArgs())
+    {
+      int n1 = param1.size();
+      int n2 = param2.size();
+
+      // Last element is an array
+      Class<?> cls = param2.get(n2 - 1);
+      Class<?> cls2 = cls.getComponentType();
+
+      // Less arguments, chop the list
+      if (n2 - 1 == n1)
+        return isMoreSpecificThan(param1, param2.subList(0, n2));
+
+      // Same arguments
+      if (n1 == n2)
+      {
+        List<Class<?>> q = new ArrayList<>(param2);
+        q.set(n2 - 1, cls2);
+
+        // Compare both ways
+        return isMoreSpecificThan(param1, param2) || isMoreSpecificThan(param1, q);
+      }
+
+      // More arguments
+      if (n2 < n1)
+      {
+        // Grow the list
+        List<Class<?>> q = new ArrayList<>(param2);
+        q.set(n2 - 1, cls2);
+        for (int i = n2; i < n1; ++i)
+          q.add(cls2);
+        return isMoreSpecificThan(param1, q);
+      }
+    }
+
+    return isMoreSpecificThan(param1, param2);
+  }
+
+  public static boolean isMoreSpecificThan(List<Class<?>> param1, List<Class<?>> param2)
+  {
+    // FIXME need to consider resolving mixing of static and non-static
+    // Methods here.
+    if (param1.size() != param2.size())
+      return false;
+
+    for (int i = 0; i < param1.size(); ++i)
+    {
+      if (!isAssignableTo(param1.get(i), param2.get(i)))
+        return false;
+    }
+    return true;
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/ModifierCode.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/ModifierCode.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/ModifierCode.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/ModifierCode.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,82 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.manager;
+
+import java.lang.reflect.Modifier;
+import java.util.EnumSet;
+
+/**
+ * Definitions for JPype modifiers.
+ * <p>
+ * These pretty much match Java plus a few codes we need.
+ *
+ * @author nelson85
+ */
+public enum ModifierCode
+{
+  // we need
+  //   fields: static, final
+  //   methods: static, final, varargs, constructor
+  //   class: interface, throwable, abstract, final
+  PUBLIC(Modifier.PUBLIC),
+  PRIVATE(Modifier.PRIVATE),
+  PROTECTED(Modifier.PROTECTED),
+  STATIC(Modifier.STATIC),
+  FINAL(Modifier.FINAL),
+  VARARGS(0x0080),
+  ENUM(0x4000),
+  ABSTRACT(0x0400),
+  // Special flags for classes required for JPype
+  SPECIAL(0x00010000),
+  THROWABLE(0x00020000),
+  SERIALIZABLE(0x00040000),
+  ANONYMOUS(0x00080000),
+  FUNCTIONAL(0x00100000),
+  CALLER_SENSITIVE(0x00200000),
+  PRIMITIVE_ARRAY(0x00400000),
+  COMPARABLE(0x00800000),
+  BUFFER(0x01000000),
+  CTOR(0x10000000),
+  BEAN_ACCESSOR(0x20000000),
+  BEAN_MUTATOR(0x40000000);
+  final public int value;
+
+  ModifierCode(int value)
+  {
+    this.value = value;
+  }
+
+  public static int get(EnumSet<ModifierCode> set)
+  {
+    int out = 0;
+    for (ModifierCode m : set)
+    {
+      out |= m.value;
+    }
+    return out;
+  }
+
+  public static EnumSet<ModifierCode> decode(long modifiers)
+  {
+    EnumSet<ModifierCode> out = EnumSet.noneOf(ModifierCode.class);
+    for (ModifierCode code : ModifierCode.values())
+    {
+      if ((modifiers & code.value) == code.value)
+        out.add(code);
+    }
+    return out;
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeAudit.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeAudit.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeAudit.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeAudit.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,35 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.manager;
+
+import java.lang.reflect.Method;
+
+/**
+ * Auditing class for TypeManager used during testing.
+ * <p>
+ * This is not used during operation.
+ *
+ * @author nelson85
+ */
+public interface TypeAudit
+{
+
+  void dump(ClassDescriptor desc);
+
+  void verifyMembers(ClassDescriptor desc);
+
+  public void failFindMethod(ClassDescriptor desc, Method method);
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactory.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactory.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactory.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactory.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,192 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.manager;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+
+/**
+ * Interface for creating new resources used by JPype.
+ * <p>
+ * This calls the C++ constructors with all of the required fields for each
+ * class. This pattern eliminates the need for C++ layer probing Java for
+ * resources.
+ * <p>
+ * This is an interface for testing.
+ *
+ * @author nelson85
+ */
+public interface TypeFactory
+{
+//<editor-fold desc="class" defaultstate="collapsed">
+
+  /**
+   * Create a new wrapper type for Python.
+   *
+   * @param context
+   * @param cls is the pointer to the JClass.
+   */
+  void newWrapper(long context, long cls);
+
+  /**
+   * Create a JPArray class.
+   *
+   * @param context JPContext object
+   * @param cls is the class type.
+   * @param name
+   * @param superClass
+   * @param componentPtr
+   * @param modifiers
+   * @return the pointer to the JPArrayClass.
+   */
+  long defineArrayClass(
+          long context,
+          Class cls,
+          String name,
+          long superClass,
+          long componentPtr,
+          int modifiers);
+
+  /**
+   * Create a class type.
+   *
+   * @param context JPContext object
+   * @param cls
+   * @param superClass
+   * @param interfaces
+   * @param modifiers
+   * @param name
+   * @return the pointer to the JPClass.
+   */
+  long defineObjectClass(
+          long context,
+          Class cls,
+          String name,
+          long superClass,
+          long[] interfaces,
+          int modifiers);
+
+  /**
+   * Define a primitive types.
+   *
+   * @param context JPContext object
+   * @param cls is the Java class for this primitive.
+   * @param boxedPtr is the JPClass for the boxed class.
+   * @param modifiers
+   * @return
+   */
+  long definePrimitive(
+          long context,
+          String name,
+          Class cls,
+          long boxedPtr,
+          int modifiers);
+
+//</editor-fold>
+//<editor-fold desc="members" defaultstate="collapsed">
+  /**
+   * Called after a class is constructed to populate the required fields and
+   * methods.
+   *
+   * @param context JPContext object
+   * @param cls is the JPClass to populate
+   * @param ctorMethod is the JPMethod for the constructor.
+   * @param methodList is a list of JPMethod for the method list.
+   * @param fieldList is a list of JPField for the field list.
+   */
+  void assignMembers(
+          long context,
+          long cls,
+          long ctorMethod,
+          long[] methodList,
+          long[] fieldList);
+
+  /**
+   * Create a Method.
+   *
+   * @param context JPContext object
+   * @param cls is the class holding this.
+   * @param name
+   * @param field
+   * @param fieldType
+   * @param modifiers
+   * @return the pointer to the JPMethod.
+   */
+  long defineField(
+          long context,
+          long cls,
+          String name,
+          Field field, // This will convert to a field id
+          long fieldType,
+          int modifiers);
+
+  /**
+   * Create a Method.
+   *
+   * @param context JPContext object
+   * @param cls is the class holding this.
+   * @param name
+   * @param method is the Java method that will be called, converts to a method
+   * id.
+   * @param overloadList
+   * @param modifiers
+   * @return the pointer to the JPMethod.
+   */
+  long defineMethod(
+          long context,
+          long cls,
+          String name,
+          Executable method,
+          long[] overloadList,
+          int modifiers);
+
+  void populateMethod(
+          long context,
+          long method,
+          long returnType,
+          long[] argumentTypes);
+
+  /**
+   * Create a Method dispatch for Python by name.
+   *
+   * @param context JPContext object
+   * @param cls is the class that owns this dispatch.
+   * @param name is the name of the dispatch.
+   * @param overloadList is the list of all methods constructed for this class.
+   * @param modifiers contains if the method is (CTOR, STATIC),
+   * @return the pointer to the JPMethodDispatch.
+   */
+  long defineMethodDispatch(
+          long context,
+          long cls,
+          String name,
+          long[] overloadList,
+          int modifiers);
+
+//</editor-fold>
+//<editor-fold desc="destroy" defaultstate="collapsed">
+  /**
+   * Destroy the resources.
+   *
+   * @param context JPContext object
+   * @param resources
+   * @param sz
+   */
+  void destroy(
+          long context,
+          long[] resources, int sz);
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactoryNative.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactoryNative.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactoryNative.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeFactoryNative.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,105 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.manager;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+
+/**
+ * This is the interface for creating C++ object in JPype.
+ * <p>
+ * These methods are all native.
+ * <p>
+ */
+public class TypeFactoryNative implements TypeFactory
+{
+
+  public long context;
+
+  public native void newWrapper(long context, long cls);
+
+  @Override
+  public native long defineArrayClass(
+          long context,
+          Class cls,
+          String name,
+          long superClass,
+          long componentPtr,
+          int modifiers);
+
+  @Override
+  public native long defineObjectClass(
+          long context,
+          Class cls,
+          String name,
+          long superClass,
+          long[] interfaces,
+          int modifiers);
+
+  @Override
+  public native long definePrimitive(
+          long context,
+          String name,
+          Class cls,
+          long boxedPtr,
+          int modifiers);
+
+  @Override
+  public native void assignMembers(
+          long context,
+          long cls,
+          long ctorMethod,
+          long[] methodList,
+          long[] fieldList);
+
+  @Override
+  public native long defineField(
+          long context,
+          long cls,
+          String name,
+          Field field,
+          long fieldType,
+          int modifiers);
+
+  @Override
+  public native long defineMethod(
+          long context,
+          long cls,
+          String name,
+          Executable method,
+          long[] overloadList,
+          int modifiers);
+
+  @Override
+  public native void populateMethod(
+          long context,
+          long method,
+          long returnType,
+          long[] argumentTypes);
+
+  @Override
+  public native long defineMethodDispatch(
+          long context,
+          long cls,
+          String name,
+          long[] overloadList,
+          int modifiers);
+
+  @Override
+  public native void destroy(
+          long context,
+          long[] resources, int sz);
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeManager.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeManager.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeManager.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/manager/TypeManager.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,1001 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.manager;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.nio.Buffer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+import org.jpype.JPypeContext;
+import org.jpype.JPypeUtilities;
+import org.jpype.proxy.JPypeProxy;
+
+/**
+ *
+ */
+public class TypeManager
+{
+
+  public long context = 0;
+  public boolean isStarted = false;
+  public boolean isShutdown = false;
+  public HashMap<Class, ClassDescriptor> classMap = new HashMap<>();
+  public TypeFactory typeFactory = null;
+  public TypeAudit audit = null;
+  private ClassDescriptor java_lang_Object;
+  // For reasons that are less than clear, this object cannot be created
+  // during shutdown
+  private Destroyer destroyer = new Destroyer();
+
+  public TypeManager()
+  {
+  }
+
+  public TypeManager(long context, TypeFactory typeFactory)
+  {
+    this.context = context;
+    this.typeFactory = typeFactory;
+  }
+
+//<editor-fold desc="interface">
+  public synchronized void init()
+  {
+    try
+    {
+      if (isStarted)
+        throw new RuntimeException("Cannot be restarted");
+      isStarted = true;
+      isShutdown = false;
+
+      // Create the required minimum classes
+      this.java_lang_Object = createClass(Object.class, true);
+
+      // Note that order is very important when creating these initial wrapper
+      // types. If something inherits from another type then the super class
+      // will be created without the special flag and the type system won't
+      // be able to handle the duplicate type properly.
+      Class[] cls =
+      {
+        Class.class, Number.class, CharSequence.class, Throwable.class,
+        Void.class, Boolean.class, Byte.class, Character.class,
+        Short.class, Integer.class, Long.class, Float.class, Double.class,
+        String.class, JPypeProxy.class,
+        Method.class, Field.class
+      };
+      for (Class c : cls)
+      {
+        createClass(c, true);
+      }
+
+      // Create the primitive types
+      // Link boxed and primitive types so that the wrappers can find them.
+      createPrimitive("void", Void.TYPE, Void.class);
+      createPrimitive("boolean", Boolean.TYPE, Boolean.class);
+      createPrimitive("byte", Byte.TYPE, Byte.class);
+      createPrimitive("char", Character.TYPE, Character.class);
+      createPrimitive("short", Short.TYPE, Short.class);
+      createPrimitive("int", Integer.TYPE, Integer.class);
+      createPrimitive("long", Long.TYPE, Long.class);
+      createPrimitive("float", Float.TYPE, Float.class);
+      createPrimitive("double", Double.TYPE, Double.class);
+    } catch (Throwable ex)
+    {
+      // We can't get debugging information at this point in the process.
+      ex.printStackTrace();
+      throw ex;
+    }
+  }
+
+  /**
+   * Find a wrapper for a class.
+   * <p>
+   * Creates one if needed. This a front end used by JPype.
+   *
+   * @param cls
+   * @return the JPClass, or 0 it one cannot be created.
+   */
+  public synchronized long findClass(Class<?> cls)
+  {
+    if (cls == null)
+      return 0;
+    if (this.isShutdown)
+      return 0;
+
+    long out;
+    if (cls.isSynthetic() && cls.getSimpleName().contains("$Lambda$"))
+    {
+      // If is it lambda, we need a special wrapper
+      // we don't want to create a class each time in that case.
+      // Thus use the parent interface for this class
+      out = getClass(cls.getInterfaces()[0]).classPtr;
+    } else if (cls.isAnonymousClass())
+    {
+      // This one is more of a burden.  It depends what whether is was
+      // anonymous extends or implements.
+      if (cls.getInterfaces().length == 1)
+        out = getClass(cls.getInterfaces()[0]).classPtr;
+      else
+      {
+        ClassDescriptor parent = getClass(cls.getSuperclass());
+        out = createAnonymous(parent);
+      }
+    } else
+    {
+      // Just a regular class
+      out = getClass(cls).classPtr;
+    }
+
+    return out;
+  }
+
+  /**
+   * Get a class by name.
+   *
+   * @param name is the class name.
+   * @return the C++ portion.
+   */
+  public long findClassByName(String name)
+  {
+    Class<?> cls = lookupByName(name);
+    if (cls == null)
+      return 0;
+    return this.findClass(cls);
+  }
+
+  public Class<?> lookupByName(String name)
+  {
+    ClassLoader classLoader = JPypeContext.getInstance().getClassLoader();
+
+    // Handle arrays
+    if (name.endsWith("[]"))
+    {
+      int dims = 0;
+      while (name.endsWith("[]"))
+      {
+        dims++;
+        name = name.substring(0, name.length() - 2);
+      }
+      Class<?> cls = lookupByName(name);
+      if (cls == null)
+        return null;
+      return Array.newInstance(cls, new int[dims]).getClass();
+    }
+
+    try
+    {
+      // Attempt direct lookup
+      return Class.forName(name, true, classLoader);
+    } catch (ClassNotFoundException ex)
+    {
+    }
+
+    // Deal with JNI style names
+    if (name.contains("/"))
+    {
+      try
+      {
+        return Class.forName(name.replaceAll("/", "."), true, classLoader);
+      } catch (ClassNotFoundException ex)
+      {
+      }
+    }
+
+    // Special case for primitives
+    if (!name.contains("."))
+    {
+      if ("boolean".equals(name))
+        return Boolean.TYPE;
+      if ("byte".equals(name))
+        return Byte.TYPE;
+      if ("char".equals(name))
+        return Character.TYPE;
+      if ("short".equals(name))
+        return Short.TYPE;
+      if ("long".equals(name))
+        return Long.TYPE;
+      if ("int".equals(name))
+        return Integer.TYPE;
+      if ("float".equals(name))
+        return Float.TYPE;
+      if ("double".equals(name))
+        return Double.TYPE;
+    }
+
+    // Attempt to find an inner class
+    String[] parts = name.split("\\.");
+    StringBuilder sb = new StringBuilder();
+    sb.append(parts[0]);
+    for (int i = 1; i < parts.length; ++i)
+    {
+      try
+      {
+        sb.append(".");
+        sb.append(parts[i]);
+        Class<?> cls = Class.forName(sb.toString());
+        for (int j = i + 1; j < parts.length; ++j)
+        {
+          sb.append("$");
+          sb.append(parts[j]);
+        }
+        return Class.forName(sb.toString());
+      } catch (ClassNotFoundException ex)
+      {
+      }
+    }
+    return null;
+  }
+
+  public synchronized void populateMethod(long wrapper, Executable method)
+  {
+    if (method == null)
+      return;
+
+    long returnType = 0;
+    if (method instanceof Method)
+    {
+      returnType = getClass(((Method) method).getReturnType()).classPtr;
+    }
+
+    Class<?>[] params = method.getParameterTypes();
+    int i = 0;
+    long[] paramPtrs;
+    if (!Modifier.isStatic(method.getModifiers()) && !(method instanceof Constructor))
+    {
+      paramPtrs = new long[params.length + 1];
+      paramPtrs[0] = getClass(method.getDeclaringClass()).classPtr;
+      i++;
+    } else
+    {
+      paramPtrs = new long[params.length];
+    }
+
+    // Copy in the parameters
+    for (Class<?> p : params)
+    {
+      paramPtrs[i] = getClass(p).classPtr;
+      i++;
+    }
+
+    try
+    {
+      typeFactory.populateMethod(context, wrapper, returnType, paramPtrs);
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Returns the number of arguments an interface only unimplemented method
+   * accept.
+   *
+   * @param interfaceClass The class of the interface
+   * @return the number of arguments the only unimplemented method of the
+   * interface accept.
+   */
+  public int interfaceParameterCount(Class<?> interfaceClass)
+  {
+    ClassDescriptor classDescriptor = classMap.get(interfaceClass);
+    return classDescriptor.functional_interface_parameter_count;
+  }
+
+  /**
+   * Get a class for an object.
+   *
+   * @param object is the object to interrogate.
+   * @return the C++ portion or null if the object is null.
+   * @throws java.lang.InterruptedException
+   */
+  public long findClassForObject(Object object) throws InterruptedException
+  {
+    JPypeContext.clearInterrupt(true);
+    if (object == null)
+      return 0;
+
+    Class cls = object.getClass();
+    if (Proxy.isProxyClass(cls)
+            && (Proxy.getInvocationHandler(object) instanceof JPypeProxy))
+    {
+      return this.findClass(JPypeProxy.class);
+    }
+
+    return this.findClass(cls);
+  }
+
+  /**
+   * Called to delete all C++ resources
+   */
+  public synchronized void shutdown()
+  {
+    // First and most important, we can't operate from this
+    // point forward.
+    this.isShutdown = true;
+
+    // Destroy all the resources held in C++
+    for (ClassDescriptor entry : this.classMap.values())
+    {
+      destroyer.add(entry.constructorDispatch);
+      destroyer.add(entry.constructors);
+      destroyer.add(entry.methodDispatch);
+      destroyer.add(entry.methods);
+      destroyer.add(entry.fields);
+      destroyer.add(entry.anonymous);
+      destroyer.add(entry.classPtr);
+
+      // The same wrapper can appear more than once so blank as we go.
+      entry.constructorDispatch = 0;
+      entry.constructors = null;
+      entry.methodDispatch = null;
+      entry.methods = null;
+      entry.fields = null;
+      entry.anonymous = 0;
+      entry.classPtr = 0;
+    }
+    destroyer.flush();
+
+    // FIXME. If someone attempts to shutdown the JVM within a Python
+    // proxy, everything will crash here.  We would lose the class
+    // that is calling things and the ability to throw exceptions.
+    // Most likely this will go splat. We need to catch this
+    // from within JPype and hard fault our way to safety.
+    this.classMap.clear();
+  }
+//</editor-fold>
+//<editor-fold desc="classes" defaultstate="defaultstate">
+
+  private ClassDescriptor getClass(Class cls)
+  {
+    if (cls == null)
+      return null;
+
+    // Look up the current description
+    ClassDescriptor ptr = this.classMap.get(cls);
+    if (ptr != null)
+      return ptr;
+
+    // If we can't find it create a new class
+    return createClass(cls, false);
+  }
+
+  /**
+   * Allocate a new wrapper for a java class.
+   * <p>
+   * Boxed types require special handlers, as does java.lang.String
+   *
+   * @param cls is the Java class to wrap.
+   * @param special marks class as requiring a specialized C++ wrapper.
+   * @return a C++ wrapper handle for a jp_classtype
+   */
+  private ClassDescriptor createClass(Class<?> cls, boolean special)
+  {
+    if (cls.isArray())
+      return this.createArrayClass(cls);
+
+    return createOrdinaryClass(cls, special, true);
+  }
+
+  private ClassDescriptor createOrdinaryClass(Class<?> cls, boolean special, boolean bases)
+  {
+    // Verify the class will be loadable prior to creating the class.
+    // If we fail to do this then the class may end up crashing later when the
+    // members get populated which could leave us in a bad state.
+    cls.getMethods();
+    cls.getFields();
+
+    // Object classes are more work as we need the super information as well.
+    // Make sure all base classes are loaded
+    Class<?> superClass = cls.getSuperclass();
+    Class<?>[] interfaces = cls.getInterfaces();
+    ClassDescriptor[] parents = new ClassDescriptor[interfaces.length + 1];
+    long[] interfacesPtr = null;
+    long superClassPtr = 0;
+    superClassPtr = 0;
+    if (superClass != null)
+    {
+      parents[0] = this.getClass(superClass);
+      superClassPtr = parents[0].classPtr;
+    }
+
+    if (bases)
+    {
+      interfacesPtr = new long[interfaces.length];
+
+      // Make sure all interfaces are loaded.
+      for (int i = 0; i < interfaces.length; ++i)
+      {
+        parents[i + 1] = this.getClass(interfaces[i]);
+        interfacesPtr[i] = parents[i + 1].classPtr;
+      }
+    } else
+    {
+      interfacesPtr = new long[0];
+    }
+
+    // Set up the modifiers
+    int modifiers = cls.getModifiers() & 0xffff;
+    if (special)
+      modifiers |= ModifierCode.SPECIAL.value;
+    if (Throwable.class.isAssignableFrom(cls))
+      modifiers |= ModifierCode.THROWABLE.value;
+    if (Serializable.class.isAssignableFrom(cls))
+      modifiers |= ModifierCode.SERIALIZABLE.value;
+    if (Arrays.asList(cls.getInterfaces()).contains(Comparable.class))
+      modifiers |= ModifierCode.COMPARABLE.value;
+    if (Buffer.class.isAssignableFrom(cls))
+      modifiers |= ModifierCode.BUFFER.value | ModifierCode.SPECIAL.value;
+
+    // Check if is Functional class
+    Method method = JPypeUtilities.getFunctionalInterfaceMethod(cls);
+    if (method != null)
+      modifiers |= ModifierCode.FUNCTIONAL.value | ModifierCode.SPECIAL.value;
+
+    // FIXME watch out for anonyous and lambda here.
+    String name = cls.getCanonicalName();
+    if (name == null)
+      name = cls.getName();
+
+    // Create the JPClass
+    long classPtr = typeFactory.defineObjectClass(context, cls, name,
+            superClassPtr,
+            interfacesPtr,
+            modifiers);
+
+    // Cache the wrapper.
+    ClassDescriptor out = new ClassDescriptor(cls, classPtr, method);
+    this.classMap.put(cls, out);
+    return out;
+  }
+
+  private long createAnonymous(ClassDescriptor parent)
+  {
+    if (parent.anonymous != 0)
+      return parent.anonymous;
+
+    parent.anonymous = typeFactory.defineObjectClass(context,
+            parent.cls, parent.cls.getCanonicalName() + "$Anonymous",
+            parent.classPtr,
+            null,
+            ModifierCode.ANONYMOUS.value);
+    return parent.anonymous;
+  }
+
+  ClassDescriptor createArrayClass(Class cls)
+  {
+    // Array classes are simple, we just need the component type
+    Class componentType = cls.getComponentType();
+    long componentTypePtr = this.getClass(componentType).classPtr;
+
+    int modifiers = cls.getModifiers() & 0xffff;
+    String name = cls.getName();
+    if (!name.endsWith(";"))
+      modifiers |= ModifierCode.PRIMITIVE_ARRAY.value;
+
+    long classPtr = typeFactory
+            .defineArrayClass(context, cls,
+                    cls.getCanonicalName(),
+                    this.java_lang_Object.classPtr,
+                    componentTypePtr,
+                    modifiers);
+
+    ClassDescriptor out = new ClassDescriptor(cls, classPtr, null);
+    this.classMap.put(cls, out);
+    return out;
+  }
+
+  /**
+   * Tell JPype to make a primitive Class.
+   *
+   * @param name
+   * @param cls
+   * @param boxed
+   */
+  private void createPrimitive(String name, Class cls, Class boxed)
+  {
+    long classPtr = typeFactory.definePrimitive(context,
+            name,
+            cls,
+            this.getClass(boxed).classPtr,
+            cls.getModifiers() & 0xffff);
+    this.classMap.put(cls, new ClassDescriptor(cls, classPtr, null));
+  }
+
+//</editor-fold>
+//<editor-fold desc="members" defaultstate="collapsed">
+  public synchronized void populateMembers(Class cls)
+  {
+    ClassDescriptor desc = this.classMap.get(cls);
+    if (desc == null)
+      throw new RuntimeException("Class not loaded");
+    if (desc.fields != null)
+      return;
+    try
+    {
+      createMembers(desc);
+    } catch (Exception ex)
+    {
+      ex.printStackTrace(System.out);
+      throw ex;
+    }
+  }
+
+  private void createMembers(ClassDescriptor desc)
+  {
+    this.createFields(desc);
+    this.createConstructorDispatch(desc);
+    this.createMethodDispatches(desc);
+
+    // Verify integrity
+    if (audit != null)
+      audit.verifyMembers(desc);
+
+    // Pass this to JPype
+    this.typeFactory.assignMembers(context,
+            desc.classPtr,
+            desc.constructorDispatch,
+            desc.methodDispatch,
+            desc.fields);
+  }
+
+//<editor-fold desc="fields" defaultstate="collapsed">
+  private void createFields(ClassDescriptor desc)
+  {
+    // We only need declared fields as the wrappers for previous classes hold
+    // members declared earlier
+    LinkedList<Field> fields = filterPublic(desc.cls.getDeclaredFields());
+
+    long[] fieldPtr = new long[fields.size()];
+    int i = 0;
+    for (Field field : fields)
+    {
+      fieldPtr[i++] = this.typeFactory.defineField(context,
+              desc.classPtr,
+              field.getName(),
+              field,
+              getClass(field.getType()).classPtr,
+              field.getModifiers() & 0xffff);
+    }
+    desc.fields = fieldPtr;
+  }
+//</editor-fold>
+//<editor-fold desc="ctor" defaultstate="collapsed">
+
+  /**
+   * Load the constructors for a class.
+   *
+   * @param desc
+   */
+  public void createConstructorDispatch(ClassDescriptor desc)
+  {
+    Class cls = desc.cls;
+
+    // Get the list of declared constructors
+    LinkedList<Constructor> constructors
+            = filterPublic(cls.getDeclaredConstructors());
+
+    if (constructors.isEmpty())
+      return;
+
+    // Sort them by precedence order
+    List<MethodResolution> overloads = MethodResolution.sortMethods(constructors);
+
+    // Convert overload list to a list of overloads pointers
+    desc.constructors = this.createConstructors(desc, overloads);
+
+    // Create the dispatch for it
+    desc.constructorDispatch = typeFactory
+            .defineMethodDispatch(context,
+                    desc.classPtr,
+                    "<init>",
+                    desc.constructors,
+                    ModifierCode.PUBLIC.value | ModifierCode.CTOR.value);
+  }
+
+  /**
+   * Construct a set of constructor overloads for an OverloadResolution.
+   * <p>
+   * These will be added to the shutdown destruction list.
+   *
+   * @param desc
+   * @param overloads
+   * @return
+   */
+  private long[] createConstructors(ClassDescriptor desc,
+          List<MethodResolution> overloads)
+  {
+    int n = overloads.size();
+    long[] overloadPtrs = new long[overloads.size()];
+    for (MethodResolution ov : overloads)
+    {
+      Constructor constructor = (Constructor) ov.executable;
+
+      int i = 0;
+      long[] precedencePtrs = new long[ov.children.size()];
+      for (MethodResolution ch : ov.children)
+      {
+        precedencePtrs[i++] = ch.ptr;
+      }
+
+      int modifiers = constructor.getModifiers() & 0xffff;
+      modifiers |= ModifierCode.CTOR.value;
+      ov.ptr = typeFactory.defineMethod(context,
+              desc.classPtr,
+              constructor.toString(),
+              constructor,
+              precedencePtrs,
+              modifiers);
+      overloadPtrs[--n] = ov.ptr;
+    }
+    return overloadPtrs;
+  }
+
+//</editor-fold>
+//<editor-fold desc="methods" defaultstate="collapsed">
+  /**
+   * Load the methods for a class.
+   *
+   * @param desc
+   */
+  public void createMethodDispatches(ClassDescriptor desc)
+  {
+    Class<?> cls = desc.cls;
+
+    // Get the list of all public, non-overrided methods we will process
+    LinkedList<Method> methods = filterOverridden(cls, cls.getMethods());
+
+    // Get the list of public declared methods
+    LinkedList<Method> declaredMethods = filterOverridden(cls, cls.getDeclaredMethods());
+
+    // We only need one dispatch per name
+    TreeSet<String> resolve = new TreeSet<>();
+    for (Method method : declaredMethods)
+    {
+      resolve.add(method.getName());
+    }
+
+    // Reserve memory for our lookup table
+    desc.methods = new long[declaredMethods.size()];
+    desc.methodIndex = new Method[declaredMethods.size()];
+    desc.methodDispatch = new long[resolve.size()];
+
+    int i = 0;
+    for (String name : resolve)
+    {
+      desc.methodDispatch[i++] = this.createMethodDispatch(desc, name, methods);
+    }
+  }
+
+  private long createMethodDispatch(
+          ClassDescriptor desc,
+          String key,
+          LinkedList<Method> candidates)
+  {
+    // Find all the methods that match the key
+    LinkedList<Method> methods = new LinkedList<>();
+    Iterator<Method> iter = candidates.iterator();
+
+    int modifiers = 0;
+    while (iter.hasNext())
+    {
+      Method next = iter.next();
+      if (!next.getName().equals(key))
+        continue;
+      iter.remove();
+      methods.add(next);
+      if (Modifier.isStatic(next.getModifiers()))
+        modifiers |= ModifierCode.STATIC.value;
+      if (isBeanAccessor(next))
+        modifiers |= ModifierCode.BEAN_ACCESSOR.value;
+      if (isBeanMutator(next))
+        modifiers |= ModifierCode.BEAN_MUTATOR.value;
+    }
+
+    // Convert overload list to a list of overloads pointers
+    List<MethodResolution> overloads = MethodResolution.sortMethods(methods);
+    long[] overloadPtrs = this.createMethods(desc, overloads);
+
+    long methodContainer = typeFactory.defineMethodDispatch(context,
+            desc.classPtr,
+            key,
+            overloadPtrs,
+            modifiers);
+
+    return methodContainer;
+  }
+
+  /**
+   * Convert a list of executable overload resolutions into a executable
+   * overload list.
+   * <p>
+   * These will be added to the shutdown destruction list.
+   *
+   * @param desc
+   * @param overloads
+   * @return a list of method overload wrappers.
+   */
+  private long[] createMethods(
+          ClassDescriptor desc,
+          List<MethodResolution> overloads)
+  {
+    int n = overloads.size();
+    long[] overloadPtrs = new long[overloads.size()];
+    for (MethodResolution ov : overloads)
+    {
+      Method method = (Method) ov.executable;
+
+      // We may already have built a methodoverload for this
+      Class<?> decl = method.getDeclaringClass();
+      if (method.getDeclaringClass() != desc.cls)
+      {
+        this.populateMembers(decl);
+        ov.ptr = this.classMap.get(decl).getMethod(method);
+        if (ov.ptr == 0)
+        {
+          if (audit != null)
+            audit.failFindMethod(desc, method);
+          throw new RuntimeException("Fail");
+        }
+        overloadPtrs[--n] = ov.ptr;
+        continue;
+      }
+
+      // Determine what takes precedence
+      int i = 0;
+      long[] precedencePtrs = new long[ov.children.size()];
+      for (MethodResolution ch : ov.children)
+      {
+        precedencePtrs[i++] = ch.ptr;
+      }
+
+      int modifiers = method.getModifiers() & 0xffff;
+      if (isBeanMutator(method))
+        modifiers |= ModifierCode.BEAN_MUTATOR.value;
+      if (isBeanAccessor(method))
+        modifiers |= ModifierCode.BEAN_ACCESSOR.value;
+      if (isCallerSensitive(method))
+        modifiers |= ModifierCode.CALLER_SENSITIVE.value;
+
+      ov.ptr = typeFactory.defineMethod(context,
+              desc.classPtr,
+              method.toString(),
+              method,
+              precedencePtrs,
+              modifiers);
+      overloadPtrs[--n] = ov.ptr;
+      desc.methods[desc.methodCounter] = ov.ptr;
+      desc.methodIndex[desc.methodCounter] = method;
+      desc.methodCounter++;
+    }
+    return overloadPtrs;
+  }
+
+  static boolean hasCallerSensitive = false;
+
+  static
+  {
+    try
+    {
+      java.lang.reflect.Method method = java.lang.Class.class.getDeclaredMethod("forName", String.class);
+      for (Annotation annotation : method.getAnnotations())
+      {
+        if ("@jdk.internal.reflect.CallerSensitive()".equals(annotation.toString()))
+        {
+          hasCallerSensitive = true;
+        }
+      }
+    } catch (NoSuchMethodException | SecurityException ex)
+    {
+    }
+  }
+
+  /**
+   * Checks to see if the method is caller sensitive.
+   *
+   * As the annotation is a private internal, we must check by name.
+   *
+   * @param method is the method to be probed.
+   * @return true if caller sensitive.
+   */
+  public static boolean isCallerSensitive(Method method)
+  {
+    if (hasCallerSensitive)
+    {
+      for (Annotation annotation : method.getAnnotations())
+      {
+        if ("@jdk.internal.reflect.CallerSensitive()".equals(annotation.toString()))
+        {
+          return true;
+        }
+      }
+    } else
+    {
+      // JDK prior versions prior to 9 do not annotate methods that
+      // require special handling, thus we will just blanket those
+      // classes known to have issues.
+      Class<?> cls = method.getDeclaringClass();
+      if (cls.equals(java.lang.Class.class)
+              || cls.equals(java.lang.ClassLoader.class)
+              || cls.equals(java.sql.DriverManager.class))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+//</editor-fold>
+//<editor-fold desc="containers" defaultstate="collapsed">
+//</editor-fold>
+//</editor-fold>
+//<editor-fold desc="filters" defaultstate="collapsed">
+  /**
+   * Remove any methods that are not public from a list.
+   *
+   * @param <T>
+   * @param methods
+   * @return a new list containing only public members.
+   */
+  public static <T extends Member> LinkedList<T> filterPublic(T[] methods)
+  {
+    LinkedList<T> out = new LinkedList<>();
+    for (T method : methods)
+    {
+      if (!Modifier.isPublic(method.getModifiers()))
+        continue;
+      out.add(method);
+    }
+    return out;
+  }
+
+  /**
+   * Remove any methods that are not public and have been overridden from a
+   * list.
+   *
+   * @param cls
+   * @param methods
+   * @return a new list containing only public members that are not overridden.
+   */
+  public static LinkedList<Method> filterOverridden(Class<?> cls, Method[] methods)
+  {
+    LinkedList<Method> out = new LinkedList<>();
+    for (Method method : methods)
+    {
+      if (!Modifier.isPublic(method.getModifiers()) || isOverridden(cls, method))
+        continue;
+      out.add(method);
+    }
+    return out;
+  }
+
+//</editor-fold>
+//<editor-fold desc="utilities" defaultstate="collapsed">
+  /**
+   * Determines if a method is masked by another in a class.
+   *
+   * @param cls is the class to investigate.
+   * @param method is a method that applies to the class.
+   * @return true if the method is hidden by another method.
+   */
+  public static boolean isOverridden(Class<?> cls, Method method)
+  {
+    try
+    {
+      return !method.equals(cls.getMethod(method.getName(), method.getParameterTypes()));
+    } catch (NoSuchMethodException | SecurityException ex)
+    {
+      return false;
+    }
+  }
+
+  /**
+   * Bean accessor is flag is used for property module.
+   * <p>
+   * Accessors need
+   *
+   * @param method
+   * @return
+   */
+  private boolean isBeanAccessor(Method method)
+  {
+    if (Modifier.isStatic(method.getModifiers()))
+      return false;
+    if (method.getReturnType().equals(void.class))
+      return false;
+    if (method.getParameterCount() > 0)
+      return false;
+    if (method.getName().length() < 4)
+      return false;
+    return (method.getName().startsWith("get"));
+  }
+
+  /**
+   * Bean mutator is flag is used for property module.
+   *
+   * @param method
+   * @return
+   */
+  private boolean isBeanMutator(Method method)
+  {
+    if (Modifier.isStatic(method.getModifiers()))
+      return false;
+    if (!method.getReturnType().equals(void.class))
+      return false;
+    if (method.getParameterCount() != 1)
+      return false;
+    if (method.getName().length() < 4)
+      return false;
+    return (method.getName().startsWith("set"));
+  }
+
+//</editor-fold>
+//<editor-fold desc="inner" defaultstate="collapsed">
+  private class Destroyer
+  {
+
+    final int BLOCK_SIZE = 1024;
+    long[] queue = new long[BLOCK_SIZE];
+    int index = 0;
+
+    void add(long v)
+    {
+      if (v == 0)
+        return;
+      queue[index++] = v;
+      if (index == BLOCK_SIZE)
+        flush();
+    }
+
+    void add(long[] v)
+    {
+      if (v == null)
+        return;
+      if (v.length > BLOCK_SIZE / 2)
+      {
+        typeFactory.destroy(context, v, v.length);
+        return;
+      }
+      if (index + v.length > BLOCK_SIZE)
+      {
+        flush();
+      }
+      for (int j = 0; j < v.length; ++j)
+      {
+        queue[index++] = v[j];
+      }
+      if (index == BLOCK_SIZE)
+        flush();
+    }
+
+    void flush()
+    {
+      typeFactory.destroy(context, queue, index);
+      index = 0;
+    }
+  }
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pickle/ByteBufferInputStream.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pickle/ByteBufferInputStream.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pickle/ByteBufferInputStream.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pickle/ByteBufferInputStream.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,95 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.pickle;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+
+public class ByteBufferInputStream extends InputStream
+{
+
+  private LinkedList<ByteBuffer> buffers = new LinkedList<>();
+
+  public void put(byte[] bytes)
+  {
+    // We can just wrap the buffer instead of copying it, since the buffer is
+    // wrapped we don't need to write the bytes to it. Wrapping the bytes relies
+    // on the array not being changed before being read.
+    ByteBuffer buffer = ByteBuffer.wrap(bytes);
+    buffers.add(buffer);
+  }
+
+  @Override
+  public int read() throws IOException
+  {
+    if (buffers.isEmpty())
+      return -1;
+
+    ByteBuffer b = buffers.getFirst();
+    while (b.remaining() == 0)
+    {
+      buffers.removeFirst();
+      if (buffers.isEmpty())
+        return -1; // EOF
+      b = buffers.getFirst();
+    }
+    return b.get() & 0xFF; // Mask with 0xFF to convert signed byte to int (range 0-255)
+  }
+
+  @Override
+  public int read(byte[] arg0) throws IOException
+  {
+    return read(arg0, 0, arg0.length);
+  }
+
+  @Override
+  public int read(byte[] buffer, int offset, int len) throws IOException
+  {
+    if (buffer == null)
+      throw new NullPointerException("Buffer cannot be null");
+    if (offset < 0 || len < 0 || len > buffer.length - offset)
+      throw new IndexOutOfBoundsException("Invalid offset/length parameters");
+    if (len == 0)
+      return 0;
+
+    int total = 0;
+    while (len > 0 && !buffers.isEmpty())
+    {
+      ByteBuffer b = buffers.getFirst();
+      int remaining = b.remaining();
+      if (remaining == 0)
+      {
+        buffers.removeFirst();
+        continue;
+      }
+
+      int toRead = Math.min(len, remaining);
+      b.get(buffer, offset, toRead);
+      total += toRead;
+      len -= toRead;
+      offset += toRead;
+    }
+    return (total == 0) ? -1 : total;
+  }
+
+  @Override
+  public void close() throws IOException
+  {
+    buffers.clear();
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Decoder.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Decoder.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Decoder.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Decoder.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,39 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.pickle;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+public class Decoder
+{
+
+  ByteBufferInputStream bb;
+  ObjectInputStream ois = null;
+
+  public Decoder() throws IOException
+  {
+    bb = new ByteBufferInputStream();
+  }
+
+  public Object unpack(byte[] data) throws IOException, ClassNotFoundException
+  {
+    bb.put(data);
+    if (ois == null)
+      ois = new ObjectInputStream(bb);
+    return ois.readObject();
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Encoder.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Encoder.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Encoder.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pickle/Encoder.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,46 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.pickle;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ *
+ * @author Karl Einar Nelson
+ */
+public class Encoder
+{
+
+  ByteArrayOutputStream baos;
+  ObjectOutputStream oos;
+
+  public Encoder() throws IOException
+  {
+    baos = new ByteArrayOutputStream();
+    oos = new ObjectOutputStream(baos);
+  }
+
+  public byte[] pack(Object obj) throws IOException
+  {
+    oos.writeObject(obj);
+    oos.flush();
+    byte[] out = baos.toByteArray().clone();
+    baos.reset();
+    return out;
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackage.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackage.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackage.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackage.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,235 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.pkg;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Map;
+import org.jpype.JPypeClassLoader;
+import org.jpype.JPypeContext;
+import org.jpype.JPypeKeywords;
+
+/**
+ * Representation of a JPackage in Java.
+ *
+ * This provides the dir and attributes for a JPackage and by extension jpype
+ * imports. Almost all of the actual work happens in the PackageManager which
+ * acts like the classloader to figure out what resource are available.
+ *
+ */
+public class JPypePackage
+{
+
+  // Name of the package
+  final String pkg;
+  // A mapping from Python names into Paths into the module/jar file system.
+  Map<String, URI> contents;
+  int code;
+  private final JPypeClassLoader classLoader;
+
+  public JPypePackage(String pkg)
+  {
+    this.pkg = pkg;
+    this.contents = JPypePackageManager.getContentMap(pkg);
+    this.classLoader = ((JPypeClassLoader) (JPypeContext.getInstance().getClassLoader()));
+    this.code = classLoader.getCode();
+  }
+
+  /**
+   * Get an object from the package.
+   *
+   * This is used by the importer to create the attributes for `getattro`. The
+   * type returned is polymorphic. We can potentially support any type of
+   * resource (package, classes, property files, xml, data, etc). But for now we
+   * are primarily interested in packages and classes. Packages are returned as
+   * strings as loading the package info is not guaranteed to work. Classes are
+   * returned as classes which are immediately converted into Python wrappers.
+   * We can return other resource types so long as they have either a wrapper
+   * type to place the instance into an Python object directly or a magic
+   * wrapper which will load the resource into a Python object type.
+   *
+   * This should match the acceptable types in getContents so that everything in
+   * the `dir` is also an attribute of JPackage.
+   *
+   * @param name is the name of the resource.
+   * @return the object or null if no resource is found with a matching name.
+   */
+  public Object getObject(String name)
+  {
+    // We can't use the url contents as the contents may be incomplete due
+    // to bugs in the JVM classloaders.  Instead we will have to probe.
+    String basename = pkg + "." + JPypeKeywords.unwrap(name);
+    ClassLoader cl = JPypeContext.getInstance().getClassLoader();
+    try
+    {
+      // Check if it a package
+      if (JPypePackageManager.isPackage(basename))
+      {
+        return basename;
+      }
+
+      // Else probe for a class.
+      Class<?> cls = Class.forName(basename, false, JPypeContext.getInstance().getClassLoader());
+      if (Modifier.isPublic(cls.getModifiers()))
+        return Class.forName(basename, true, cl);
+    } catch (ClassNotFoundException ex)
+    {
+      // Continue
+    }
+    return null;
+  }
+
+  /**
+   * Get a list of contents from a Java package.
+   *
+   * This will be used when creating the package `dir`
+   *
+   * @return
+   */
+  public String[] getContents()
+  {
+    checkCache();
+    ArrayList<String> out = new ArrayList<>();
+    for (String key : contents.keySet())
+    {
+      URI uri = contents.get(key);
+      // If there is anything null, then skip it.
+      if (uri == null)
+        continue;
+      Path p = JPypePackageManager.getPath(uri);
+
+      // package are acceptable
+      if (Files.isDirectory(p))
+        out.add(key);
+
+      // classes must be public
+      else if (uri.toString().endsWith(".class"))
+      {
+        // Make sure it is public
+        if (isPublic(p))
+          out.add(key);
+      }
+    }
+    return out.toArray(new String[out.size()]);
+  }
+
+  /**
+   * Determine if a class is public.
+   *
+   * This checks if a class file contains a public class. When importing classes
+   * we do not want to instantiate a class which is not public as it may result
+   * in instantiation of static variables or unwanted class resources. The only
+   * alternative is to read the class file and get the class modifier flags.
+   * Unfortunately, the developers of Java were rather stingy on their byte
+   * allocation and thus the field we want is not in the header but rather
+   * buried after the constant pool. Further as they didn't give the actual size
+   * of the tables in bytes, but rather in entries, that means we have to parse
+   * the whole table just to get the access flags after it.
+   *
+   * @param p
+   * @return
+   */
+  static boolean isPublic(Path p)
+  {
+    try (InputStream is = Files.newInputStream(p))
+    {
+      // Allocate a three byte buffer for traversing the constant pool.
+      // The minumum entry is a byte for the type and 2 data bytes.  We
+      // will read these three bytes and then based on the type advance
+      // the read pointer to the next entry.
+      ByteBuffer buffer3 = ByteBuffer.allocate(3);
+
+      // Check the magic
+      ByteBuffer header = ByteBuffer.allocate(4 + 2 + 2 + 2);
+      is.read(header.array());
+      ((Buffer) header).rewind();
+      int magic = header.getInt();
+      if (magic != (int) 0xcafebabe)
+        return false;
+      header.getShort(); // skip major
+      header.getShort(); // skip minor
+      short cpitems = header.getShort(); // get the number of items
+
+      // Traverse the cp pool
+      for (int i = 0; i < cpitems - 1; ++i)
+      {
+        is.read(buffer3.array());
+        ((Buffer) buffer3).rewind();
+        byte type = buffer3.get(); // First byte is the type
+
+        // Now based on the entry type we will advance the pointer
+        switch (type)
+        {
+          case 1:  // Strings are variable length
+            is.skip(buffer3.getShort());
+            break;
+          case 7:
+          case 8:
+          case 16:
+          case 19:
+          case 20:
+            break;
+          case 15:
+            is.skip(1);
+            break;
+          case 3:
+          case 4:
+          case 9:
+          case 10:
+          case 11:
+          case 12:
+          case 17:
+          case 18:
+            is.skip(2);
+            break;
+          case 5:
+          case 6:
+            is.skip(6); // double and long are special as they are double entries
+            i++; // long and double take two slots
+            break;
+          default:
+            return false;
+        }
+      }
+
+      // Get the flags
+      is.read(buffer3.array());
+      ((Buffer) buffer3).rewind();
+      short flags = buffer3.getShort();
+      return (flags & 1) == 1; // it is public if bit zero is set
+    } catch (IOException ex)
+    {
+      return false; // If anything goes wrong then it won't be considered a public class.
+    }
+  }
+
+  void checkCache()
+  {
+    int current = classLoader.getCode();
+    if (this.code == current)
+      return;
+    this.code = current;
+    this.contents = JPypePackageManager.getContentMap(pkg);
+  }
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackageManager.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackageManager.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackageManager.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/pkg/JPypePackageManager.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,494 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.pkg;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.ProviderNotFoundException;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.jpype.JPypeContext;
+import org.jpype.JPypeKeywords;
+
+/**
+ * Manager for the contents of a package.
+ *
+ * This class uses a number of tricks to provide a way to determine what
+ * packages are available in the class loader. It searches the jar path, the
+ * boot path (Java 8), and the module path (Java 9+). It does not currently work
+ * with alternative classloaders. This class was rumored to be unobtainium as
+ * endless posts indicated that it wasn't possible to determine the contents of
+ * a package in general nor to retrieve the package contents, but this appears
+ * to be largely incorrect as the jar and jrt file system provide all the
+ * required methods.
+ *
+ */
+public class JPypePackageManager
+{
+
+  final static List<FileSystem> bases = new ArrayList();
+  final static List<ModuleDirectory> modules = getModules();
+  final static FileSystemProvider jfsp = getFileSystemProvider("jar");
+  final static Map<String, String> env = new HashMap<>();
+  final static LinkedList<FileSystem> fs = new LinkedList<>();
+
+  /**
+   * Checks if a package exists.
+   *
+   * @param name is the name of the package.
+   * @return true if this is a Java package either in a jar, module, or in the
+   * boot path.
+   */
+  public static boolean isPackage(String name)
+  {
+    if (name.indexOf('.') != -1)
+      name = name.replace(".", "/");
+    if (isModulePackage(name) || isBasePackage(name) || isJarPackage(name))
+      return true;
+    return false;
+  }
+
+  /**
+   * Get the list of the contents of a package.
+   *
+   * @param packageName
+   * @return the list of all resources found.
+   */
+  public static Map<String, URI> getContentMap(String packageName)
+  {
+    Map<String, URI> out = new HashMap<>();
+    packageName = packageName.replace(".", "/");
+    // We need to merge all the file systems into one view like the classloader
+    getJarContents(out, packageName);
+    getBaseContents(out, packageName);
+    getModuleContents(out, packageName);
+    return out;
+  }
+
+  /**
+   * Convert a URI into a path.
+   *
+   * This has special magic methods to deal with jar file systems.
+   *
+   * @param uri is the location of the resource.
+   * @return the path to the uri resource.
+   */
+  static Path getPath(URI uri)
+  {
+    try
+    {
+      return Paths.get(uri);
+    } catch (java.nio.file.FileSystemNotFoundException ex)
+    {
+    }
+
+    if (uri.getScheme().equals("jar"))
+    {
+      try
+      {
+        // Limit the number of filesystems open at any one time
+        fs.add(jfsp.newFileSystem(uri, env));
+        if (fs.size() > 8)
+          fs.removeFirst().close();
+        return Paths.get(uri);
+      } catch (IOException ex)
+      {
+      }
+    }
+    throw new FileSystemNotFoundException("Unknown filesystem for " + uri);
+  }
+
+  /**
+   * Retrieve the Jar file system.
+   *
+   * @return
+   */
+  private static FileSystemProvider getFileSystemProvider(String str)
+  {
+    for (FileSystemProvider fsp : FileSystemProvider.installedProviders())
+    {
+      if (fsp.getScheme().equals(str))
+        return fsp;
+    }
+    throw new FileSystemNotFoundException("Unable to find filesystem for " + str);
+  }
+
+//<editor-fold desc="java 8" defaultstate="collapsed">
+  /**
+   * Older versions of Java do not have a file system for boot packages. Thus
+   * rather working through the classloader, we will instead probe java to get
+   * the rt.jar. Crypto is a special case as it has its own jar. All other
+   * resources are sourced through the regular jar loading method.
+   */
+  static
+  {
+    env.put("create", "true");
+
+    ClassLoader cl = ClassLoader.getSystemClassLoader();
+    URI uri = null;
+    try
+    {
+      // This is for Java 8 and earlier in which the API jars are in rt.jar
+      // and jce.jar
+      uri = cl.getResource("java/lang/String.class").toURI();
+      if (uri != null && uri.getScheme().equals("jar"))
+      {
+        FileSystem fs = jfsp.newFileSystem(uri, env);
+        if (fs != null)
+          bases.add(fs);
+      }
+      uri = cl.getResource("javax/crypto/Cipher.class").toURI();
+      if (uri != null && uri.getScheme().equals("jar"))
+      {
+        FileSystem fs = jfsp.newFileSystem(uri, env);
+        if (fs != null)
+          bases.add(fs);
+      }
+    } catch (URISyntaxException | IOException ex)
+    {
+    }
+  }
+
+  private static void getBaseContents(Map<String, URI> out, String packageName)
+  {
+    for (FileSystem b : bases)
+    {
+      collectContents(out, b.getPath(packageName));
+    }
+  }
+
+  /**
+   * Check if a name is a package in the java bootstrap classloader.
+   *
+   * @param name
+   * @return
+   */
+  private static boolean isBasePackage(String name)
+  {
+    try
+    {
+      if (name.isEmpty())
+        return false;
+      for (FileSystem jar : bases)
+      {
+        if (Files.isDirectory(jar.getPath(name)))
+          return true;
+      }
+      return false;
+    } catch (Exception ex)
+    {
+      throw new RuntimeException("Fail checking package '" + name + "'", ex);
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="java 9" defaultstate="collapsed">
+  /**
+   * Get a list of all modules.
+   *
+   * This may be many modules or just a few. Limited distributes created using
+   * jlink will only have a portion of the usual modules.
+   *
+   * @return
+   */
+  static List<ModuleDirectory> getModules()
+  {
+    ArrayList<ModuleDirectory> out = new ArrayList<>();
+    try
+    {
+      FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+      Path modulePath = fs.getPath("modules");
+      for (Path module : Files.newDirectoryStream(modulePath))
+      {
+        out.add(new ModuleDirectory(module));
+      }
+    } catch (ProviderNotFoundException | IOException ex)
+    {
+    }
+    return out;
+  }
+
+  /**
+   * Check if a name corresponds to a package in a module.
+   *
+   * @param name
+   * @return true if it is a package.
+   */
+  private static boolean isModulePackage(String name)
+  {
+    if (modules.isEmpty())
+      return false;
+    String[] split = name.split("/");
+    String search = name;
+    if (split.length > 3)
+      search = String.join("/", Arrays.copyOfRange(split, 0, 3));
+    for (ModuleDirectory module : modules)
+    {
+      if (module.contains(search))
+      {
+        if (Files.isDirectory(module.modulePath.resolve(name)))
+          return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Retrieve the contents of a module by package name.
+   *
+   * @param out
+   * @param name
+   */
+  private static void getModuleContents(Map<String, URI> out, String name)
+  {
+    if (modules.isEmpty())
+      return;
+    String[] split = name.split("/");
+    String search = name;
+    if (split.length > 3)
+      search = String.join("/", Arrays.copyOfRange(split, 0, 3));
+    for (ModuleDirectory module : modules)
+    {
+      if (module.contains(search))
+      {
+        Path path2 = module.modulePath.resolve(name);
+        if (Files.isDirectory(path2))
+          collectContents(out, path2);
+      }
+    }
+  }
+
+  /**
+   * Modules are stored in the jrt filesystem.
+   *
+   * However, that is not a simple flat filesystem by path as the jrt files are
+   * structured by package name. Thus we will need a separate structure which is
+   * rooted at the top of each module.
+   */
+  private static class ModuleDirectory
+  {
+
+    List<String> contents = new ArrayList<>();
+    private final Path modulePath;
+
+    ModuleDirectory(Path module)
+    {
+      this.modulePath = module;
+      listPackages(contents, module, module, 0);
+    }
+
+    boolean contains(String path)
+    {
+      for (String s : contents)
+      {
+        if (s.equals(path))
+          return true;
+      }
+      return false;
+    }
+
+    private static void listPackages(List<String> o, Path base, Path p, int depth)
+    {
+      try
+      {
+        if (depth >= 3)
+          return;
+        for (Path d : Files.newDirectoryStream(p))
+        {
+          if (Files.isDirectory(d))
+          {
+            o.add(base.relativize(d).toString());
+            listPackages(o, base, d, depth + 1);
+          }
+        }
+      } catch (IOException ex)
+      {
+      }
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="jar" defaultstate="collapsed">
+  /**
+   * Checks if a name corresponds to package in a jar file or on the classpath
+   * filesystem.
+   *
+   * Classloaders provide a method to get all resources with a given name. This
+   * is needed because the same package name may appear in multiple jars or
+   * filesystems. We do not need to disambiguate it here, but just get a listing
+   * that we can use to find a resource later.
+   *
+   * @param name is the name of the package to search for.
+   * @return true if the name corresponds to a Java package.
+   */
+  private static boolean isJarPackage(String name)
+  {
+    ClassLoader cl = JPypeContext.getInstance().getClassLoader();
+    try
+    {
+      Enumeration<URL> resources = cl.getResources(name);
+      while (resources.hasMoreElements())
+      {
+        URI uri = resources.nextElement().toURI();
+        if (Files.isDirectory(getPath(uri)))
+          return true;
+      }
+    } catch (IOException | URISyntaxException ex)
+    {
+    }
+    return false;
+  }
+
+  /**
+   * Retrieve a list of packages and classes stored on a file system or in a
+   * jar.
+   *
+   * @param out is the map to store the result in.
+   * @param packageName is the name of the package
+   */
+  private static void getJarContents(Map<String, URI> out, String packageName)
+  {
+    ClassLoader cl = JPypeContext.getInstance().getClassLoader();
+    try
+    {
+      String path = packageName.replace('.', '/');
+      Enumeration<URL> resources = cl.getResources(path);
+      while (resources.hasMoreElements())
+      {
+        URI resource = resources.nextElement().toURI();
+
+        // Handle MRJAR format
+        //   MRJAR may not report every directory but instead just the overlay.
+        //   So we need to find the original and interogate it first before
+        //   checking the version specific part.  Worst case we collect the
+        //   contents twice.
+        String schemePart = resource.getSchemeSpecificPart();
+        int index = schemePart.indexOf("!");
+        if (index != -1)
+        {
+          if (schemePart.substring(index + 1).startsWith("/META-INF/versions/"))
+          {
+            int index2 = schemePart.indexOf('/', index + 20);
+            schemePart = schemePart.substring(0, index + 1) + schemePart.substring(index2);
+            URI resource2 = new URI(resource.getScheme() + ":" + schemePart);
+            Path path3 = getPath(resource2);
+            collectContents(out, path3);
+          }
+        }
+
+        Path path2 = getPath(resource);
+        collectContents(out, path2);
+      }
+    } catch (IOException | URISyntaxException ex)
+    {
+    }
+  }
+
+//</editor-fold>
+//<editor-fold desc="utility" defaultstate="collapsed">
+  /**
+   * Collect the contents from a path.
+   *
+   * This operates on jars, modules, and filesystems to collect the names of all
+   * resources found. We skip over inner classes as those are accessed under
+   * their included classes. For now we are not screening against other private
+   * symbols.
+   *
+   * @param out is the map to store the result in.
+   * @param path2 is a path holding a directory to probe.
+   */
+  private static void collectContents(Map<String, URI> out, Path path2)
+  {
+    try
+    {
+      for (Path file : Files.newDirectoryStream(path2))
+      {
+        String filename = file.getFileName().toString();
+        if (Files.isDirectory(file))
+        {
+          // Same implementations add the path separator to the end of toString().
+          if (filename.endsWith(file.getFileSystem().getSeparator()))
+            filename = filename.substring(0, filename.length() - 1);
+          out.put(JPypeKeywords.wrap(filename), toURI(file));
+          continue;
+        }
+        // Skip inner classes
+        if (filename.contains("$"))
+          continue;
+
+        // Include class files
+        if (filename.endsWith(".class"))
+        {
+          String key = JPypeKeywords.wrap(filename.substring(0, filename.length() - 6));
+          out.put(key, toURI(file));
+        }
+
+        // We can add other types of files here and import them in JPypePackage
+        // as required.
+      }
+    } catch (IOException ex)
+    {
+    }
+  }
+
+  private static URI toURI(Path path)
+  {
+    URI uri = path.toUri();
+
+    try
+    {
+      // Java 8 bug https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8131067
+      // Zip file system provider returns doubly % encoded URIs. We resolve this
+      // by re-encoding the URI after decoding it.
+      uri = new URI(
+              uri.getScheme(),
+              URLDecoder.decode(uri.getSchemeSpecificPart(), StandardCharsets.UTF_8),
+              uri.getFragment()
+      );
+
+      // `toASCIIString` ensures the URI is URL encoded with only ascii
+      // characters. This avoids issues in `sun.nio.fs.UnixUriUtils.fromUri` that
+      // naively uses `uri.getRawPath()` despite the possibility that it contains
+      // non-ascii characters that will cause errors. By using `toASCIIString` and
+      // re-wrapping it in a URI object we ensure that the URI is properly
+      // encoded. See: https://github.com/jpype-project/jpype/issues/1194
+      return new URI(uri.toASCIIString());
+    } catch (Exception e)
+    {
+      // This exception *should* never occur as we are re-encoding a valid URI.
+      // Throwing a runtime exception avoids java exception handling boilerplate
+      // for a situation that *should* never occur.
+      throw new RuntimeException("Failed to encode URI: " + uri, e);
+    }
+  }
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/proxy/JPypeProxy.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/proxy/JPypeProxy.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/proxy/JPypeProxy.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/proxy/JPypeProxy.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,165 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.proxy;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.jpype.JPypeContext;
+import org.jpype.manager.TypeManager;
+import org.jpype.ref.JPypeReferenceQueue;
+
+/**
+ *
+ * @author Karl Einar Nelson
+ */
+public class JPypeProxy implements InvocationHandler
+{
+
+  private final static Constructor<Lookup> constructor;
+  private final static JPypeReferenceQueue referenceQueue = JPypeReferenceQueue.getInstance();
+  JPypeContext context;
+  public long instance;
+  public long cleanup;
+  Class<?>[] interfaces;
+  ClassLoader cl = ClassLoader.getSystemClassLoader();
+  public static Object missing = new Object();
+
+  // See following link for Java 8 default access implementation
+  //   https://blog.jooq.org/correct-reflective-access-to-interface-default-methods-in-java-8-9-10/
+  static
+  {
+    Constructor<Lookup> c = null;
+    if (System.getProperty("java.version").startsWith("1."))
+    {
+      try
+      {
+        c = Lookup.class
+                .getDeclaredConstructor(Class.class);
+        c.setAccessible(true);
+      } catch (NoSuchMethodException | SecurityException ex)
+      {
+        Logger.getLogger(JPypeProxy.class.getName()).log(Level.SEVERE, null, ex);
+      }
+    }
+    constructor = c;
+  }
+
+  public static JPypeProxy newProxy(JPypeContext context,
+          long instance,
+          long cleanup,
+          Class<?>[] interfaces)
+  {
+    JPypeProxy proxy = new JPypeProxy();
+    proxy.context = context;
+    proxy.instance = instance;
+    proxy.interfaces = interfaces;
+    proxy.cleanup = cleanup;
+    // Proxies must point to the correct class loader.  For most cases the
+    // system classloader is find.  But if the class is in a custom classloader
+    // we need to use that one instead
+    for (Class cls : interfaces)
+    {
+      ClassLoader icl = cls.getClassLoader();
+      if (icl != null && icl != proxy.cl)
+        proxy.cl = icl;
+    }
+    return proxy;
+  }
+
+  public Object newInstance()
+  {
+    Object out = Proxy.newProxyInstance(cl, interfaces, this);
+    referenceQueue.registerRef(out, instance, cleanup);
+    return out;
+  }
+
+  @Override
+  public Object invoke(Object proxy, Method method, Object[] args)
+          throws Throwable
+  {
+
+    if (context.isShutdown())
+      throw new RuntimeException("Proxy called during shutdown");
+
+    // We can save a lot of effort on the C++ side by doing all the
+    // type lookup work here.
+    TypeManager typeManager = context.getTypeManager();
+    long returnType;
+    long[] parameterTypes;
+    synchronized (typeManager)
+    {
+      returnType = typeManager.findClass(method.getReturnType());
+      Class<?>[] types = method.getParameterTypes();
+      parameterTypes = new long[types.length];
+      for (int i = 0; i < types.length; ++i)
+      {
+        parameterTypes[i] = typeManager.findClass(types[i]);
+      }
+    }
+
+    // Check first to see if Python has implementated it
+    Object result = hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args, missing);
+
+    // If we get a good result than return it
+    if (result != missing)
+      return result;
+
+    // If it is a default method in the interface then we have to invoke it using special reflection.
+    if (method.isDefault())
+    {
+      try
+      {
+        Class<?> cls = method.getDeclaringClass();
+
+        // Java 8
+        if (constructor != null)
+        {
+          return constructor.newInstance(cls)
+                  .findSpecial(cls,
+                          method.getName(),
+                          MethodType.methodType(method.getReturnType()),
+                          cls)
+                  .bindTo(proxy)
+                  .invokeWithArguments(args);
+        }
+
+        return MethodHandles.lookup()
+                .findSpecial(cls,
+                        method.getName(),
+                        MethodType.methodType(method.getReturnType()),
+                        cls)
+                .bindTo(proxy)
+                .invokeWithArguments(args);
+      } catch (java.lang.IllegalAccessException ex)
+      {
+        throw new RuntimeException(ex);
+      }
+    }
+
+    // Else throw... (this should never happen as proxies are checked when created.)
+    throw new NoSuchMethodError(method.getName());
+  }
+
+  private static native Object hostInvoke(long context, String name, long pyObject,
+          long returnType, long[] argsTypes, Object[] args, Object bad);
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReference.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReference.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReference.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReference.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,55 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.ref;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * (internal) Reference to a PyObject*.
+ */
+class JPypeReference extends PhantomReference
+{
+
+  long hostReference;
+  long cleanup;
+  int pool;
+  int index;
+
+  public JPypeReference(ReferenceQueue arg1, Object javaObject, long host, long cleanup)
+  {
+    super(javaObject, arg1);
+    this.hostReference = host;
+    this.cleanup = cleanup;
+  }
+
+  @Override
+  public int hashCode()
+  {
+    return (int) hostReference;
+  }
+
+  @Override
+  public boolean equals(Object arg0)
+  {
+    if (!(arg0 instanceof JPypeReference))
+    {
+      return false;
+    }
+
+    return ((JPypeReference) arg0).hostReference == hostReference;
+  }
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceNative.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceNative.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceNative.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceNative.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,33 @@
+package org.jpype.ref;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ * @author nelson85
+ */
+public class JPypeReferenceNative
+{
+
+  /**
+   * Native hook to delete a native resource.
+   *
+   * @param host is the address of memory in C.
+   * @param cleanup is the address the function to cleanup the memory.
+   */
+  public static native void removeHostReference(long host, long cleanup);
+
+  /**
+   * Triggered by the sentinel when a GC starts.
+   */
+  public static native void wake();
+
+  /**
+   * Initialize resources.
+   *
+   * @param self
+   * @param m
+   */
+  public static native void init(Object self, Method m);
+
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceQueue.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceQueue.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceQueue.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceQueue.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,196 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.ref;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * Reference queue holds the life of python objects to be as long as java items.
+ * <p>
+ * Any java class that holds a pointer into python needs to have a reference so
+ * that python object does not go away if the references in python fall to zero.
+ * JPype will add an extra reference to the object which is removed when the
+ * python object dies.
+ *
+ * @author smenard
+ *
+ */
+final public class JPypeReferenceQueue extends ReferenceQueue
+{
+
+  private final static JPypeReferenceQueue INSTANCE = new JPypeReferenceQueue();
+  private JPypeReferenceSet hostReferences;
+  private boolean isStopped = false;
+  private Thread queueThread;
+  private Object queueStopMutex = new Object();
+  private PhantomReference sentinel = null;
+
+  public static JPypeReferenceQueue getInstance()
+  {
+    return INSTANCE;
+  }
+
+  private JPypeReferenceQueue()
+  {
+    super();
+    this.hostReferences = new JPypeReferenceSet();
+    addSentinel();
+    JPypeReferenceNative.removeHostReference(0, 0);
+    try
+    {
+      JPypeReferenceNative.init(this, getClass().getDeclaredMethod("registerRef", Object.class, Long.TYPE, Long.TYPE));
+    } catch (NoSuchMethodException | SecurityException ex)
+    {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  /**
+   * (internal) Binds the lifetime of a Python object to a Java object.
+   * <p>
+   * JPype adds an extra reference to a PyObject* and then calls this method to
+   * hold that reference until the Java object is garbage collected.
+   *
+   * @param javaObject is the object to bind the lifespan to.
+   * @param host is the pointer to the host object.
+   * @param cleanup is the pointer to the function to call to delete the
+   * resource.
+   */
+  public void registerRef(Object javaObject, long host, long cleanup)
+  {
+    if (cleanup == 0)
+      return;
+    if (isStopped)
+    {
+      JPypeReferenceNative.removeHostReference(host, cleanup);
+    } else
+    {
+      JPypeReference ref = new JPypeReference(this, javaObject, host, cleanup);
+      hostReferences.add(ref);
+    }
+  }
+
+  /**
+   * Start the threading queue.
+   */
+  public void start()
+  {
+    isStopped = false;
+    queueThread = new Thread(new Worker(), "Python Reference Queue");
+    queueThread.setDaemon(true);
+    queueThread.start();
+  }
+
+  /**
+   * Stops the reference queue.
+   * <p>
+   * This is called by jpype when the jvm shuts down.
+   */
+  public void stop()
+  {
+    try
+    {
+      synchronized (queueStopMutex)
+      {
+        synchronized (this)
+        {
+          isStopped = true;
+          queueThread.interrupt();
+        }
+
+        // wait for the thread to finish ...
+        queueStopMutex.wait(10000);
+      }
+    } catch (InterruptedException ex)
+    {
+      // who cares ...
+    }
+
+    // Empty the queue.
+    hostReferences.flush();
+  }
+
+  /**
+   * Checks the status of the reference queue.
+   *
+   * @return true is the queue is running.
+   */
+  public boolean isRunning()
+  {
+    return !isStopped;
+  }
+
+  /**
+   * Get the number of items in the reference queue.
+   *
+   * @return the number of python resources held.
+   */
+  public int getQueueSize()
+  {
+    return this.hostReferences.size();
+  }
+
+//<editor-fold desc="internal" defaultstate="collapsed">
+  /**
+   * Thread to monitor the queue and delete resources.
+   */
+  private class Worker implements Runnable
+  {
+
+    @Override
+    public void run()
+    {
+      while (!isStopped)
+      {
+        try
+        {
+          // Check if a ref has been queued. and check if the thread has been
+          // stopped every 0.25 seconds
+          JPypeReference ref = (JPypeReference) remove(250);
+          if (ref == sentinel)
+          {
+            addSentinel();
+            JPypeReferenceNative.wake();
+            continue;
+          }
+          if (ref != null)
+          {
+            long hostRef = ref.hostReference;
+            long cleanup = ref.cleanup;
+            hostReferences.remove(ref);
+            JPypeReferenceNative.removeHostReference(hostRef, cleanup);
+          }
+        } catch (InterruptedException ex)
+        {
+          // don't know why ... don't really care ...
+        }
+      }
+
+      synchronized (queueStopMutex)
+      {
+        queueStopMutex.notifyAll();
+      }
+    }
+  }
+
+  final void addSentinel()
+  {
+    sentinel = new JPypeReference(this, new byte[0], 0, 0);
+  }
+
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceSet.java 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceSet.java
--- 1.5.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceSet.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/native/jpype_module/src/main/java/org/jpype/ref/JPypeReferenceSet.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,148 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  See NOTICE file for details.
+**************************************************************************** */
+package org.jpype.ref;
+
+import java.util.ArrayList;
+
+/**
+ *
+ * @author nelson85
+ */
+public class JPypeReferenceSet
+{
+
+  static final int SIZE = 256;
+  ArrayList<Pool> pools = new ArrayList<>();
+  Pool current;
+  private int items;
+
+  JPypeReferenceSet()
+  {
+  }
+
+  int size()
+  {
+    return items;
+  }
+
+  /**
+   * Add a reference to the set.
+   *
+   * This should be O(1).
+   *
+   * @param ref
+   */
+  synchronized void add(JPypeReference ref)
+  {
+    if (ref.cleanup == 0)
+      return;
+
+    this.items++;
+    if (current == null)
+    {
+      current = new Pool(pools.size());
+      pools.add(current);
+    }
+
+    if (current.add(ref))
+    {
+      // It is full
+      current = null;
+
+      // Find a free pool
+      for (Pool pool : pools)
+      {
+        if (pool.tail < SIZE)
+        {
+          current = pool;
+          return;
+        }
+      }
+    }
+  }
+
+  /**
+   * Remove a reference from the set.
+   *
+   * @param ref
+   */
+  synchronized void remove(JPypeReference ref)
+  {
+    if (ref.cleanup == 0)
+      return;
+    pools.get(ref.pool).remove(ref);
+    this.items--;
+    ref.cleanup = 0;
+    ref.pool = -1;
+  }
+
+  /**
+   * Release all resources.
+   *
+   * This is triggered by shutdown to release an current Python references that
+   * are being held.
+   *
+   */
+  void flush()
+  {
+    for (Pool pool : pools)
+    {
+      for (int i = 0; i < pool.tail; ++i)
+      {
+        JPypeReference ref = pool.entries[i];
+        long hostRef = ref.hostReference;
+        long cleanup = ref.cleanup;
+        // This is a sanity check to prevent calling a cleanup with a null
+        // pointer, it would only occur if we failed to manage a deleted
+        // item.
+        if (cleanup == 0)
+          continue;
+        ref.cleanup = 0;
+        JPypeReferenceNative.removeHostReference(hostRef, cleanup);
+      }
+      pool.tail = 0;
+    }
+  }
+
+//<editor-fold desc="internal" defaultstate="collapsed">
+  static class Pool
+  {
+
+    JPypeReference[] entries = new JPypeReference[SIZE];
+    int tail;
+    int id;
+
+    Pool(int id)
+    {
+      this.id = id;
+    }
+
+    boolean add(JPypeReference ref)
+    {
+      ref.pool = id;
+      ref.index = tail;
+      entries[tail++] = ref;
+      return (tail == entries.length);
+    }
+
+    void remove(JPypeReference ref)
+    {
+      entries[ref.index] = entries[--tail];
+      entries[ref.index].index = ref.index;
+    }
+  }
+//</editor-fold>
+}
diff -pruN 1.5.0-1/native/python/include/pyjp.h 1.6.0-1/native/python/include/pyjp.h
--- 1.5.0-1/native/python/include/pyjp.h	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/include/pyjp.h	2025-06-01 03:57:43.000000000 +0000
@@ -108,6 +108,7 @@ struct PyJPProxy
 	PyObject_HEAD
 	JPProxy* m_Proxy;
 	PyObject* m_Target;
+	PyObject* m_Dispatch;
 	bool m_Convert;
 } ;
 
@@ -178,13 +179,14 @@ JPValue   *PyJPValue_getJavaSlot(PyObjec
 PyObject  *PyJPModule_getClass(PyObject* module, PyObject *obj);
 PyObject  *PyJPValue_getattro(PyObject *obj, PyObject *name);
 int        PyJPValue_setattro(PyObject *self, PyObject *name, PyObject *value);
-void       PyJPClass_hook(JPJavaFrame &frame, JPClass* cls);
 PyObject  *PyJPChar_Create(PyTypeObject *type, Py_UCS2 p);
 
 #ifdef __cplusplus
 }
 #endif
 
+void       PyJPClass_hook(JPJavaFrame &frame, JPClass* cls);
+
 // C++ methods
 JPPyObject PyJPArray_create(JPJavaFrame &frame, PyTypeObject* wrapper, const JPValue& value);
 JPPyObject PyJPBuffer_create(JPJavaFrame &frame, PyTypeObject *type, const JPValue & value);
diff -pruN 1.5.0-1/native/python/jp_pythontypes.cpp 1.6.0-1/native/python/jp_pythontypes.cpp
--- 1.5.0-1/native/python/jp_pythontypes.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/jp_pythontypes.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -22,7 +22,8 @@
 
 static void assertValid(PyObject *obj)
 {
-	if (obj->ob_refcnt >= 1)
+#ifndef Py_GIL_DISABLED
+	if (Py_REFCNT(obj) >= 1)
 		return;
 
 	// GCOVR_EXCL_START
@@ -34,6 +35,9 @@ static void assertValid(PyObject *obj)
 	JP_TRACE_PY("pyref FAULT", obj);
 	JP_RAISE(PyExc_SystemError, "Deleted reference");
 	// GCOVR_EXCL_STOP
+#else
+    return; // GIL is disabled; we assume obj is valid
+#endif
 }
 
 /**
@@ -483,7 +487,7 @@ void JPPyErrFrame::normalize()
 	// we have forced it to realize the exception.
 	if (!PyExceptionInstance_Check(m_ExceptionValue.get()))
 	{
-		JPPyObject args = JPPyObject::call(PyTuple_Pack(1, m_ExceptionValue.get()));
+		JPPyObject args = JPPyTuple_Pack(m_ExceptionValue.get());
 		m_ExceptionValue = JPPyObject::call(PyObject_Call(m_ExceptionClass.get(), args.get(), nullptr));
 		PyException_SetTraceback(m_ExceptionValue.get(), m_ExceptionTrace.get());
 		JPPyErr::restore(m_ExceptionClass, m_ExceptionValue, m_ExceptionTrace);
diff -pruN 1.5.0-1/native/python/pyjp_array.cpp 1.6.0-1/native/python/pyjp_array.cpp
--- 1.5.0-1/native/python/pyjp_array.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_array.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -47,8 +47,6 @@ static PyObject *PyJPArray_new(PyTypeObj
 static int PyJPArray_init(PyObject *self, PyObject *args, PyObject *kwargs)
 {
 	JP_PY_TRY("PyJPArray_init");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 
 	// Cases here.
 	//  -  We got here with a JPValue
@@ -65,6 +63,7 @@ static int PyJPArray_init(PyObject *self
 	if (arrayClass == nullptr)
 		JP_RAISE(PyExc_TypeError, "Class must be array type");
 
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	JPValue *value = PyJPValue_getJavaSlot(v);
 	if (value != nullptr)
@@ -127,7 +126,7 @@ static PyObject *PyJPArray_repr(PyJPArra
 static Py_ssize_t PyJPArray_len(PyJPArray *self)
 {
 	JP_PY_TRY("PyJPArray_len");
-	PyJPModule_getContext();
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Array == nullptr)
 		JP_RAISE(PyExc_ValueError, "Null array"); // GCOVR_EXCL_LINE
 	return self->m_Array->getLength();
@@ -142,8 +141,7 @@ static PyObject* PyJPArray_length(PyJPAr
 static PyObject *PyJPArray_getItem(PyJPArray *self, PyObject *item)
 {
 	JP_PY_TRY("PyJPArray_getArrayItem");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Array == nullptr)
 		JP_RAISE(PyExc_ValueError, "Null array");
 
@@ -195,8 +193,7 @@ static PyObject *PyJPArray_getItem(PyJPA
 static int PyJPArray_assignSubscript(PyJPArray *self, PyObject *item, PyObject *value)
 {
 	JP_PY_TRY("PyJPArray_assignSubscript");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	// Verified with numpy that item deletion on immutable should
 	// be ValueError
 	if ( value == nullptr)
@@ -248,15 +245,12 @@ static void PyJPArray_releaseBuffer(PyJP
 {
 	JP_PY_TRY("PyJPArrayPrimitive_releaseBuffer");
 	JPContext* context = JPContext_global;
-	if (!context->isRunning())
+	if (context->isRunning())
 	{
-		delete self->m_View;
-		self->m_View = nullptr;
-		return;
-	}
-	JPJavaFrame frame = JPJavaFrame::outer(context);
-	if (self->m_View == nullptr || !self->m_View->unreference())
-		return;
+		JPJavaFrame frame = JPJavaFrame::outer();
+		if (self->m_View == nullptr || !self->m_View->unreference())
+			return;
+	}
 	delete self->m_View;
 	self->m_View = nullptr;
 	JP_PY_CATCH(); // GCOVR_EXCL_LINE
@@ -265,8 +259,7 @@ static void PyJPArray_releaseBuffer(PyJP
 int PyJPArray_getBuffer(PyJPArray *self, Py_buffer *view, int flags)
 {
 	JP_PY_TRY("PyJPArray_getBuffer");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Array == nullptr)
 		JP_RAISE(PyExc_ValueError, "Null array");
 
@@ -349,8 +342,7 @@ int PyJPArray_getBuffer(PyJPArray *self,
 int PyJPArrayPrimitive_getBuffer(PyJPArray *self, Py_buffer *view, int flags)
 {
 	JP_PY_TRY("PyJPArrayPrimitive_getBuffer");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Array == nullptr)
 		JP_RAISE(PyExc_ValueError, "Null array");
 	try
@@ -484,7 +476,7 @@ static PyType_Spec arrayPrimSpec = {
 
 void PyJPArray_initType(PyObject * module)
 {
-	JPPyObject tuple = JPPyObject::call(PyTuple_Pack(1, PyJPObject_Type));
+	JPPyObject tuple = JPPyTuple_Pack(PyJPObject_Type);
 	PyJPArray_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&arraySpec, tuple.get());
 	JP_PY_CHECK();
 #if PY_VERSION_HEX < 0x03090000
@@ -493,7 +485,7 @@ void PyJPArray_initType(PyObject * modul
 	PyModule_AddObject(module, "_JArray", (PyObject*) PyJPArray_Type);
 	JP_PY_CHECK();
 
-	tuple = JPPyObject::call(PyTuple_Pack(1, PyJPArray_Type));
+	tuple = JPPyTuple_Pack(PyJPArray_Type);
 	PyJPArrayPrimitive_Type = (PyTypeObject*)
 			PyJPClass_FromSpecWithBases(&arrayPrimSpec, tuple.get());
 #if PY_VERSION_HEX < 0x03090000
diff -pruN 1.5.0-1/native/python/pyjp_buffer.cpp 1.6.0-1/native/python/pyjp_buffer.cpp
--- 1.5.0-1/native/python/pyjp_buffer.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_buffer.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -53,8 +53,7 @@ static void PyJPBuffer_releaseBuffer(PyJ
 int PyJPBuffer_getBuffer(PyJPBuffer *self, Py_buffer *view, int flags)
 {
 	JP_PY_TRY("PyJPBufferPrimitive_getBuffer");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Buffer == nullptr)
 		JP_RAISE(PyExc_ValueError, "Null buffer"); // GCOVR_EXCL_LINE
 	try
@@ -143,7 +142,7 @@ static PyType_Spec bufferSpec = {
 
 void PyJPBuffer_initType(PyObject * module)
 {
-	JPPyObject tuple = JPPyObject::call(PyTuple_Pack(1, PyJPObject_Type));
+	JPPyObject tuple = JPPyTuple_Pack(PyJPObject_Type);
 	PyJPBuffer_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&bufferSpec, tuple.get());
 #if PY_VERSION_HEX < 0x03090000
 	PyJPBuffer_Type->tp_as_buffer = &directBuffer;
diff -pruN 1.5.0-1/native/python/pyjp_char.cpp 1.6.0-1/native/python/pyjp_char.cpp
--- 1.5.0-1/native/python/pyjp_char.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_char.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -91,7 +91,7 @@ PyObject *PyJPChar_Create(PyTypeObject *
 	self->m_Data[3] = 0;
 
 	// Values taken from internal/cpython/unicode.h
-	
+
 	// Mark the type in unicode
 	_PyUnicode_LENGTH(self) = 1;
 	_PyUnicode_HASH(self) = -1;
@@ -120,7 +120,7 @@ PyObject *PyJPChar_Create(PyTypeObject *
 		char *data = (char*) ( ((PyCompactUnicodeObject*) self) + 1);
 		data[0] = (char) p;
 		data[1] = 0;
-	
+
 #if PY_VERSION_HEX < 0x030c0000
 		_PyUnicode_WSTR_LENGTH(self) = 0;
 		_PyUnicode_WSTR(self) = nullptr;
@@ -157,6 +157,7 @@ PyObject *PyJPChar_Create(PyTypeObject *
  */
 Py_UCS2 fromJPValue(const JPValue & value)
 {
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClass* cls = value.getClass();
 	if (cls->isPrimitive())
 		return (Py_UCS2) (value.getValue().c);
@@ -164,7 +165,7 @@ Py_UCS2 fromJPValue(const JPValue & valu
 	if (value.getValue().l == nullptr)
 		return (Py_UCS2) - 1;
 	else
-		return (Py_UCS2) (pcls->getValueFromObject(value).getValue().c);
+		return (Py_UCS2) (pcls->getValueFromObject(frame, value).getValue().c);
 }
 
 /** Get the value of the char.  Does not touch Java.
@@ -196,10 +197,7 @@ static PyObject * PyJPChar_new(PyTypeObj
 		return nullptr;
 	}  // GCOVR_EXCL_STOP
 
-	JPContext *context = PyJPModule_getContext();
-
 	// Create an instance (this may fail)
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	if (PyTuple_Size(pyargs) != 1)
 	{
 		PyErr_SetString(PyExc_TypeError, "Java chars require one argument");
@@ -209,10 +207,12 @@ static PyObject * PyJPChar_new(PyTypeObj
 	JPValue jv;
 	PyObject *in = PyTuple_GetItem(pyargs, 0);
 	Py_UCS4 cv = ord(in);
+
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (cv != (Py_UCS4) - 1)
 	{
 		JPPyObject v = JPPyObject::call(PyLong_FromLong(cv));
-		JPPyObject args0 = JPPyObject::call(PyTuple_Pack(1, v.get()));
+		JPPyObject args0 = JPPyTuple_Pack(v.get());
 		JPPyObjectVector args(args0.get());
 		jv = cls->newInstance(frame, args);
 	} else if (PyIndex_Check(in))
@@ -222,7 +222,7 @@ static PyObject * PyJPChar_new(PyTypeObj
 	} else if (PyFloat_Check(in))
 	{
 		JPPyObject v = JPPyObject::call(PyNumber_Long(in));
-		JPPyObject args0 = JPPyObject::call(PyTuple_Pack(1, v.get()));
+		JPPyObject args0 = JPPyTuple_Pack(v.get());
 		JPPyObjectVector args(args0.get());
 		jv = cls->newInstance(frame, args);
 	} else
@@ -242,7 +242,6 @@ static PyObject * PyJPChar_new(PyTypeObj
 static PyObject *PyJPChar_str(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_str");
-	PyJPModule_getContext(); // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (javaSlot == nullptr)
 	{  // GCOVR_EXCL_START
@@ -259,7 +258,6 @@ static PyObject *PyJPChar_str(PyJPChar *
 static PyObject *PyJPChar_repr(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_repr");
-	PyJPModule_getContext(); // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (javaSlot == nullptr)
 	{  // GCOVR_EXCL_START
@@ -276,7 +274,6 @@ static PyObject *PyJPChar_repr(PyJPChar
 static PyObject *PyJPChar_index(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_index");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (assertNotNull(javaSlot))
 		return nullptr;
@@ -287,7 +284,6 @@ static PyObject *PyJPChar_index(PyJPChar
 static PyObject *PyJPChar_float(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_float");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (assertNotNull(javaSlot))
 		return nullptr;
@@ -298,7 +294,6 @@ static PyObject *PyJPChar_float(PyJPChar
 static PyObject *PyJPChar_abs(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_nop");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (assertNotNull(javaSlot))
 		return nullptr;
@@ -312,7 +307,6 @@ static PyObject *PyJPChar_abs(PyJPChar *
 static Py_ssize_t PyJPChar_len(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_nop");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (assertNotNull(javaSlot))
 		return -1;
@@ -325,7 +319,7 @@ static PyObject *apply(PyObject *first,
 	JPValue *slot0 = PyJPValue_getJavaSlot(first);
 	JPValue *slot1 = PyJPValue_getJavaSlot(second);
 	if (slot0 != nullptr && slot1 != nullptr)
-	{	
+	{
 		if (assertNotNull(slot0))
 			return nullptr;
 		if (assertNotNull(slot1))
@@ -357,7 +351,6 @@ static PyObject *apply(PyObject *first,
 static  PyObject *PyJPChar_and(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_and");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_And);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -365,7 +358,6 @@ static  PyObject *PyJPChar_and(PyObject
 static  PyObject *PyJPChar_or(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_or");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_Or);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -373,7 +365,6 @@ static  PyObject *PyJPChar_or(PyObject *
 static  PyObject *PyJPChar_xor(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_xor");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_Xor);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -381,11 +372,10 @@ static  PyObject *PyJPChar_xor(PyObject
 static  PyObject *PyJPChar_add(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_add");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *slot0 = PyJPValue_getJavaSlot(first);
 	JPValue *slot1 = PyJPValue_getJavaSlot(second);
 	if (slot1 != nullptr && slot0 != nullptr)
-	{	
+	{
 		if (assertNotNull(slot0))
 			return nullptr;
 		if (assertNotNull(slot1))
@@ -424,7 +414,6 @@ static  PyObject *PyJPChar_add(PyObject
 static  PyObject *PyJPChar_subtract(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_subtract");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_Subtract);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -432,7 +421,6 @@ static  PyObject *PyJPChar_subtract(PyOb
 static  PyObject *PyJPChar_mult(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_mult");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_Multiply);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -440,7 +428,6 @@ static  PyObject *PyJPChar_mult(PyObject
 static  PyObject *PyJPChar_rshift(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_rshift");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_Rshift);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -448,7 +435,6 @@ static  PyObject *PyJPChar_rshift(PyObje
 static  PyObject *PyJPChar_lshift(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_lshift");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_Lshift);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -456,7 +442,6 @@ static  PyObject *PyJPChar_lshift(PyObje
 static  PyObject *PyJPChar_floordiv(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_floordiv");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_FloorDivide);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -464,7 +449,6 @@ static  PyObject *PyJPChar_floordiv(PyOb
 static  PyObject *PyJPChar_divmod(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_divmod");
-	PyJPModule_getContext();  // Check that JVM is running
 	return apply(first, second, PyNumber_Divmod);
 	JP_PY_CATCH(nullptr);  // GCOVR_EXCL_LINE
 }
@@ -472,7 +456,6 @@ static  PyObject *PyJPChar_divmod(PyObje
 static PyObject *PyJPChar_neg(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_neg");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (assertNotNull(javaSlot))
 		return nullptr;
@@ -485,7 +468,6 @@ static PyObject *PyJPChar_neg(PyJPChar *
 static PyObject *PyJPChar_pos(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPChar_pos");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (assertNotNull(javaSlot))
 		return nullptr;
@@ -498,7 +480,6 @@ static PyObject *PyJPChar_pos(PyJPChar *
 static PyObject *PyJPChar_inv(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPObject_neg");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (assertNotNull(javaSlot))
 		return nullptr;
@@ -511,7 +492,6 @@ static PyObject *PyJPChar_inv(PyJPChar *
 static PyObject *PyJPJChar_compare(PyObject *self, PyObject *other, int op)
 {
 	JP_PY_TRY("PyJPJChar_compare");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot1 = PyJPValue_getJavaSlot(other);
 	JPValue *javaSlot0 = PyJPValue_getJavaSlot(self);
 	if (isNull(javaSlot0))
@@ -576,7 +556,6 @@ static PyObject *PyJPJChar_compare(PyObj
 static Py_hash_t PyJPChar_hash(PyObject *self)
 {
 	JP_PY_TRY("PyJPObject_hash");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot(self);
 	if (isNull(javaSlot))
 		return Py_TYPE(Py_None)->tp_hash(Py_None);
@@ -587,7 +566,6 @@ static Py_hash_t PyJPChar_hash(PyObject
 static int PyJPChar_bool(PyJPChar *self)
 {
 	JP_PY_TRY("PyJPObject_bool");
-	PyJPModule_getContext();  // Check that JVM is running
 	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
 	if (isNull(javaSlot))
 		return 0;
@@ -597,12 +575,10 @@ static int PyJPChar_bool(PyJPChar *self)
 
 
 static PyMethodDef charMethods[] = {
-	//	{"thing", (PyCFunction) PyJPMethod_matchReport, METH_VARARGS, ""},
 	{nullptr},
 };
 
 struct PyGetSetDef charGetSet[] = {
-	//	{"thing", (getter) PyJPMethod_getSelf, NULL, NULL, NULL},
 	{nullptr},
 };
 
@@ -655,9 +631,8 @@ static PyType_Spec charSpec = {
 void PyJPChar_initType(PyObject* module)
 {
 	// We will inherit from str and JObject
-	PyObject *bases = PyTuple_Pack(2, &PyUnicode_Type, PyJPObject_Type);
-	PyJPChar_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&charSpec, bases);
-	Py_DECREF(bases);
+	JPPyObject bases = JPPyTuple_Pack(&PyUnicode_Type, PyJPObject_Type);
+	PyJPChar_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&charSpec, bases.get());
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 	PyModule_AddObject(module, "_JChar", (PyObject*) PyJPChar_Type);
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
diff -pruN 1.5.0-1/native/python/pyjp_class.cpp 1.6.0-1/native/python/pyjp_class.cpp
--- 1.5.0-1/native/python/pyjp_class.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_class.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -58,82 +58,18 @@ static int PyJPClass_clear(PyJPClass *se
 	return 0;
 }
 
-PyObject *PyJPClass_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
-{
-	JP_PY_TRY("PyJPClass_new");
-	if (PyTuple_Size(args) != 3)
-		JP_RAISE(PyExc_TypeError, "Java class meta required 3 arguments");
-
-	JP_BLOCK("PyJPClass_new::verify")
-	{
-		// Watch for final classes
-		PyObject *bases = PyTuple_GetItem(args, 1);
-		Py_ssize_t len = PyTuple_Size(bases);
-		for (Py_ssize_t i = 0; i < len; ++i)
-		{
-			PyObject *item = PyTuple_GetItem(bases, i);
-			JPClass *cls = PyJPClass_getJPClass(item);
-			if (cls != nullptr && cls->isFinal())
-			{
-				PyErr_Format(PyExc_TypeError, "Cannot extend final class '%s'",
-						((PyTypeObject*) item)->tp_name);
-			}
-		}
-	}
-
-	int magic = 0;
-	if (kwargs == PyJPClassMagic || (kwargs != nullptr && PyDict_GetItemString(kwargs, "internal") != nullptr))
-	{
-		magic = 1;
-		kwargs = nullptr;
-	}
-	if (magic == 0)
-	{
-		PyErr_Format(PyExc_TypeError, "Java classes cannot be extended in Python");
-		return nullptr;
-	}
-
-	auto *typenew = (PyTypeObject*) PyType_Type.tp_new(type, args, kwargs);
-
-	// GCOVR_EXCL_START
-	// Sanity checks.  Not testable
-	if (typenew == nullptr)
-		return nullptr;
-	if (typenew->tp_finalize != nullptr && typenew->tp_finalize != (destructor) PyJPValue_finalize)
-	{
-		Py_DECREF(typenew);
-		PyErr_SetString(PyExc_TypeError, "finalizer conflict");
-		return nullptr;
-	}
-
-	// This sanity check is trigger if the user attempts to build their own
-	// type wrapper with a __del__ method defined.  It is hard to trigger.
-	if (typenew->tp_alloc != (allocfunc) PyJPValue_alloc
-			&& typenew->tp_alloc != PyBaseObject_Type.tp_alloc)
-	{
-		Py_DECREF(typenew);
-		PyErr_SetString(PyExc_TypeError, "alloc conflict");
-		return nullptr;
-	}
-	// GCOVR_EXCL_STOP
-
-	typenew->tp_alloc = (allocfunc) PyJPValue_alloc;
-	typenew->tp_finalize = (destructor) PyJPValue_finalize;
-
-	if (PyObject_IsSubclass((PyObject*) typenew, (PyObject*) PyJPException_Type))
-	{
-		typenew->tp_new = PyJPException_Type->tp_new;
-	}
-	((PyJPClass*) typenew)->m_Doc = nullptr;
-	return (PyObject*) typenew;
-	JP_PY_CATCH(nullptr);
-}
-
 PyObject* examine(PyObject *module, PyObject *other);
 
 PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
 {
 	JP_PY_TRY("PyJPClass_FromSpecWithBases");
+#if PY_VERSION_HEX>=0x030c0000
+	// Starting in Python 3.12 there is a function for creating from a meta class
+	// that replaces this madeness.
+	PyTypeObject *type = (PyTypeObject*) PyType_FromMetaclass((PyTypeObject*) PyJPClass_Type, NULL, spec, bases);
+	if (type == nullptr)
+		return (PyObject*) type;
+#else
 	// Python lacks a FromSpecWithMeta so we are going to have to fake it here.
 	auto* type = (PyTypeObject*) PyJPClass_Type->tp_alloc(PyJPClass_Type, 0);
 	auto* heap = (PyHeapTypeObject*) type;
@@ -148,7 +84,10 @@ PyObject* PyJPClass_FromSpecWithBases(Py
 	heap->ht_name = heap->ht_qualname;
 	Py_INCREF(heap->ht_name);
 	if (bases == nullptr)
+	{
+		// do NOT use JPPyTuple_Pack here
 		type->tp_bases = PyTuple_Pack(1, (PyObject*) & PyBaseObject_Type);
+	}
 	else
 	{
 		type->tp_bases = bases;
@@ -174,6 +113,12 @@ PyObject* PyJPClass_FromSpecWithBases(Py
 	{
 		switch (slot->slot)
 		{
+			case Py_tp_finalize:
+				type->tp_finalize = (destructor) slot->pfunc;
+				break;
+			case Py_tp_alloc:
+				type->tp_alloc = (allocfunc) slot->pfunc;
+				break;
 			case Py_tp_free:
 				type->tp_free = (freefunc) slot->pfunc;
 				break;
@@ -293,7 +238,7 @@ PyObject* PyJPClass_FromSpecWithBases(Py
 	}
 
 	// GC objects are required to implement clear and traverse, this is a
-	// safety check to make sure we implemented all properly.   This error should
+	// safety check to make sure we implemented all properly.	This error should
 	// never happen in production code.
 	if (PyType_IS_GC(type) && (
 			type->tp_traverse==nullptr ||
@@ -302,6 +247,13 @@ PyObject* PyJPClass_FromSpecWithBases(Py
 		PyErr_Format(PyExc_TypeError, "GC requirements failed for %s", spec->name);
 		JP_RAISE_PYTHON();
 	}
+
+#endif
+
+	// Make sure our memory model is used
+	type->tp_alloc = (allocfunc) PyJPValue_alloc;
+	type->tp_finalize = (destructor) PyJPValue_finalize;
+
 	PyType_Ready(type);
 	PyDict_SetItemString(type->tp_dict, "__module__", PyUnicode_FromString("_jpype"));
 	return (PyObject*) type;
@@ -311,8 +263,34 @@ PyObject* PyJPClass_FromSpecWithBases(Py
 int PyJPClass_init(PyObject *self, PyObject *args, PyObject *kwargs)
 {
 	JP_PY_TRY("PyJPClass_init");
-	if (PyTuple_Size(args) == 1)
-		return 0;
+
+	if (!PyObject_IsInstance(self, (PyObject*) PyJPClass_Type))
+	{
+		PyErr_SetString(PyExc_TypeError, "Type incorrect");
+		return -1;
+	}
+
+	PyTypeObject *type = (PyTypeObject*) self;
+
+#if PY_VERSION_HEX >= 0x030d0000
+	// Python 3.13 - This flag will try to place the dictionary are part of the object which 
+	// adds an unknown number of bytes to the end of the object making it impossible
+	// to attach our needed data.  If we kill the flag then we get usable behavior.
+	type->tp_flags &= ~Py_TPFLAGS_INLINE_VALUES;
+#endif
+
+	// Verify that we were called internally
+	int magic = 0;
+	if (kwargs == PyJPClassMagic || (kwargs != nullptr && PyDict_GetItemString(kwargs, "internal") != nullptr))
+	{
+		magic = 1;
+		kwargs = nullptr;
+	}
+	if (magic == 0)
+	{
+		PyErr_Format(PyExc_TypeError, "Java classes cannot be extended in Python");
+		return -1;
+	}
 
 	// Set the host object
 	PyObject *name = nullptr;
@@ -327,20 +305,61 @@ int PyJPClass_init(PyObject *self, PyObj
 		PyErr_SetString(PyExc_TypeError, "Bases must be a tuple");
 		return -1;
 	}
-	for (int i = 0; i < PyTuple_Size(bases); ++i)
+
+	JP_BLOCK("PyJPClass_new::verify")
 	{
-		if (!PyJPClass_Check(PyTuple_GetItem(bases, i)))
+		// Watch for final classes
+		Py_ssize_t len = PyTuple_Size(bases);
+		for (Py_ssize_t i = 0; i < len; ++i)
 		{
-			PyErr_SetString(PyExc_TypeError, "All bases must be Java types");
-			return -1;
+			PyObject *item = PyTuple_GetItem(bases, i);
+			JPClass *cls = PyJPClass_getJPClass(item);
+			if (cls != nullptr && cls->isFinal())
+			{
+				PyErr_Format(PyExc_TypeError, "Cannot extend final class '%s'",
+						((PyTypeObject*) item)->tp_name);
+			}
 		}
 	}
 
+	// We must make sure that all classes have our allocator
+	type->tp_alloc = (allocfunc) PyJPValue_alloc;
+	type->tp_finalize = (destructor) PyJPValue_finalize;
+	((PyJPClass*) self)->m_Doc = nullptr;
+
 	// Call the type init
 	int rc = PyType_Type.tp_init(self, args, nullptr);
 	if (rc == -1)
 		return rc; // GCOVR_EXCL_LINE no clue how to trigger this one
 
+	// GCOVR_EXCL_START
+	// Sanity checks.  Not testable
+	if (type == nullptr)
+		return -1;
+	if (type->tp_finalize != nullptr && type->tp_finalize != (destructor) PyJPValue_finalize)
+	{
+		PyErr_SetString(PyExc_TypeError, "finalizer conflict");
+		return -1;
+	}
+
+	// This sanity check is trigger if the user attempts to build their own
+	// type wrapper with a __del__ method defined.	It is hard to trigger.
+	if (type->tp_alloc != (allocfunc) PyJPValue_alloc
+			&& type->tp_alloc != PyBaseObject_Type.tp_alloc)
+	{
+		PyErr_SetString(PyExc_TypeError, "alloc conflict");
+		return -1;
+	}
+	// GCOVR_EXCL_STOP
+
+#if PY_VERSION_HEX < 0x03090000
+	// This was required at one point but I don't know what version it applied to.
+	if (PyObject_IsSubclass((PyObject*) type, (PyObject*) PyJPException_Type))
+	{
+		type->tp_new = PyJPException_Type->tp_new;
+	}
+#endif
+
 	return rc;
 	JP_PY_CATCH(-1);
 }
@@ -504,8 +523,7 @@ PyObject* PyJPClass_subclasscheck(PyType
 	}
 	// GCOVR_EXCL_STOP
 
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	// Check for class inheritance first
 	JPClass *testClass = PyJPClass_getJPClass((PyObject*) test);
@@ -546,8 +564,7 @@ PyObject* PyJPClass_subclasscheck(PyType
 static PyObject *PyJPClass_class(PyObject *self, PyObject *closure)
 {
 	JP_PY_TRY("PyJPClass_class");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue* javaSlot = PyJPValue_getJavaSlot(self);
 	if (javaSlot == nullptr)
 	{
@@ -561,8 +578,8 @@ static PyObject *PyJPClass_class(PyObjec
 static int PyJPClass_setClass(PyObject *self, PyObject *type, PyObject *closure)
 {
 	JP_PY_TRY("PyJPClass_setClass", self);
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
+	JPContext *context = frame.getContext();
 	JPValue* javaSlot = PyJPValue_getJavaSlot(type);
 	if (javaSlot == nullptr || javaSlot->getClass() != context->_java_lang_Class)
 	{
@@ -642,15 +659,13 @@ PyObject* PyJPClass_instancecheck(PyType
 	// JInterface is a meta
 	if ((PyObject*) self == _JInterface)
 	{
-		JPContext *context = PyJPModule_getContext();
-		JPJavaFrame frame = JPJavaFrame::outer(context);
+		JPJavaFrame frame = JPJavaFrame::outer();
 		JPClass *testClass = PyJPClass_getJPClass((PyObject*) test);
 		return PyBool_FromLong(testClass != nullptr && testClass->isInterface());
 	}
 	if ((PyObject*) self == _JException)
 	{
-		JPContext *context = PyJPModule_getContext();
-		JPJavaFrame frame = JPJavaFrame::outer(context);
+		JPJavaFrame frame = JPJavaFrame::outer();
 		JPClass *testClass = PyJPClass_getJPClass((PyObject*) test);
 		if (testClass)
 			return PyBool_FromLong(testClass->isThrowable());
@@ -661,8 +676,7 @@ PyObject* PyJPClass_instancecheck(PyType
 static PyObject *PyJPClass_canCast(PyJPClass *self, PyObject *other)
 {
 	JP_PY_TRY("PyJPClass_canCast");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	JPClass *cls = self->m_Class;
 
@@ -679,8 +693,7 @@ static PyObject *PyJPClass_canCast(PyJPC
 static PyObject *PyJPClass_canConvertToJava(PyJPClass *self, PyObject *other)
 {
 	JP_PY_TRY("PyJPClass_canConvertToJava");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	JPClass *cls = self->m_Class;
 
@@ -723,13 +736,20 @@ static bool PySlice_CheckFull(PyObject *
 static PyObject *PyJPClass_array(PyJPClass *self, PyObject *item)
 {
 	JP_PY_TRY("PyJPClass_array");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
+	JPContext *context = frame.getContext();
 
 	if (self->m_Class == NULL)
 	{
-		PyErr_Format(PyExc_TypeError, "Cannot instantiate unspecified array type");
-		return NULL;
+		// This is only reachable through an internal Jpype type that doesn't have a
+		// Java class equivalent such as JArray.
+		// __getitem__ takes precedence over __class_getitem__ which forces us through
+		// this path.
+		// If __class_getitem__ is not implemented in this class, the raised AttributeError
+		// will make its way back to Python.
+		PyObject *res = PyObject_CallMethod((PyObject *)self, "__class_getitem__", "O", item);
+		Py_DECREF(item);
+		return res;
 	}
 
 	if (PyIndex_Check(item))
@@ -811,8 +831,7 @@ static PyObject *PyJPClass_array(PyJPCla
 static PyObject *PyJPClass_cast(PyJPClass *self, PyObject *other)
 {
 	JP_PY_TRY("PyJPClass_cast");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClass *type = self->m_Class;
 	JPValue *val = PyJPValue_getJavaSlot(other);
 
@@ -867,7 +886,6 @@ static PyObject *PyJPClass_cast(PyJPClas
 		auto *array = (PyJPArray*) other;
 		if (array->m_Array->isSlice())
 		{
-			JPJavaFrame frame = JPJavaFrame::outer(context);
 			jvalue v;
 			v.l = array->m_Array->clone(frame, other);
 			return type->convertToPythonObject(frame, v, true).keep();
@@ -891,8 +909,7 @@ static PyObject *PyJPClass_castEq(PyJPCl
 static PyObject *PyJPClass_convertToJava(PyJPClass *self, PyObject *other)
 {
 	JP_PY_TRY("PyJPClass_convertToJava");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	JPClass *cls = self->m_Class;
 
@@ -924,8 +941,7 @@ static PyObject *PyJPClass_repr(PyJPClas
 static PyObject *PyJPClass_getDoc(PyJPClass *self, void *ctxt)
 {
 	JP_PY_TRY("PyJPMethod_getDoc");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Doc)
 	{
 		Py_INCREF(self->m_Doc);
@@ -935,7 +951,7 @@ static PyObject *PyJPClass_getDoc(PyJPCl
 	// Pack the arguments
 	{
 		JP_TRACE("Pack arguments");
-		JPPyObject args = JPPyObject::call(PyTuple_Pack(1, self));
+		JPPyObject args = JPPyTuple_Pack(self);
 		JP_TRACE("Call Python");
 		self->m_Doc = PyObject_Call(_JClassDoc, args.get(), nullptr);
 		Py_XINCREF(self->m_Doc);
@@ -990,7 +1006,6 @@ static PyGetSetDef classGetSets[] = {
 static PyType_Slot classSlots[] = {
 	{ Py_tp_alloc, (void*) PyJPValue_alloc},
 	{ Py_tp_finalize, (void*) PyJPValue_finalize},
-	{ Py_tp_new, (void*) PyJPClass_new},
 	{ Py_tp_init, (void*) PyJPClass_init},
 	{ Py_tp_dealloc, (void*) PyJPClass_dealloc},
 	{ Py_tp_traverse, (void*) PyJPClass_traverse},
@@ -1021,9 +1036,8 @@ static PyType_Spec classSpec = {
 
 void PyJPClass_initType(PyObject* module)
 {
-	PyObject *bases = PyTuple_Pack(1, &PyType_Type);
-	PyJPClass_Type = (PyTypeObject*) PyType_FromSpecWithBases(&classSpec, bases);
-	Py_DECREF(bases);
+	JPPyObject bases = JPPyTuple_Pack(&PyType_Type);
+	PyJPClass_Type = (PyTypeObject*) PyType_FromSpecWithBases(&classSpec, bases.get());
 	JP_PY_CHECK();
 	PyModule_AddObject(module, "_JClass", (PyObject*) PyJPClass_Type);
 	JP_PY_CHECK();
@@ -1041,9 +1055,9 @@ JPClass* PyJPClass_getJPClass(PyObject*
 		if (javaSlot == nullptr)
 			return nullptr;
 		JPClass *cls = javaSlot->getClass();
-		if (cls != cls->getContext()->_java_lang_Class)
+		JPJavaFrame frame = JPJavaFrame::outer();
+		if (cls != frame.getContext()->_java_lang_Class)
 			return nullptr;
-		JPJavaFrame frame = JPJavaFrame::outer(cls->getContext());
 		return frame.findClass((jclass) javaSlot->getJavaObject());
 	} catch (...) // GCOVR_EXCL_LINE
 	{
@@ -1059,7 +1073,7 @@ JPPyObject PyJPClass_getBases(JPJavaFram
 
 	// Decide the base for this object
 	JPPyObject baseType;
-	JPContext *context = PyJPModule_getContext();
+	JPContext *context = frame.getContext();
 	JPClass *super = cls->getSuperClass();
 	if (dynamic_cast<JPBoxedType*> (cls) == cls)
 	{
@@ -1130,7 +1144,7 @@ JPPyObject PyJPClass_getBases(JPJavaFram
  * Internal method for wrapping a returned Java class instance.
  *
  * This checks the cache for existing wrappers and then
- * transfers control to JClassFactory.  This is required because all of
+ * transfers control to JClassFactory.	This is required because all of
  * the post load stuff needs to be in Python.
  *
  * @param cls
@@ -1160,10 +1174,10 @@ void PyJPClass_hook(JPJavaFrame &frame,
 
 
 	JPPyObject members = JPPyObject::call(PyDict_New());
-	JPPyObject args = JPPyObject::call(PyTuple_Pack(3,
+	JPPyObject args = JPPyTuple_Pack(
 			JPPyString::fromStringUTF8(cls->getCanonicalName()).get(),
 			PyJPClass_getBases(frame, cls).get(),
-			members.get()));
+			members.get());
 
 	// Catch creation loop,  the process of creating our parent
 	host = (PyObject*) cls->getHost();
@@ -1201,7 +1215,7 @@ void PyJPClass_hook(JPJavaFrame &frame,
 
 	JP_TRACE("type new");
 	// Create the type using the meta class magic
-	JPPyObject vself = JPPyObject::call(PyJPClass_Type->tp_new(PyJPClass_Type, rc.get(), PyJPClassMagic));
+	JPPyObject vself = JPPyObject::call(PyJPClass_Type->tp_call((PyObject*) PyJPClass_Type, rc.get(), PyJPClassMagic));
 	auto *self = (PyJPClass*) vself.get();
 
 	// Attach the javaSlot
@@ -1216,6 +1230,6 @@ void PyJPClass_hook(JPJavaFrame &frame,
 
 	// Call the post load routine to attach inner classes
 	JP_TRACE("call post");
-	args = JPPyObject::call(PyTuple_Pack(1, self));
+	args = JPPyTuple_Pack(self);
 	JPPyObject rc2 = JPPyObject::call(PyObject_Call(_JClassPost, args.get(), nullptr));
 }
diff -pruN 1.5.0-1/native/python/pyjp_field.cpp 1.6.0-1/native/python/pyjp_field.cpp
--- 1.5.0-1/native/python/pyjp_field.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_field.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -37,8 +37,7 @@ static void PyJPField_dealloc(PyJPField
 static PyObject *PyJPField_get(PyJPField *self, PyObject *obj, PyObject *type)
 {
 	JP_PY_TRY("PyJPField_get");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	// Clear any pending interrupts if we are on the main thread.
 	if (hasInterrupt())
 		frame.clearInterrupt(false);
@@ -57,8 +56,7 @@ static PyObject *PyJPField_get(PyJPField
 static int PyJPField_set(PyJPField *self, PyObject *obj, PyObject *pyvalue)
 {
 	JP_PY_TRY("PyJPField_set");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Field->isFinal())
 	{
 		PyErr_SetString(PyExc_AttributeError, "Field is final");
@@ -88,8 +86,7 @@ static int PyJPField_set(PyJPField *self
 static PyObject *PyJPField_repr(PyJPField *self)
 {
 	JP_PY_TRY("PyJPField_repr");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	return PyUnicode_FromFormat("<java field '%s' of '%s'>",
 			self->m_Field->getName().c_str(),
 			self->m_Field->getClass()->getCanonicalName().c_str()
diff -pruN 1.5.0-1/native/python/pyjp_method.cpp 1.6.0-1/native/python/pyjp_method.cpp
--- 1.5.0-1/native/python/pyjp_method.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_method.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -90,8 +90,7 @@ static PyObject *PyJPMethod_get(PyJPMeth
 static PyObject *PyJPMethod_call(PyJPMethod *self, PyObject *args, PyObject *kwargs)
 {
 	JP_PY_TRY("PyJPMethod_call");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JP_TRACE(self->m_Method->getName());
 	// Clear any pending interrupts if we are on the main thread
 	if (hasInterrupt())
@@ -113,8 +112,7 @@ static PyObject *PyJPMethod_call(PyJPMet
 static PyObject *PyJPMethod_matches(PyJPMethod *self, PyObject *args, PyObject *kwargs)
 {
 	JP_PY_TRY("PyJPMethod_matches");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JP_TRACE(self->m_Method->getName());
 	if (self->m_Instance == nullptr)
 	{
@@ -131,8 +129,7 @@ static PyObject *PyJPMethod_matches(PyJP
 static PyObject *PyJPMethod_str(PyJPMethod *self)
 {
 	JP_PY_TRY("PyJPMethod_str");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	return PyUnicode_FromFormat("%s.%s",
 			self->m_Method->getClass()->getCanonicalName().c_str(),
 			self->m_Method->getName().c_str());
@@ -188,7 +185,7 @@ static PyObject *PyJPMethod_getDoc(PyJPM
 {
 	JP_PY_TRY("PyJPMethod_getDoc");
 	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Doc)
 	{
 		Py_INCREF(self->m_Doc);
@@ -216,8 +213,7 @@ static PyObject *PyJPMethod_getDoc(PyJPM
 		jvalue v;
 		v.l = (jobject) self->m_Method->getClass()->getJavaClass();
 		JPPyObject obj(context->_java_lang_Class->convertToPythonObject(frame, v, true));
-		JPPyObject args = JPPyObject::call(PyTuple_Pack(3,
-				self, obj.get(), ov.get()));
+		JPPyObject args = JPPyTuple_Pack(self, obj.get(), ov.get());
 		JP_TRACE("Call Python");
 		self->m_Doc = PyObject_Call(_JMethodDoc, args.get(), nullptr);
 		Py_XINCREF(self->m_Doc);
@@ -240,7 +236,7 @@ PyObject *PyJPMethod_getAnnotations(PyJP
 {
 	JP_PY_TRY("PyJPMethod_getAnnotations");
 	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (self->m_Annotations)
 	{
 		Py_INCREF(self->m_Annotations);
@@ -268,8 +264,7 @@ PyObject *PyJPMethod_getAnnotations(PyJP
 		jvalue v;
 		v.l = (jobject) self->m_Method->getClass()->getJavaClass();
 		JPPyObject obj(context->_java_lang_Class->convertToPythonObject(frame, v, true));
-		JPPyObject args = JPPyObject::call(PyTuple_Pack(3,
-				self, obj.get(), ov.get()));
+		JPPyObject args = JPPyTuple_Pack(self, obj.get(), ov.get());
 		JP_TRACE("Call Python");
 		self->m_Annotations = PyObject_Call(_JMethodAnnotations, args.get(), nullptr);
 	}
@@ -293,7 +288,7 @@ PyObject *PyJPMethod_getCodeAttr(PyJPMet
 	PyJPModule_getContext();
 	if (self->m_CodeRep == nullptr)
 	{
-		JPPyObject args = JPPyObject::call(PyTuple_Pack(1, self));
+		JPPyObject args = JPPyTuple_Pack(self);
 		JP_TRACE("Call Python");
 		self->m_CodeRep = PyObject_Call(_JMethodCode, args.get(), nullptr);
 	}
@@ -396,7 +391,7 @@ void PyJPMethod_initType(PyObject* modul
 	// We inherit from PyFunction_Type just so we are an instance
 	// for purposes of inspect and tab completion tools.  But
 	// we will just ignore their memory layout as we have our own.
-	JPPyObject tuple = JPPyObject::call(PyTuple_Pack(1, &PyFunction_Type));
+	JPPyObject tuple = JPPyTuple_Pack(&PyFunction_Type);
 	unsigned long flags = PyFunction_Type.tp_flags;
 	PyFunction_Type.tp_flags |= Py_TPFLAGS_BASETYPE;
 	PyJPMethod_Type = (PyTypeObject*) PyType_FromSpecWithBases(&methodSpec, tuple.get());
diff -pruN 1.5.0-1/native/python/pyjp_module.cpp 1.6.0-1/native/python/pyjp_module.cpp
--- 1.5.0-1/native/python/pyjp_module.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_module.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -20,6 +20,9 @@
 #include "jp_gc.h"
 #include "jp_stringtype.h"
 #include "jp_classloader.h"
+#ifdef WIN32
+#include <Windows.h>
+#endif
 
 void PyJPModule_installGC(PyObject* module);
 
@@ -37,6 +40,7 @@ extern void PyJPNumber_initType(PyObject
 extern void PyJPClassHints_initType(PyObject* module);
 extern void PyJPPackage_initType(PyObject* module);
 extern void PyJPChar_initType(PyObject* module);
+extern void PyJPValue_initType(PyObject* module);
 
 static PyObject *PyJPModule_convertBuffer(JPPyBuffer& buffer, PyObject *dtype);
 
@@ -179,7 +183,7 @@ PyObject* PyJP_GetAttrDescriptor(PyTypeO
 	for (Py_ssize_t i = 0; i < n; ++i)
 	{
 		auto *type2 = (PyTypeObject*) PyTuple_GetItem(mro, i);
-		
+
 		// Skip objects without a functioning dictionary
 		if (type2->tp_dict == NULL)
 			continue;
@@ -228,6 +232,7 @@ int PyJP_IsInstanceSingle(PyObject* obj,
 #ifndef ANDROID
 extern JNIEnv *Android_JNI_GetEnv();
 
+static string jarTmpPath;
 static PyObject* PyJPModule_startup(PyObject* module, PyObject* pyargs)
 {
 	JP_PY_TRY("PyJPModule_startup");
@@ -237,11 +242,23 @@ static PyObject* PyJPModule_startup(PyOb
 	char ignoreUnrecognized = true;
 	char convertStrings = false;
 	char interrupt = false;
+	PyObject* tmp;
 
-	if (!PyArg_ParseTuple(pyargs, "OO!bbb", &vmPath, &PyTuple_Type, &vmOpt,
-			&ignoreUnrecognized, &convertStrings, &interrupt))
+	if (!PyArg_ParseTuple(pyargs, "OO!bbbO", &vmPath, &PyTuple_Type, &vmOpt,
+			&ignoreUnrecognized, &convertStrings, &interrupt, &tmp))
 		return nullptr;
 
+	if (tmp != Py_None)
+	{
+		if (!(JPPyString::check(tmp)))
+		{
+			PyErr_SetString(PyExc_TypeError, "Java jar path must be a string");
+			return nullptr;
+		}
+		jarTmpPath = JPPyString::asStringUTF8(tmp);
+	}
+
+
 	if (!(JPPyString::check(vmPath)))
 	{
 		PyErr_SetString(PyExc_TypeError, "Java JVM path must be a string");
@@ -297,6 +314,18 @@ static PyObject* PyJPModule_shutdown(PyO
 		return nullptr;
 
 	JPContext_global->shutdownJVM(destroyJVM, freeJVM);
+
+#ifdef WIN32
+	// Thus far this doesn't work on WINDOWS.  The issue is a bug in the JVM
+	// is holding the file open and there is no apparent method to close it
+	// so that this can succeed
+	if (jarTmpPath != "")
+		remove(jarTmpPath.c_str());
+#else
+	if (jarTmpPath != "")
+		unlink(jarTmpPath.c_str());
+#endif
+
 	Py_RETURN_NONE;
 	JP_PY_CATCH(nullptr);
 }
@@ -355,22 +384,29 @@ static void releaseView(void* view)
 	}
 }
 
-static PyObject* PyJPModule_convertToDirectByteBuffer(PyObject* self, PyObject* src)
+static PyObject* PyJPModule_convertToDirectByteBuffer(PyObject* self, PyObject* args)
 {
 	JP_PY_TRY("PyJPModule_convertToDirectByteBuffer");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
+
+	PyObject *src;
+	int ro;
+	if (!PyArg_ParseTuple(args, "Op", &src, &ro))
+		return nullptr;
 
 	if (PyObject_CheckBuffer(src))
 	{
 		JPViewWrapper vw;
-		if (PyObject_GetBuffer(src, vw.view, PyBUF_WRITABLE) == -1)
+		if (PyObject_GetBuffer(src, vw.view, ro ? 0 : PyBUF_WRITABLE) == -1)
 			return nullptr;
 
 		// Create a byte buffer
 		jvalue v;
 		v.l = frame.NewDirectByteBuffer(vw.view->buf, vw.view->len);
 
+		if (vw.view->readonly)
+			v.l = frame.asReadOnlyBuffer(v.l);
+
 		// Bind lifespan of the view to the java object.
 		frame.registerRef(v.l, vw.view, &releaseView);
 		vw.view = nullptr;
@@ -390,8 +426,7 @@ static PyObject* PyJPModule_enableStackt
 PyObject *PyJPModule_newArrayType(PyObject *module, PyObject *args)
 {
 	JP_PY_TRY("PyJPModule_newArrayType");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	PyObject *type, *dims;
 	if (!PyArg_ParseTuple(args, "OO", &type, &dims))
@@ -417,8 +452,8 @@ PyObject *PyJPModule_newArrayType(PyObje
 PyObject *PyJPModule_getClass(PyObject* module, PyObject *obj)
 {
 	JP_PY_TRY("PyJPModule_getClass");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
+	JPContext *context = frame.getContext();
 
 	JPClass* cls;
 	if (JPPyString::check(obj))
@@ -456,8 +491,7 @@ PyObject *PyJPModule_hasClass(PyObject*
 	JP_PY_TRY("PyJPModule_hasClass");
 	if (!JPContext_global->isRunning())
 		Py_RETURN_FALSE; // GCOVR_EXCL_LINE
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	JPClass* cls;
 	if (JPPyString::check(obj))
@@ -569,8 +603,7 @@ static PyObject* PyJPModule_isPackage(Py
 		PyErr_Format(PyExc_TypeError, "isPackage required unicode");
 		return nullptr;
 	}
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	return PyBool_FromLong(frame.isPackage(JPPyString::asStringUTF8(pkg)));
 	JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE
 }
@@ -694,7 +727,7 @@ static PyMethodDef moduleMethods[] = {
 
 	//{"dumpJVMStats", (PyCFunction) (&PyJPModule_dumpJVMStats), METH_NOARGS, ""},
 
-	{"convertToDirectBuffer", (PyCFunction) PyJPModule_convertToDirectByteBuffer, METH_O, ""},
+	{"convertToDirectBuffer", (PyCFunction) PyJPModule_convertToDirectByteBuffer, METH_VARARGS, ""},
 	{"arrayFromBuffer", (PyCFunction) PyJPModule_arrayFromBuffer, METH_VARARGS, ""},
 	{"enableStacktraces", (PyCFunction) PyJPModule_enableStacktraces, METH_O, ""},
 	{"isPackage", (PyCFunction) PyJPModule_isPackage, METH_O, ""},
@@ -729,8 +762,11 @@ PyMODINIT_FUNC PyInit__jpype()
 	// PyJPModule = module;
 	Py_INCREF(module);
 	PyJPModule = module;
-	PyModule_AddStringConstant(module, "__version__", "1.5.0");
-	
+#ifdef Py_GIL_DISABLED
+    PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
+#endif
+	PyModule_AddStringConstant(module, "__version__", "1.6.0");
+
 	// Our module will be used for PyFrame object and it is a requirement that
 	// we have a builtins in our dictionary.
 	PyObject *builtins = PyEval_GetBuiltins();
@@ -739,6 +775,7 @@ PyMODINIT_FUNC PyInit__jpype()
 
 	PyJPClassMagic = PyDict_New();
 	// Initialize each of the python extension types
+	PyJPValue_initType(module);
 	PyJPClass_initType(module);
 	PyJPObject_initType(module);
 
@@ -790,7 +827,7 @@ void PyJPModule_rethrow(const JPStackInf
 static PyObject *PyJPModule_convertBuffer(JPPyBuffer& buffer, PyObject *dtype)
 {
 	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	Py_buffer& view = buffer.getView();
 
 	// Okay two possibilities here.  We have a valid dtype specified,
diff -pruN 1.5.0-1/native/python/pyjp_monitor.cpp 1.6.0-1/native/python/pyjp_monitor.cpp
--- 1.5.0-1/native/python/pyjp_monitor.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_monitor.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -34,7 +34,7 @@ static int PyJPMonitor_init(PyJPMonitor
 	JP_PY_TRY("PyJPMonitor_init");
 	self->m_Monitor = nullptr;
 	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 
 	PyObject* value;
 
@@ -66,7 +66,7 @@ static int PyJPMonitor_init(PyJPMonitor
 		return -1;
 	}
 
-	self->m_Monitor = new JPMonitor(context, v1->getValue().l);
+	self->m_Monitor = new JPMonitor(v1->getValue().l);
 	return 0;
 	JP_PY_CATCH(-1);
 }
@@ -89,8 +89,7 @@ static PyObject *PyJPMonitor_str(PyJPMon
 static PyObject *PyJPMonitor_enter(PyJPMonitor *self, PyObject *args)
 {
 	JP_PY_TRY("PyJPMonitor_enter");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	self->m_Monitor->enter();
 	Py_RETURN_NONE;
 	JP_PY_CATCH(nullptr);
@@ -99,8 +98,7 @@ static PyObject *PyJPMonitor_enter(PyJPM
 static PyObject *PyJPMonitor_exit(PyJPMonitor *self, PyObject *args)
 {
 	JP_PY_TRY("PyJPMonitor_exit");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	self->m_Monitor->exit();
 	Py_RETURN_NONE;
 	JP_PY_CATCH(nullptr);
diff -pruN 1.5.0-1/native/python/pyjp_number.cpp 1.6.0-1/native/python/pyjp_number.cpp
--- 1.5.0-1/native/python/pyjp_number.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_number.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -35,12 +35,11 @@ extern "C"
 static PyObject *PyJPNumber_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 {
 	JP_PY_TRY("PyJPNumber_new", type);
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	auto *cls = (JPClass*) PyJPClass_getJPClass((PyObject*) type);
 	if (cls == nullptr)
 		JP_RAISE(PyExc_TypeError, "Class type incorrect");
 
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jvalue val;
 	// One argument tries Java conversion first
 	if (PyTuple_Size(args) == 1)
@@ -85,8 +84,7 @@ static PyObject *PyJPNumber_new(PyTypeOb
 static PyObject *PyJPNumberLong_int(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberLong_int");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (!isNull(self))
 		return PyLong_Type.tp_as_number->nb_int(self);
 	PyErr_SetString(PyExc_TypeError, "cast of null pointer would return non-int");
@@ -96,8 +94,7 @@ static PyObject *PyJPNumberLong_int(PyOb
 static PyObject *PyJPNumberLong_float(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberLong_float");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (!isNull(self))
 		return PyLong_Type.tp_as_number->nb_float(self);
 	PyErr_SetString(PyExc_TypeError, "cast of null pointer would return non-float");
@@ -107,8 +104,7 @@ static PyObject *PyJPNumberLong_float(Py
 static PyObject *PyJPNumberFloat_int(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberFloat_int");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (!isNull(self))
 		return PyFloat_Type.tp_as_number->nb_int(self);
 	PyErr_SetString(PyExc_TypeError, "cast of null pointer would return non-int");
@@ -118,8 +114,7 @@ static PyObject *PyJPNumberFloat_int(PyO
 static PyObject *PyJPNumberFloat_float(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberFloat_float");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (!isNull(self))
 		return PyFloat_Type.tp_as_number->nb_float(self);
 	PyErr_SetString(PyExc_TypeError, "cast of null pointer would return non-float");
@@ -129,8 +124,7 @@ static PyObject *PyJPNumberFloat_float(P
 static PyObject *PyJPNumberLong_str(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberLong_str");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (isNull(self))
 		return Py_TYPE(Py_None)->tp_str(Py_None);
 	return PyLong_Type.tp_str(self);
@@ -140,8 +134,7 @@ static PyObject *PyJPNumberLong_str(PyOb
 static PyObject *PyJPNumberFloat_str(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberFloat_str");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (isNull(self))
 		return Py_TYPE(Py_None)->tp_str(Py_None);
 	return PyFloat_Type.tp_str(self);
@@ -151,8 +144,7 @@ static PyObject *PyJPNumberFloat_str(PyO
 static PyObject *PyJPNumberLong_repr(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberLong_repr");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (isNull(self))
 		return Py_TYPE(Py_None)->tp_str(Py_None);
 	return PyLong_Type.tp_repr(self);
@@ -162,8 +154,7 @@ static PyObject *PyJPNumberLong_repr(PyO
 static PyObject *PyJPNumberFloat_repr(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberFloat_repr");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (isNull(self))
 		return Py_TYPE(Py_None)->tp_str(Py_None);
 	return PyFloat_Type.tp_repr(self);
@@ -177,8 +168,7 @@ static const char* op_names[] = {
 static PyObject *PyJPNumberLong_compare(PyObject *self, PyObject *other, int op)
 {
 	JP_PY_TRY("PyJPNumberLong_compare");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (isNull(self))
 	{
 		if (op == Py_EQ)
@@ -201,8 +191,7 @@ static PyObject *PyJPNumberLong_compare(
 static PyObject *PyJPNumberFloat_compare(PyObject *self, PyObject *other, int op)
 {
 	JP_PY_TRY("PyJPNumberFloat_compare");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	if (isNull(self))
 	{
 		if (op == Py_EQ)
@@ -225,8 +214,7 @@ static PyObject *PyJPNumberFloat_compare
 static Py_hash_t PyJPNumberLong_hash(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberLong_hash");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue *javaSlot = PyJPValue_getJavaSlot(self);
 	if (javaSlot == nullptr)
 		return Py_TYPE(Py_None)->tp_hash(Py_None);
@@ -243,8 +231,7 @@ static Py_hash_t PyJPNumberLong_hash(PyO
 static Py_hash_t PyJPNumberFloat_hash(PyObject *self)
 {
 	JP_PY_TRY("PyJPNumberFloat_hash");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue *javaSlot = PyJPValue_getJavaSlot(self);
 	if (javaSlot == nullptr)
 		return Py_TYPE(Py_None)->tp_hash(Py_None);
@@ -261,8 +248,6 @@ static Py_hash_t PyJPNumberFloat_hash(Py
 static PyObject *PyJPBoolean_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 {
 	JP_PY_TRY("PyJPBoolean_new", type);
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	JPPyObject self;
 	if (PyTuple_Size(args) != 1)
 	{
@@ -270,15 +255,15 @@ static PyObject *PyJPBoolean_new(PyTypeO
 		return nullptr;
 	}
 	int i = PyObject_IsTrue(PyTuple_GetItem(args, 0));
-	PyObject *args2 = PyTuple_Pack(1, PyLong_FromLong(i));
-	self = JPPyObject::call(PyLong_Type.tp_new(type, args2, kwargs));
-	Py_DECREF(args2);
+	JPPyObject args2 = JPPyTuple_Pack(PyLong_FromLong(i));
+	self = JPPyObject::call(PyLong_Type.tp_new(type, args2.get(), kwargs));
 	JPClass *cls = PyJPClass_getJPClass((PyObject*) type);
 	if (cls == nullptr)
 	{
 		PyErr_SetString(PyExc_TypeError, "Class type incorrect");
 		return nullptr;
 	}
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPMatch match(&frame, self.get());
 	cls->findJavaConversion(match);
 	jvalue val = match.convert();
@@ -299,6 +284,17 @@ static PyObject* PyJPBoolean_str(PyObjec
 	JP_PY_CATCH(nullptr);
 }
 
+static PyObject *PyJPNumber_initSubclass(PyObject *cls, PyObject* args, PyObject *kwargs)
+{
+        Py_RETURN_NONE;
+}
+
+static PyMethodDef numberMethods[] = {
+    {"__init_subclass__", (PyCFunction) PyJPNumber_initSubclass, METH_CLASS | METH_VARARGS | METH_KEYWORDS, ""},
+    {0}
+};
+
+
 static PyType_Slot numberLongSlots[] = {
 	{Py_tp_new,      (void*) &PyJPNumber_new},
 	{Py_tp_getattro, (void*) &PyJPValue_getattro},
@@ -309,6 +305,7 @@ static PyType_Slot numberLongSlots[] = {
 	{Py_tp_repr,     (void*) &PyJPNumberLong_repr},
 	{Py_tp_hash,     (void*) &PyJPNumberLong_hash},
 	{Py_tp_richcompare, (void*) &PyJPNumberLong_compare},
+	{Py_tp_methods,  (void*) numberMethods},
 	{0}
 };
 
@@ -331,6 +328,7 @@ static PyType_Slot numberFloatSlots[] =
 	{Py_tp_repr,     (void*) &PyJPNumberFloat_repr},
 	{Py_tp_hash,     (void*) &PyJPNumberFloat_hash},
 	{Py_tp_richcompare, (void*) &PyJPNumberFloat_compare},
+	{Py_tp_methods,  (void*) numberMethods},
 	{0}
 };
 
@@ -353,6 +351,7 @@ static PyType_Slot numberBooleanSlots[]
 	{Py_nb_float,    (void*) PyJPNumberLong_float},
 	{Py_tp_hash,     (void*) PyJPNumberLong_hash},
 	{Py_tp_richcompare, (void*) PyJPNumberLong_compare},
+	{Py_tp_methods,  (void*) numberMethods},
 	{0}
 };
 
@@ -371,25 +370,21 @@ PyType_Spec numberBooleanSpec = {
 
 void PyJPNumber_initType(PyObject* module)
 {
-	PyObject *bases;
 
-	bases = PyTuple_Pack(2, &PyLong_Type, PyJPObject_Type);
-	PyJPNumberLong_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&numberLongSpec, bases);
-	Py_DECREF(bases);
+	JPPyObject bases = JPPyTuple_Pack(&PyLong_Type, PyJPObject_Type);
+	PyJPNumberLong_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&numberLongSpec, bases.get());
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 	PyModule_AddObject(module, "_JNumberLong", (PyObject*) PyJPNumberLong_Type);
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 
-	bases = PyTuple_Pack(2, &PyFloat_Type, PyJPObject_Type);
-	PyJPNumberFloat_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&numberFloatSpec, bases);
-	Py_DECREF(bases);
+	bases = JPPyTuple_Pack(&PyFloat_Type, PyJPObject_Type);
+	PyJPNumberFloat_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&numberFloatSpec, bases.get());
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 	PyModule_AddObject(module, "_JNumberFloat", (PyObject*) PyJPNumberFloat_Type);
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 
-	bases = PyTuple_Pack(1, &PyLong_Type, PyJPObject_Type);
-	PyJPNumberBool_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&numberBooleanSpec, bases);
-	Py_DECREF(bases);
+	bases = JPPyTuple_Pack(&PyLong_Type, PyJPObject_Type);
+	PyJPNumberBool_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&numberBooleanSpec, bases.get());
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 	PyModule_AddObject(module, "_JBoolean", (PyObject*) PyJPNumberBool_Type);
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
@@ -397,15 +392,15 @@ void PyJPNumber_initType(PyObject* modul
 
 JPPyObject PyJPNumber_create(JPJavaFrame &frame, JPPyObject& wrapper, const JPValue& value)
 {
-	JPContext *context = frame.getContext();
+	JPContext *context = PyJPModule_getContext();
 	// Bools are not numbers in Java
 	if (value.getClass() == context->_java_lang_Boolean)
 	{
 		jlong l = 0;
 		if (value.getValue().l != nullptr)
 			l = frame.CallBooleanMethodA(value.getJavaObject(), context->_java_lang_Boolean->m_BooleanValueID, nullptr);
-		PyObject *args = PyTuple_Pack(1, PyLong_FromLongLong(l));
-		return JPPyObject::call(PyLong_Type.tp_new((PyTypeObject*) wrapper.get(), args, nullptr));
+		JPPyObject args = JPPyTuple_Pack(PyLong_FromLongLong(l));
+		return JPPyObject::call(PyLong_Type.tp_new((PyTypeObject*) wrapper.get(), args.get(), nullptr));
 	}
 	if (PyObject_IsSubclass(wrapper.get(), (PyObject*) & PyLong_Type))
 	{
@@ -415,8 +410,8 @@ JPPyObject PyJPNumber_create(JPJavaFrame
 			auto* jb = dynamic_cast<JPBoxedType*>( value.getClass());
 			l = frame.CallLongMethodA(value.getJavaObject(), jb->m_LongValueID, nullptr);
 		}
-		PyObject *args = PyTuple_Pack(1, PyLong_FromLongLong(l));
-		return JPPyObject::call(PyLong_Type.tp_new((PyTypeObject*) wrapper.get(), args, nullptr));
+		JPPyObject args = JPPyTuple_Pack(PyLong_FromLongLong(l));
+		return JPPyObject::call(PyLong_Type.tp_new((PyTypeObject*) wrapper.get(), args.get(), nullptr));
 	}
 	if (PyObject_IsSubclass(wrapper.get(), (PyObject*) & PyFloat_Type))
 	{
@@ -426,8 +421,8 @@ JPPyObject PyJPNumber_create(JPJavaFrame
 			auto* jb = dynamic_cast<JPBoxedType*>( value.getClass());
 			l = frame.CallDoubleMethodA(value.getJavaObject(), jb->m_DoubleValueID, nullptr);
 		}
-		PyObject *args = PyTuple_Pack(1, PyFloat_FromDouble(l));
-		return JPPyObject::call(PyFloat_Type.tp_new((PyTypeObject*) wrapper.get(), args, nullptr));
+		JPPyObject args = JPPyTuple_Pack(PyFloat_FromDouble(l));
+		return JPPyObject::call(PyFloat_Type.tp_new((PyTypeObject*) wrapper.get(), args.get(), nullptr));
 	}
 	JP_RAISE(PyExc_TypeError, "unable to convert");  //GCOVR_EXCL_LINE
 }
diff -pruN 1.5.0-1/native/python/pyjp_object.cpp 1.6.0-1/native/python/pyjp_object.cpp
--- 1.5.0-1/native/python/pyjp_object.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_object.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -15,7 +15,6 @@
  *****************************************************************************/
 #include "jpype.h"
 #include "pyjp.h"
-#include <structmember.h>
 
 #ifdef __cplusplus
 extern "C"
@@ -34,8 +33,7 @@ static PyObject *PyJPObject_new(PyTypeOb
 	}
 
 	// Create an instance (this may fail)
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPPyObjectVector args(pyargs);
 	JPValue jv = cls->newInstance(frame, args);
 
@@ -68,8 +66,7 @@ static PyObject *PyJPObject_compare(PyOb
 		return out;
 	}
 
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue *javaSlot0 = PyJPValue_getJavaSlot(self);
 	JPValue *javaSlot1 = PyJPValue_getJavaSlot(other);
 
@@ -111,8 +108,7 @@ static PyObject *PyJPObject_compare(PyOb
 static PyObject *PyJPComparable_compare(PyObject *self, PyObject *other, int op)
 {
 	JP_PY_TRY("PyJPComparable_compare");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue *javaSlot0 = PyJPValue_getJavaSlot(self);
 	JPValue *javaSlot1 = PyJPValue_getJavaSlot(other);
 
@@ -203,8 +199,7 @@ static PyObject *PyJPComparable_compare(
 static Py_hash_t PyJPObject_hash(PyObject *obj)
 {
 	JP_PY_TRY("PyJPObject_hash");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue *javaSlot = PyJPValue_getJavaSlot(obj);
 	if (javaSlot == nullptr)
 		return Py_TYPE(Py_None)->tp_hash(Py_None);
@@ -222,7 +217,19 @@ static PyObject *PyJPObject_repr(PyObjec
 	JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE
 }
 
+static PyObject *PyJPObject_initSubclass(PyObject *cls, PyObject* args, PyObject *kwargs)
+{
+    Py_RETURN_NONE;
+}
+
+static PyMethodDef objectMethods[] = {
+	{"__init_subclass__", (PyCFunction) PyJPObject_initSubclass, METH_CLASS | METH_VARARGS | METH_KEYWORDS, ""},
+    {0}
+};
+
 static PyType_Slot objectSlots[] = {
+	{Py_tp_alloc,    (void*) &PyJPValue_alloc},
+	{Py_tp_finalize,    (void*) &PyJPValue_finalize},
 	{Py_tp_new,      (void*) &PyJPObject_new},
 	{Py_tp_free,     (void*) &PyJPValue_free},
 	{Py_tp_getattro, (void*) &PyJPValue_getattro},
@@ -231,6 +238,7 @@ static PyType_Slot objectSlots[] = {
 	{Py_tp_repr,     (void*) &PyJPObject_repr},
 	{Py_tp_richcompare, (void*) &PyJPObject_compare},
 	{Py_tp_hash,     (void*) &PyJPObject_hash},
+	{Py_tp_methods,  (void*) objectMethods},
 	{0}
 };
 
@@ -255,13 +263,12 @@ static PyObject *PyJPException_new(PyTyp
 	}  // GCOVR_EXCL_STOP
 
 	// Special constructor path for Exceptions
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPPyObjectVector args(pyargs);
 	if (args.size() == 2 && args[0] == _JObjectKey)
 		return ((PyTypeObject*) PyExc_BaseException)->tp_new(type, args[1], kwargs);
 
 	// Create an instance (this may fail)
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	JPValue jv = cls->newInstance(frame, args);
 
 	// Exception must be constructed with the BaseException_new
@@ -289,8 +296,7 @@ static int PyJPException_init(PyObject *
 static PyObject* PyJPException_expandStacktrace(PyObject* self)
 {
 	JP_PY_TRY("PyJPModule_expandStackTrace");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue *val = PyJPValue_getJavaSlot(self);
 
 	// These two are loop invariants and must match each time
@@ -361,18 +367,17 @@ static PyType_Spec comparableSpec = {
 
 void PyJPObject_initType(PyObject* module)
 {
-	PyJPObject_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&objectSpec, nullptr);
-	JP_PY_CHECK(); // GCOVR_EXCL_LINE
+    PyJPObject_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&objectSpec, nullptr);
+    JP_PY_CHECK(); // GCOVR_EXCL_LINE
 	PyModule_AddObject(module, "_JObject", (PyObject*) PyJPObject_Type);
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
-
-	JPPyObject bases = JPPyObject::call(PyTuple_Pack(2, PyExc_Exception, PyJPObject_Type));
+    JPPyObject bases = JPPyTuple_Pack(PyExc_Exception, PyJPObject_Type);
 	PyJPException_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&excSpec, bases.get());
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 	PyModule_AddObject(module, "_JException", (PyObject*) PyJPException_Type);
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 
-	bases = JPPyObject::call(PyTuple_Pack(1, PyJPObject_Type));
+	bases = JPPyTuple_Pack(PyJPObject_Type);
 	PyJPComparable_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&comparableSpec, bases.get());
 	JP_PY_CHECK(); // GCOVR_EXCL_LINE
 	PyModule_AddObject(module, "_JComparable", (PyObject*) PyJPComparable_Type);
@@ -385,12 +390,13 @@ void PyJPObject_initType(PyObject* modul
 void PyJPException_normalize(JPJavaFrame frame, JPPyObject exc, jthrowable th, jthrowable enclosing)
 {
 	JP_TRACE_IN("PyJPException_normalize");
-	JPContext *context = frame.getContext();
+	JPContext *context = PyJPModule_getContext();
 	while (th != nullptr)
 	{
 		// Attach the frame to first
 		JPPyObject trace = PyTrace_FromJavaException(frame, th, enclosing);
-		PyException_SetTraceback(exc.get(), trace.get());
+		if (trace.get() != nullptr)
+			PyException_SetTraceback(exc.get(), trace.get());
 
 		// Check for the next in the cause list
 		enclosing = th;
diff -pruN 1.5.0-1/native/python/pyjp_package.cpp 1.6.0-1/native/python/pyjp_package.cpp
--- 1.5.0-1/native/python/pyjp_package.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_package.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -58,13 +58,14 @@ static PyObject *PyJPPackage_new(PyTypeO
 
 static void dtor(PyObject *self)
 {
+	// No need for exception if JVM is not running.
 	JPContext *context = JPContext_global;
 	if (context == nullptr || !context->isRunning())
 		return;
 	auto jo = (jobject) PyCapsule_GetPointer(self, nullptr);
 	if (jo == nullptr)
 		return;
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	frame.DeleteGlobalRef(jo);
 }
 
@@ -135,6 +136,7 @@ static PyObject *PyJPPackage_getattro(Py
 	if (attrName.compare(0, 2, "__") == 0)
 		return PyObject_GenericGetAttr((PyObject*) self, attr);
 
+	// Check for JVM running first
 	JPContext* context = JPContext_global;
 	if (!context->isRunning())
 	{
@@ -143,7 +145,7 @@ static PyObject *PyJPPackage_getattro(Py
 				PyModule_GetName(self), attr);
 		return nullptr;
 	}
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jobject pkg = getPackage(frame, self);
 	if (pkg == nullptr)
 		return nullptr;
@@ -163,7 +165,7 @@ static PyObject *PyJPPackage_getattro(Py
 			JPPyErrFrame err;
 			err.normalize();
 			err.clear();
-			JPPyObject tuple0 = JPPyObject::call(PyTuple_Pack(3, self, attr, err.m_ExceptionValue.get()));
+			JPPyObject tuple0 = JPPyTuple_Pack(self, attr, err.m_ExceptionValue.get());
 			PyObject *rc = PyObject_Call(h.get(), tuple0.get(), nullptr);
 			if (rc == nullptr)
 				return nullptr;
@@ -182,7 +184,7 @@ static PyObject *PyJPPackage_getattro(Py
 	{
 		JPPyObject u = JPPyObject::call(PyUnicode_FromFormat("%s.%U",
 				PyModule_GetName(self), attr));
-		JPPyObject args = JPPyObject::call(PyTuple_Pack(1, u.get()));
+		JPPyObject args = JPPyTuple_Pack(u.get());
 		out = JPPyObject::call(PyObject_Call((PyObject*) PyJPPackage_Type, args.get(), nullptr));
 	} else
 	{
@@ -248,8 +250,7 @@ static PyObject *PyJPPackage_path(PyObje
 static PyObject *PyJPPackage_dir(PyObject *self)
 {
 	JP_PY_TRY("PyJPPackage_dir");
-	JPContext* context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	jobject pkg = getPackage(frame, self);
 	if (pkg == nullptr)
 		return nullptr;
@@ -284,7 +285,7 @@ static PyObject *PyJPPackage_cast(PyObje
 	PyObject* matmul = PyDict_GetItemString(dict, "__matmul__");
 	if (matmul == nullptr)
 		Py_RETURN_NOTIMPLEMENTED;
-	JPPyObject args = JPPyObject::call(PyTuple_Pack(2, self, other));
+	JPPyObject args = JPPyTuple_Pack(self, other);
 	return PyObject_Call(matmul, args.get(), nullptr);
 	JP_PY_CATCH(nullptr);
 }
@@ -338,7 +339,7 @@ static PyType_Spec packageSpec = {
 void PyJPPackage_initType(PyObject* module)
 {
 	// Inherit from module.
-	JPPyObject bases = JPPyObject::call(PyTuple_Pack(1, &PyModule_Type));
+	JPPyObject bases = JPPyTuple_Pack(&PyModule_Type);
 	packageSpec.basicsize = PyModule_Type.tp_basicsize;
 	PyJPPackage_Type = (PyTypeObject*) PyType_FromSpecWithBases(&packageSpec, bases.get());
 	JP_PY_CHECK();
diff -pruN 1.5.0-1/native/python/pyjp_proxy.cpp 1.6.0-1/native/python/pyjp_proxy.cpp
--- 1.5.0-1/native/python/pyjp_proxy.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_proxy.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -28,16 +28,15 @@ extern "C"
 static PyObject *PyJPProxy_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 {
 	JP_PY_TRY("PyJPProxy_new");
-	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
 	auto *self = (PyJPProxy*) type->tp_alloc(type, 0);
 	JP_PY_CHECK();
 
 	// Parse arguments
-	PyObject *target;
+	PyObject *instance;
+	PyObject *dispatch;
 	PyObject *pyintf;
 	int convert = 0;
-	if (!PyArg_ParseTuple(args, "OO|p", &target, &pyintf, &convert))
+	if (!PyArg_ParseTuple(args, "OOO|p", &instance, &dispatch, &pyintf, &convert))
 		return nullptr;
 
 	// Pack interfaces
@@ -47,6 +46,7 @@ static PyObject *PyJPProxy_new(PyTypeObj
 		return nullptr;
 	}
 
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClassList interfaces;
 	JPPySequence intf = JPPySequence::use(pyintf);
 	jlong len = intf.size();
@@ -64,16 +64,19 @@ static PyObject *PyJPProxy_new(PyTypeObj
 		interfaces.push_back(cls);
 	}
 
-	if (target == Py_None)
-		self->m_Proxy = new JPProxyDirect(context, self, interfaces);
+	if (dispatch == Py_None)
+		self->m_Proxy = new JPProxyDirect(self, interfaces);
 	else
-		self->m_Proxy = new JPProxyIndirect(context, self, interfaces);
-	self->m_Target = target;
+		self->m_Proxy = new JPProxyIndirect(self, interfaces);
+	self->m_Target = instance;
+	self->m_Dispatch = dispatch;
 	self->m_Convert = (convert != 0);
-	Py_INCREF(target);
+	Py_INCREF(self->m_Target);
+	Py_INCREF(self->m_Dispatch);
 
 	JP_TRACE("Proxy", self);
-	JP_TRACE("Target", target);
+	JP_TRACE("Target", instance);
+	JP_TRACE("Dispatch", dispatch);
 	return (PyObject*) self;
 	JP_PY_CATCH(nullptr);
 }
@@ -81,12 +84,14 @@ static PyObject *PyJPProxy_new(PyTypeObj
 static int PyJPProxy_traverse(PyJPProxy *self, visitproc visit, void *arg)
 {
 	Py_VISIT(self->m_Target);
+	Py_VISIT(self->m_Dispatch);
 	return 0;
 }
 
 static int PyJPProxy_clear(PyJPProxy *self)
 {
 	Py_CLEAR(self->m_Target);
+	Py_CLEAR(self->m_Dispatch);
 	return 0;
 }
 
@@ -102,14 +107,14 @@ void PyJPProxy_dealloc(PyJPProxy* self)
 
 static PyObject *PyJPProxy_class(PyJPProxy *self, void *context)
 {
-	JPJavaFrame frame = JPJavaFrame::outer(self->m_Proxy->getContext());
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClass* cls = self->m_Proxy->getInterfaces()[0];
 	return PyJPClass_create(frame, cls).keep();
 }
 
 static PyObject *PyJPProxy_inst(PyJPProxy *self, void *context)
 {
-	PyObject *out = self->m_Target;
+	PyObject *out = self->m_Dispatch;
 	if (out == Py_None)
 		out = (PyObject*) self;
 	Py_INCREF(out);
@@ -173,7 +178,7 @@ PyType_Spec PyJPProxySpec = {
 
 void PyJPProxy_initType(PyObject* module)
 {
-	JPPyObject bases = JPPyObject::call(PyTuple_Pack(1, &PyBaseObject_Type));
+	JPPyObject bases = JPPyTuple_Pack(&PyBaseObject_Type);
 	PyJPProxy_Type = (PyTypeObject*) PyType_FromSpecWithBases(&PyJPProxySpec, bases.get());
 	JP_PY_CHECK();
 	PyModule_AddObject(module, "_JProxy", (PyObject*) PyJPProxy_Type);
diff -pruN 1.5.0-1/native/python/pyjp_value.cpp 1.6.0-1/native/python/pyjp_value.cpp
--- 1.5.0-1/native/python/pyjp_value.cpp	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/native/python/pyjp_value.cpp	2025-06-01 03:57:43.000000000 +0000
@@ -16,12 +16,19 @@
 #include "jpype.h"
 #include "pyjp.h"
 #include "jp_stringtype.h"
+#include <Python.h>
+#include <mutex>
 
 #ifdef __cplusplus
 extern "C"
 {
 #endif
 
+std::mutex mtx;
+
+// Create a dummy type which we will use only for allocation
+PyTypeObject* PyJPAlloc_Type = nullptr;
+
 /**
  * Allocate a new Python object with a slot for Java.
  *
@@ -40,53 +47,37 @@ extern "C"
 PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems)
 {
 	JP_PY_TRY("PyJPValue_alloc");
-	// Modification from Python to add size elements
-	const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue);
-	PyObject *obj = nullptr;
-	if (PyType_IS_GC(type))
-	{
-		// Horrible kludge because python lacks an API for allocating a GC type with extra memory
-		// The private method _PyObject_GC_Alloc is no longer visible, so we are forced to allocate
-		// a different type with the extra memory and then hot swap the type to the real one.
-		PyTypeObject type2;
-		type2.tp_basicsize = size;
-		type2.tp_itemsize = 0;
-		type2.tp_name = nullptr;
-		type2.tp_flags = type->tp_flags;
-		type2.tp_traverse = type->tp_traverse;
 
-		// Allocate the fake type
-		obj = PyObject_GC_New(PyObject, &type2);
-
-		// Note the object will be inited twice which should not leak. (fingers crossed)
+#if PY_VERSION_HEX >= 0x030d0000
+	// This flag will try to place the dictionary are part of the object which 
+	// adds an unknown number of bytes to the end of the object making it impossible
+	// to attach our needed data.  If we kill the flag then we get usable behavior.
+	if (PyType_HasFeature(type, Py_TPFLAGS_INLINE_VALUES)) {
+		PyErr_Format(PyExc_RuntimeError, "Unhandled object layout");
+		return 0;
 	}
-	else
-	{
-		obj = (PyObject*) PyObject_MALLOC(size);
+#endif
+
+	PyObject* obj = nullptr;
+	{  
+		std::lock_guard<std::mutex> lock(mtx);
+		// Mutate the allocator type 
+		PyJPAlloc_Type->tp_flags = type->tp_flags;
+		PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + sizeof (JPValue);
+		PyJPAlloc_Type->tp_itemsize = type->tp_itemsize;
+	
+		// Create a new allocation for the dummy type
+		obj = PyType_GenericAlloc(PyJPAlloc_Type, nitems);
 	}
-	if (obj == nullptr)
-		return PyErr_NoMemory(); // GCOVR_EXCL_LINE
-	memset(obj, 0, size);
 
+	// Watch for memory errors
+	if (obj == nullptr)
+		return nullptr;
 
-	Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt;
+	// Polymorph the type to match our desired type
 	obj->ob_type = type;
+	Py_INCREF(type);
 
-	if (type->tp_itemsize == 0)
-		PyObject_Init(obj, type);
-	else
-		PyObject_InitVar((PyVarObject *) obj, type, nitems);
-
-	// This line is required to deal with Python bug (GH-11661)
-	// Some versions of Python fail to increment the reference counter of
-	// heap types properly.
-	if (refcnt == ((PyObject*) type)->ob_refcnt)
-		Py_INCREF(type);  // GCOVR_EXCL_LINE
-
-	if (PyType_IS_GC(type))
-	{
-		PyObject_GC_Track(obj);
-	}
 	JP_TRACE("alloc", type->tp_name, obj);
 	return obj;
 	JP_PY_CATCH(nullptr);
@@ -107,17 +98,20 @@ Py_ssize_t PyJPValue_getJavaSlotOffset(P
 	if (type == nullptr
 			|| type->tp_alloc != (allocfunc) PyJPValue_alloc
 			|| type->tp_finalize != (destructor) PyJPValue_finalize)
+	{
 		return 0;
-	Py_ssize_t offset;
-	Py_ssize_t sz = 0;
+	}
 
+	Py_ssize_t offset = 0;
+	Py_ssize_t sz = 0;
+	
 #if PY_VERSION_HEX>=0x030c0000
 	// starting in 3.12 there is no longer ob_size in PyLong
 	if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS))
 		sz = (((PyLongObject*)self)->long_value.lv_tag) >> 3;  // Private NON_SIZE_BITS
 	else 
 #endif
-		if (type->tp_itemsize != 0)
+	if (type->tp_itemsize != 0)
 		sz = Py_SIZE(self);
 	// PyLong abuses ob_size with negative values prior to 3.12
 	if (sz < 0)
@@ -169,10 +163,14 @@ void PyJPValue_finalize(void* obj)
 	JPValue* value = PyJPValue_getJavaSlot((PyObject*) obj);
 	if (value == nullptr)
 		return;
+
+	// We can skip if the JVM is stopped.  No need for an exception here.
 	JPContext *context = JPContext_global;
 	if (context == nullptr || !context->isRunning())
 		return;
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+
+	// JVM is running so set up the frame.
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPClass* cls = value->getClass();
 	// This one can't check for initialized because we may need to delete a stale
 	// resource after shutdown.
@@ -191,7 +189,7 @@ PyObject* PyJPValue_str(PyObject* self)
 {
 	JP_PY_TRY("PyJPValue_str", self);
 	JPContext *context = PyJPModule_getContext();
-	JPJavaFrame frame = JPJavaFrame::outer(context);
+	JPJavaFrame frame = JPJavaFrame::outer();
 	JPValue* value = PyJPValue_getJavaSlot(self);
 	if (value == nullptr)
 	{
@@ -221,7 +219,7 @@ PyObject* PyJPValue_str(PyObject* self)
 				Py_INCREF(cache);
 				return cache;
 			}
-            auto jstr = (jstring) value->getValue().l;
+			auto jstr = (jstring) value->getValue().l;
 			string str;
 			str = frame.toStringUTF8(jstr);
 			cache = JPPyString::fromStringUTF8(str).keep();
@@ -337,3 +335,38 @@ bool PyJPValue_isSetJavaSlot(PyObject* s
 	auto* slot = (JPValue*) (((char*) self) + offset);
 	return slot->getClass() != nullptr;
 }
+
+/***************** Create a dummy type for use when allocating. ************************/
+static int PyJPAlloc_traverse(PyObject *self, visitproc visit, void *arg)
+{
+	return 0;
+}
+
+static int PyJPAlloc_clear(PyObject *self)
+{
+	return 0;
+}
+
+
+static PyType_Slot allocSlots[] = {
+	{ Py_tp_traverse, (void*) PyJPAlloc_traverse},
+	{ Py_tp_clear, (void*) PyJPAlloc_clear},
+	{0, NULL}  // Sentinel
+};
+
+static PyType_Spec allocSpec = {
+	"_jpype._JAlloc",
+	sizeof(PyObject),
+	0,
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+	allocSlots
+};
+
+void PyJPValue_initType(PyObject* module)
+{
+	PyObject *bases = PyTuple_Pack(1, &PyBaseObject_Type);
+	PyJPAlloc_Type = (PyTypeObject*) PyType_FromSpecWithBases(&allocSpec, bases);
+	Py_DECREF(bases);
+	Py_INCREF(PyJPAlloc_Type);
+	JP_PY_CHECK();
+}
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/README" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/README"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/README"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/README"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,2 @@
+Test to verify that JAR's with unicode characters in the path are loaded correctly.
+The built jar should be placed in ``test/jar/unicode_à😎/sample_package.jar``.
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/build.sh" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/build.sh"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/build.sh"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/build.sh"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,2 @@
+javac --release 8 -d classes src/org/jpype/sample_package/*.java
+jar --create --file sample_package.jar -C classes .
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/A.java" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/A.java"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/A.java"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/A.java"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,5 @@
+package org.jpype.sample_package;
+
+public class A
+{
+}
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/B.java" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/B.java"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/B.java"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/sample_package/src/org/jpype/sample_package/B.java"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,5 @@
+package org.jpype.sample_package;
+
+public class B
+{
+}
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/README" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/README"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/README"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/README"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1 @@
+Test to verify that service providers are found and used when placed in a non ascii path
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/build.gradle" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/build.gradle"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/build.gradle"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/build.gradle"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,20 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility = 8
+targetCompatibility = 8
+
+
+tasks.withType(JavaCompile) {
+    options.release = 8
+	options.debug = false
+}
+
+jar {
+    destinationDirectory = file('dist')
+	from ('./src/main/java') {
+		include 'META-INF/services/java.time.zone.ZoneRulesProvider'
+	}
+}
+
+build.dependsOn(jar)
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/java/org/jpype/service/JpypeZoneRulesProvider.java" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/java/org/jpype/service/JpypeZoneRulesProvider.java"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/java/org/jpype/service/JpypeZoneRulesProvider.java"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/java/org/jpype/service/JpypeZoneRulesProvider.java"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,28 @@
+package org.jpype.service;
+
+import java.time.ZoneId;
+import java.time.zone.ZoneRules;
+import java.time.zone.ZoneRulesProvider;
+import java.util.Collections;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class JpypeZoneRulesProvider extends ZoneRulesProvider {
+
+	@Override
+	protected Set<String> provideZoneIds() {
+		return Collections.singleton("JpypeTest/Timezone");
+	}
+
+	@Override
+	protected ZoneRules provideRules(String zoneId, boolean forCaching) {
+		return ZoneId.of("UTC").getRules();
+	}
+
+	@Override
+	protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
+		return new TreeMap<>();
+	}
+
+}
diff -pruN "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/resources/META-INF/services/java.time.zone.ZoneRulesProvider" "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/resources/META-INF/services/java.time.zone.ZoneRulesProvider"
--- "1.5.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/resources/META-INF/services/java.time.zone.ZoneRulesProvider"	1970-01-01 00:00:00.000000000 +0000
+++ "1.6.0-1/project/jars/unicode_\303\240\360\237\230\216/service/src/main/resources/META-INF/services/java.time.zone.ZoneRulesProvider"	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1 @@
+org.jpype.service.JpypeZoneRulesProvider
diff -pruN 1.5.0-1/project/jpype_java/nbproject/project.properties 1.6.0-1/project/jpype_java/nbproject/project.properties
--- 1.5.0-1/project/jpype_java/nbproject/project.properties	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/project/jpype_java/nbproject/project.properties	2025-06-01 03:57:43.000000000 +0000
@@ -113,3 +113,4 @@ source.encoding=UTF-8
 src.java.dir=${file.reference.native-java}
 test.harness.dir=${file.reference.test-harness}
 test.src.dir=test
+manifest.file=../../native/java/manifest.txt
diff -pruN 1.5.0-1/pyproject.toml 1.6.0-1/pyproject.toml
--- 1.5.0-1/pyproject.toml	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/pyproject.toml	2025-06-01 03:57:43.000000000 +0000
@@ -1,3 +1,60 @@
+[build-system]
+requires = [
+    "setuptools",
+]
+build-backend = "setuptools.build_meta"
+
+
+[project]
+name = "jpype1"
+version = '1.6.0'
+authors = [
+    {name = "Steve Menard", email = "devilwolf@users.sourceforge.net"},
+]
+maintainers = [
+    {name = "Luis Nell", email = "cooperate@originell.org"},
+]
+description = "A Python to Java bridge"
+readme = "README.rst"
+requires-python = ">=3.8"
+license = {text = "License :: OSI Approved :: Apache Software License"}
+classifiers = [
+    'Programming Language :: Python :: 3',
+    'Programming Language :: Python :: 3.8',
+    'Programming Language :: Python :: 3.9',
+    'Programming Language :: Python :: 3.10',
+    'Programming Language :: Python :: 3.11',
+    'Programming Language :: Python :: 3.12',
+    'Programming Language :: Python :: 3.13',
+    'Topic :: Software Development',
+    'Topic :: Scientific/Engineering',
+]
+dependencies = [
+    'packaging',
+]
+
+
+[project.optional-dependencies]
+
+docs = [
+    'readthedocs-sphinx-ext',
+    'sphinx',
+    'sphinx-rtd-theme',
+]
+tests = [
+    "pytest",
+]
+
+
+[project.entry-points.pyinstaller40]
+"hook-dirs" = "jpype._pyinstaller.entry_points:get_hook_dirs"
+"tests" = "jpype._pyinstaller.entry_points:get_PyInstaller_tests"
+
+
+[project.urls]
+homepage = "https://github.com/jpype-project/jpype"
+
+
 [[tool.mypy.overrides]]
 module = [
     "_jpype",
@@ -9,4 +66,3 @@ module = [
     "jedi.access",
 ]
 ignore_missing_imports = true
-
diff -pruN 1.5.0-1/setup.cfg 1.6.0-1/setup.cfg
--- 1.5.0-1/setup.cfg	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setup.cfg	2025-06-01 03:57:43.000000000 +0000
@@ -1,6 +1,3 @@
-[alias]
-test=pytest
-
 [tool:pytest]
 addopts = --verbose
 testpaths =
diff -pruN 1.5.0-1/setup.py 1.6.0-1/setup.py
--- 1.5.0-1/setup.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setup.py	2025-06-01 03:57:43.000000000 +0000
@@ -23,6 +23,8 @@ from pathlib import Path
 from setuptools import Extension
 from setuptools import setup
 
+# Add our setupext package to the path, and import it.
+sys.path.append(str(Path(__file__).parent))
 import setupext
 
 if '--android' in sys.argv:
@@ -34,75 +36,42 @@ else:
 
 jpypeLib = Extension(name='_jpype', **setupext.platform.Platform(
     include_dirs=[Path('native', 'common', 'include'),
-                  Path('native', 'python', 'include'),
-                  Path('native', 'embedded', 'include')],
-    sources=sorted(map(str, list(Path('native', 'common').glob('*.cpp')) +
-             list(Path('native', 'python').glob('*.cpp')) +
-             list(Path('native', 'embedded').glob('*.cpp')))), platform=platform,
+                  Path('native', 'python', 'include')],
+    sources=sorted(
+        list(Path('native', 'common').glob('*.cpp')) +
+        list(Path('native', 'python').glob('*.cpp')) 
+    ),
+    platform=platform,
 ))
+
+p = [ i for i in Path("native", "jpype_module", "src", "main", "java").glob("**/*.java")]
+javaSrc = [i for i in p if not "exclude" in str(i)]
 jpypeJar = Extension(name="org.jpype",
-                     sources=sorted(map(str, Path("native", "java").glob("**/*.java"))),
+                     sources=sorted(map(str, javaSrc)),
                      language="java",
                      libraries=["lib/asm-8.0.1.jar"]
                      )
 
 
 setup(
-    name='JPype1',
-    version='1.5.0',
-    description='A Python to Java bridge.',
-    long_description=open('README.rst').read(),
-    license='License :: OSI Approved :: Apache Software License',
-    author='Steve Menard',
-    author_email='devilwolf@users.sourceforge.net',
-    maintainer='Luis Nell',
-    maintainer_email='cooperate@originell.org',
-    python_requires=">=3.7",
-    url='https://github.com/jpype-project/jpype',
+    # Non-standard, and extension behaviour of setup() - project information
+    # should be put in pyproject.toml wherever possible. See also:
+    # https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#setuptools-specific-configuration
     platforms=[
         'Operating System :: Microsoft :: Windows',
         'Operating System :: POSIX',
         'Operating System :: Unix',
         'Operating System :: MacOS',
     ],
-    classifiers=[
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Topic :: Software Development',
-        'Topic :: Scientific/Engineering',
-    ],
     packages=['jpype', 'jpype._pyinstaller'],
     package_dir={'jpype': 'jpype', },
     package_data={'jpype': ['*.pyi']},
-    install_requires=['typing_extensions ; python_version< "3.8"',
-        'packaging'],
-    tests_require=['pytest'],
-    extras_require={
-        'tests': [
-            'pytest',
-        ],
-        'docs': [
-            'readthedocs-sphinx-ext',
-            'sphinx',
-            'sphinx-rtd-theme',
-        ],
-    },
     cmdclass={
         'build_ext': setupext.build_ext.BuildExtCommand,
+        'develop': setupext.develop.Develop,
         'test_java': setupext.test_java.TestJavaCommand,
         'sdist': setupext.sdist.BuildSourceDistribution,
-        'test': setupext.pytester.PyTest,
     },
     zip_safe=False,
-    ext_modules=[jpypeJar, jpypeLib, ],
-    distclass=setupext.dist.Distribution,
-    entry_points={
-        'pyinstaller40': [
-            'hook-dirs = jpype._pyinstaller.entry_points:get_hook_dirs',
-            'tests = jpype._pyinstaller.entry_points:get_PyInstaller_tests',
-        ],
-    },
+    ext_modules=[jpypeLib, jpypeJar],
 )
diff -pruN 1.5.0-1/setupext/__init__.py 1.6.0-1/setupext/__init__.py
--- 1.5.0-1/setupext/__init__.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/__init__.py	2025-06-01 03:57:43.000000000 +0000
@@ -16,10 +16,8 @@
 #
 # *****************************************************************************
 
-from . import utils
-from . import dist
 from . import platform
 from . import build_ext
+from . import develop
 from . import test_java
 from . import sdist
-from . import pytester
diff -pruN 1.5.0-1/setupext/build_ext.py 1.6.0-1/setupext/build_ext.py
--- 1.5.0-1/setupext/build_ext.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/build_ext.py	2025-06-01 03:57:43.000000000 +0000
@@ -16,6 +16,7 @@
 #   See NOTICE file for details.
 #
 # *****************************************************************************
+
 import distutils.cmd
 import distutils.log
 import glob
@@ -43,7 +44,7 @@ class FeatureNotice(Warning):
     """ indicate notices about features """
 
 
-class Makefile(object):
+class Makefile:
     compiler_type = "unix"
 
     def __init__(self, actual):
@@ -73,7 +74,8 @@ class Makefile(object):
         self.compile_pre = pre
         self.compile_post = post
         self.includes = includes
-        self.sources.append(x[i1 + 1])
+        if not "bootstrap" in x[i1 + 1]:
+            self.sources.append(x[i1 + 1])
 
     def captureLink(self, x):
         self.link_command = x[0]
@@ -172,6 +174,10 @@ class BuildExtCommand(build_ext):
     """
 
     user_options = build_ext.user_options + [
+        ('enable-build-jar', None, 'Build the java jar portion'),
+        ('enable-tracing', None, 'Set for tracing for debugging'),
+        ('enable-coverage', None, 'Instrument c++ code for code coverage measuring'),
+
         ('android', None, 'configure for android'),
         ('makefile', None, 'Build a makefile for extensions'),
         ('jar', None, 'Build the jar only'),
@@ -179,6 +185,10 @@ class BuildExtCommand(build_ext):
 
     def initialize_options(self, *args):
         """omit -Wstrict-prototypes from CFLAGS since its only valid for C code."""
+        self.enable_tracing = False
+        self.enable_build_jar = False
+        self.enable_coverage = False
+
         self.android = False
         self.makefile = False
         self.jar = False
@@ -195,8 +205,7 @@ class BuildExtCommand(build_ext):
                 continue
 
             args = v.split()
-            for r in remove_args:
-                args = list(filter(r.__ne__, args))
+            args = [arg for arg in args if arg not in remove_args]
 
             cfg_vars[k] = " ".join(args)
         super().initialize_options()
@@ -205,13 +214,15 @@ class BuildExtCommand(build_ext):
         # set compiler flags
         c = self.compiler.compiler_type
         jpypeLib = [i for i in self.extensions if i.name == '_jpype'][0]
-        if c == 'unix' and self.distribution.enable_coverage:
+        if c == 'unix' and self.enable_coverage:
             jpypeLib.extra_compile_args.extend(
                 ['-ggdb', '--coverage', '-ftest-coverage'])
-            jpypeLib.extra_compile_args = ['-O0' if x == '-O2' else x for x in jpypeLib.extra_compile_args]
+            jpypeLib.extra_compile_args = [
+                '-O0' if x == '-O2' else x for x in jpypeLib.extra_compile_args]
             jpypeLib.extra_link_args.extend(['--coverage'])
-        if c == 'unix' and self.distribution.enable_tracing:
-            jpypeLib.extra_compile_args = ['-O0' if x == '-O2' else x for x in jpypeLib.extra_compile_args]
+        if c == 'unix' and self.enable_tracing:
+            jpypeLib.extra_compile_args = [
+                '-O0' if x == '-O2' else x for x in jpypeLib.extra_compile_args]
 
     def build_extensions(self):
         if self.makefile:
@@ -219,16 +230,12 @@ class BuildExtCommand(build_ext):
             self.force = True
 
         jpypeLib = [i for i in self.extensions if i.name == '_jpype'][0]
-        tracing = self.distribution.enable_tracing
         self._set_cflags()
-        if tracing:
+        if self.enable_tracing:
             jpypeLib.define_macros.append(('JP_TRACING_ENABLE', 1))
-        coverage = self.distribution.enable_coverage
-        if coverage:
+        if self.enable_coverage:
             jpypeLib.define_macros.append(('JP_INSTRUMENTATION', 1))
 
-        # has to be last call
-        print("Call build extensions")
         super().build_extensions()
 
     def build_extension(self, ext):
@@ -266,18 +273,20 @@ class BuildExtCommand(build_ext):
 
     def build_java_ext(self, ext):
         """Run command."""
-        java = self.distribution.enable_build_jar
+        java = self.enable_build_jar
 
         javac = "javac"
         try:
             if os.path.exists(os.path.join(os.environ['JAVA_HOME'], 'bin', 'javac')):
-                javac = '"%s"' % os.path.join(os.environ['JAVA_HOME'], 'bin', 'javac')
+                javac = '"%s"' % os.path.join(
+                    os.environ['JAVA_HOME'], 'bin', 'javac')
         except KeyError:
             pass
         jar = "jar"
         try:
             if os.path.exists(os.path.join(os.environ['JAVA_HOME'], 'bin', 'jar')):
-                jar = '"%s"' % os.path.join(os.environ['JAVA_HOME'], 'bin', 'jar')
+                jar = '"%s"' % os.path.join(
+                    os.environ['JAVA_HOME'], 'bin', 'jar')
         except KeyError:
             pass
         # Try to use the cache if we are not requested build
@@ -296,9 +305,7 @@ class BuildExtCommand(build_ext):
         distutils.log.info(
             "Jar cache is missing, using --enable-build-jar to recreate it.")
 
-        coverage = self.distribution.enable_coverage
-
-        target_version = "1.8"
+        target_version = "11"
         # build the jar
         try:
             dirname = os.path.dirname(self.get_ext_fullpath("JAVA"))
@@ -309,24 +316,35 @@ class BuildExtCommand(build_ext):
             cmd1 = shlex.split('%s -cp "%s" -d "%s" -g:none -source %s -target %s -encoding UTF-8' %
                                (javac, classpath, build_dir, target_version, target_version))
             cmd1.extend(ext.sources)
-            debug = "-g:none"
-            if coverage:
-                debug = "-g:lines,vars,source"
-            os.makedirs("build/classes", exist_ok=True)
             self.announce("  %s" % " ".join(cmd1), level=distutils.log.INFO)
             subprocess.check_call(cmd1)
+
+            cmd1 = shlex.split('%s -cp "%s" -d "%s" -g:none -source %s -target %s -encoding UTF-8' %
+                               (javac, build_dir, os.path.join(build_dir, "META-INF", "versions", "0"), target_version, target_version))
+            cmd1.extend(glob.glob(os.path.join(
+                "native", "jpype_module","src","main","java","exclude", "**", "*.java"), recursive=True))
+            os.makedirs(os.path.join(build_dir, "META-INF",
+                        "versions", "0"), exist_ok=True)
+            self.announce("  %s" % " ".join(cmd1), level=distutils.log.INFO)
+            subprocess.check_call(cmd1)
+
+            manifest = None
             try:
-                for file in glob.iglob("native/java/**/*.*", recursive=True):
+                for file in glob.iglob("native/jpype_module/src/main/java/**/*.*", recursive=True):
+                    if file.endswith("manifest.txt"):
+                        manifest = file
+                        continue
                     if file.endswith(".java") or os.path.isdir(file):
                         continue
-                    p = os.path.join(build_dir, os.path.relpath(file, "native/java"))
+                    p = os.path.join(
+                        build_dir, os.path.relpath(file, "native/jpype_module/src/main/java"))
                     print("Copy file", file, p)
                     shutil.copyfile(file, p)
             except Exception as ex:
                 print("FAIL", ex)
                 pass
             cmd3 = shlex.split(
-                '%s cvf "%s" -C "%s" .' % (jar, jarFile, build_dir))
+                '%s cvfm "%s" "%s" -C "%s" .' % (jar, jarFile, manifest, build_dir))
             self.announce("  %s" % " ".join(cmd3), level=distutils.log.INFO)
             subprocess.check_call(cmd3)
 
diff -pruN 1.5.0-1/setupext/develop.py 1.6.0-1/setupext/develop.py
--- 1.5.0-1/setupext/develop.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/setupext/develop.py	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# *****************************************************************************
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+#   See NOTICE file for details.
+#
+# *****************************************************************************
+
+from setuptools.command.develop import develop as develop_cmd
+
+
+class Develop(develop_cmd):
+    user_options = develop_cmd.user_options + [
+        ('enable-build-jar', None, 'Build the java jar portion'),
+        ('enable-tracing', None, 'Set for tracing for debugging'),
+        ('enable-coverage', None, 'Instrument c++ code for code coverage measuring'),
+    ]
+
+    def initialize_options(self, *args):
+        self.enable_tracing = False
+        self.enable_build_jar = False
+        self.enable_coverage = False
+        super().initialize_options()
+
+    def reinitialize_command(self, command, reinit_subcommands=0, **kw):
+        cmd = super().reinitialize_command(
+            command, reinit_subcommands=reinit_subcommands, **kw)
+        build_ext_command = self.distribution.get_command_obj("build_ext")
+        build_ext_command.enable_tracing = self.enable_tracing
+        build_ext_command.enable_build_jar = self.enable_build_jar
+        build_ext_command.enable_coverage = self.enable_coverage
+        return cmd
diff -pruN 1.5.0-1/setupext/dist.py 1.6.0-1/setupext/dist.py
--- 1.5.0-1/setupext/dist.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/dist.py	1970-01-01 00:00:00.000000000 +0000
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-# *****************************************************************************
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-#
-#   See NOTICE file for details.
-#
-# *****************************************************************************
-from setuptools.dist import Distribution as _Distribution
-
-# Add a new global option to the setup.py script.
-
-
-class Distribution(_Distribution):
-    global_options = [
-        ('enable-build-jar', None, 'Build the java jar portion'),
-        ('enable-tracing', None, 'Set for tracing for debugging'),
-        ('enable-coverage', None, 'Instrument c++ code for code coverage measuring'),
-
-    ] + _Distribution.global_options
-
-    def parse_command_line(self):
-        self.enable_tracing = False
-        self.enable_build_jar = False
-        self.enable_coverage = False
-        return _Distribution.parse_command_line(self)
diff -pruN 1.5.0-1/setupext/platform.py 1.6.0-1/setupext/platform.py
--- 1.5.0-1/setupext/platform.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/platform.py	2025-06-01 03:57:43.000000000 +0000
@@ -18,129 +18,288 @@
 # *****************************************************************************
 import setupext
 import os
+from pathlib import Path
 import sys
 import sysconfig
-import distutils.log
+import ctypes.util
+import typing
 
-# This handles all of the work to make our platform specific extension options.
-
-
-def Platform(include_dirs=None, sources=None, platform=sys.platform):
-    if include_dirs is None:
-        include_dirs = []
-    if sources is None:
-        sources = []
+# --- Logging compatibility shim ---
+try:
+    import distutils.log as _log
+    def log_info(msg, *args): _log.info(msg, *args)
+    def log_warn(msg, *args): _log.warn(msg, *args)
+except ImportError:
+    import logging
+    logging.basicConfig(level=logging.INFO)
+    def log_info(msg, *args): logging.info(msg, *args)
+    def log_warn(msg, *args): logging.warning(msg, *args)
+# ----------------------------------
+
+def find_python_shared_lib():
+    # Get Python version (e.g., '3.11')
+    version = '{}{}'.format(sys.version_info.major, sys.version_info.minor)
+    # Try sysconfig for the actual library name
+    ldlib = sysconfig.get_config_var('LDLIBRARY')
+    libdir = sysconfig.get_config_var('LIBDIR')
+    if ldlib and libdir:
+        candidate = os.path.join(libdir, ldlib)
+        if os.path.exists(candidate):
+            return candidate
+    # Fallback: use ctypes.util.find_library
+    if sys.platform == "win32":
+        libname = "python" + version
+    elif sys.platform == "darwin":
+        libname = "python" + version
+    else:
+        libname = "python" + version
+    path = ctypes.util.find_library(libname)
+    if path:
+        return path
+    # Fallback: search common locations
+    candidates = []
+    if sys.platform == "win32":
+        candidates = [
+            os.path.join(sys.prefix, 'python{}.dll'.format(version)),
+            os.path.join(sys.prefix, 'DLLs', 'python{}.dll'.format(version)),
+        ]
+    elif sys.platform == "darwin":
+        candidates = [
+            os.path.join(sys.prefix, 'lib', 'libpython{}.dylib'.format(version)),
+            os.path.join(sys.prefix, 'lib', 'python{}.dylib'.format(version)),
+        ]
+    else:
+        candidates = [
+            os.path.join(sys.prefix, 'lib', 'libpython{}.so'.format(version)),
+            os.path.join(sys.prefix, 'lib', 'libpython{}.so.{}'.format(version, sys.version_info.micro)),
+        ]
+    for candidate in candidates:
+        if os.path.exists(candidate):
+            return candidate
+    return None
+
+def config_windows(platform_specific, debug=False):
+    """
+    Configure build settings for the Windows platform (Python 3+ only).
+
+    - Adds 'WIN32' macro for Windows-specific compilation.
+    - Uses C++14 standard.
+    - Adds optimization, exception handling, and runtime flags.
+    - Optionally adds debug info if debug=True.
+    - If Python is built without the GIL, defines 'Py_GIL_DISABLED'.
+
+    Args:
+        platform_specific (dict): Dictionary to update with platform-specific flags.
+        debug (bool): If True, include debug info (/Zi). Default is False.
+
+    Returns:
+        str: JNI subdirectory name for Windows ('win32').
+    """
+    log_info("Add windows settings")
+    platform_specific['define_macros'] = [('WIN32', 1)]
+    
+    # Compile args for release build
+    compile_args = ['/O2', '/EHsc', '/MD', '/std:c++14']
+    
+    # Add debug info if requested
+    if debug:
+        compile_args.append('/Zi')
+    
+    platform_specific['extra_compile_args'] = compile_args
+
+    # If Python is built without the Global Interpreter Lock, define macro
+    if hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled():
+        platform_specific['define_macros'].append(('Py_GIL_DISABLED', '1'))
+    return 'win32'
+
+def config_darwin(platform_specific):
+    """
+    Configure build settings for macOS (Darwin).
+
+    - Links against the dynamic loading library ('dl').
+    - Defines 'MACOSX' macro for macOS-specific compilation.
+    - Uses C++11 standard, disables debug info, and enables optimization.
+    Returns:
+        str: JNI subdirectory name for macOS ('darwin').
+    """
+    log_info("Add darwin settings")
+    platform_specific['libraries'] = ['dl']
+    platform_specific['define_macros'] = [('MACOSX', 1)]
+    platform_specific['extra_compile_args'] = ['-g0', '-std=c++11', '-O2']
+    return 'darwin'
+
+def config_linux(platform_specific):
+    """
+    Configure build settings for Linux.
+
+    - Links against the dynamic loading library ('dl').
+    - Uses C++11 standard, disables debug info, and enables optimization.
+    Returns:
+        str: JNI subdirectory name for Linux ('linux').
+    """
+    log_info("Add linux settings")
+    platform_specific['libraries'] = ['dl']
+    platform_specific['extra_compile_args'] = ['-g0', '-std=c++11', '-O2']
+    return 'linux'
+
+def config_aix7(platform_specific):
+    """
+    Configure build settings for IBM AIX 7.
+
+    - Links against the dynamic loading library ('dl').
+    - Uses C++11 standard, disables debug info, and enables optimization.
+    Returns:
+        str: JNI subdirectory name for AIX 7 ('aix7').
+    """
+    log_info("Add aix settings")
+    platform_specific['libraries'] = ['dl']
+    platform_specific['extra_compile_args'] = ['-g0', '-std=c++11', '-O2']
+    return 'aix7'
+
+def config_freebsd(platform_specific):
+    """
+    Configure build settings for FreeBSD.
+
+    No additional macros, libraries, or compile arguments are needed.
+    Returns:
+        str: JNI subdirectory name for FreeBSD ('freebsd').
+    """
+    log_info("Add freebsd settings")
+    return 'freebsd'
+
+def config_openbsd(platform_specific):
+    """
+    Configure build settings for OpenBSD.
+
+    No additional macros, libraries, or compile arguments are needed.
+    Returns:
+        str: JNI subdirectory name for OpenBSD ('openbsd').
+    """
+    log_info("Add openbsd settings")
+    return 'openbsd'
+
+def config_android(platform_specific):
+    """
+    Configure build settings for Android.
+
+    - Links against 'dl', 'c++_shared', and 'SDL2' libraries.
+    - Uses C++11 standard, disables debug info, enables optimization,
+      and ensures exceptions and RTTI are enabled.
+    Returns:
+        str: JNI subdirectory name for Android ('linux').
+    """
+    log_info("Add android settings")
+    platform_specific['libraries'] = ['dl', 'c++_shared', 'SDL2']
+    platform_specific['extra_compile_args'] = [
+        '-g0', '-std=c++11', '-O2', '-fexceptions', '-frtti'
+    ]
+    return 'linux'
+
+def config_zos(platform_specific):
+    """
+    Configure build settings for IBM z/OS.
+
+    No additional macros, libraries, or compile arguments are needed.
+    Returns:
+        str: JNI subdirectory name for z/OS ('zos').
+    """
+    log_info("Add zos settings")
+    return 'zos'
+
+def config_sunos5(platform_specific):
+    """
+    Configure build settings for Solaris (SunOS 5).
+
+    No additional macros, libraries, or compile arguments are needed.
+    Returns:
+        str: JNI subdirectory name for Solaris ('solaris').
+    """
+    log_info("Add solaris settings")
+    return 'solaris'
+
+def config_default(platform_specific):
+    """
+    Default configuration for unrecognized platforms.
+
+    Issues a warning that the platform is not explicitly handled.
+    Returns:
+        str: Empty string, since no JNI subdirectory is specified.
+    """
+    log_warn("Your platform is not being handled explicitly. It may work or not!")
+    return ''
+
+# Map platform start strings to config functions
+PLATFORM_CONFIGS = [
+    ('win32', config_windows),
+    ('darwin', config_darwin),
+    ('linux', config_linux),
+    ('aix7', config_aix7),
+    ('freebsd', config_freebsd),
+    ('openbsd', config_openbsd),
+    ('android', config_android),
+    ('zos', config_zos),
+    ('sunos5', config_sunos5),
+]
+
+def get_platform_config(platform):
+    for prefix, func in PLATFORM_CONFIGS:
+        if platform.startswith(prefix):
+            return func
+    return config_default
+
+def Platform(*, include_dirs: typing.Sequence[Path], sources: typing.Sequence[Path], platform: str):
+    # Convert Path objects in sources to strings, as required by build systems
+    sources = [str(pth) for pth in sources]
 
+    # Initialize platform-specific build configuration dictionary
     platform_specific = {
-        'include_dirs': include_dirs,
-        'sources': setupext.utils.find_sources(sources),
+        'include_dirs': list(include_dirs),  # Start with provided include directories
+        'sources': sources,                  # Source files for compilation
     }
 
+    # Define the fallback location for JNI headers if JAVA_HOME is not set or incomplete
     fallback_jni = os.path.join('native', 'jni_include')
-    # try to include JNI first from eventually given JAVA_HOME, then from distributed
     java_home = os.getenv('JAVA_HOME', '')
     found_jni = False
-    if os.path.exists(java_home):
-        platform_specific['include_dirs'] += [os.path.join(java_home, 'include')]
 
-        # check if jni.h can be found
+    # If JAVA_HOME is set and exists, try to use its JNI headers
+    if os.path.exists(java_home):
+        # Add the JAVA_HOME/include directory to the include path
+        platform_specific['include_dirs'].append(os.path.join(java_home, 'include'))
+        # Check if any of the include directories contains jni.h
         for d in platform_specific['include_dirs']:
             if os.path.exists(os.path.join(str(d), 'jni.h')):
-                distutils.log.info("Found native jni.h at %s", d)
+                log_info("Found native jni.h at %s", d)
                 found_jni = True
                 break
-
+        # If JAVA_HOME/include does not contain jni.h, issue a warning and fall back
         if not found_jni:
-            distutils.log.warn('Falling back to provided JNI headers, since your provided'
-                               ' JAVA_HOME "%s" does not provide jni.h', java_home)
-
+            log_warn(
+                'Falling back to provided JNI headers, since your provided JAVA_HOME "%s" does not provide jni.h',
+                java_home)
+    # If JAVA_HOME is not set or jni.h was not found, use the fallback JNI headers
     if not found_jni:
-        platform_specific['include_dirs'] += [fallback_jni]
+        platform_specific['include_dirs'].append(fallback_jni)
 
+    # Initialize extra linker arguments (may be populated by platform-specific config)
     platform_specific['extra_link_args'] = []
-    distutils.log.info("Configure platform to", platform)
-    cpp_std = "c++11"
-    gcc_like_cflags = ['-g0', f'-std={cpp_std}', '-O2']
-
-    if platform == 'win32':
-        distutils.log.info("Add windows settings")
-        platform_specific['define_macros'] = [('WIN32', 1)]
-        if sys.version > '3':
-            platform_specific['extra_compile_args'] = [
-                '/Zi', '/EHsc', f'/std:c++14']
-        else:
-            platform_specific['extra_compile_args'] = ['/Zi', '/EHsc']
-        jni_md_platform = 'win32'
-
-    elif platform == 'darwin':
-        distutils.log.info("Add darwin settings")
-        platform_specific['libraries'] = ['dl']
-        platform_specific['define_macros'] = [('MACOSX', 1)]
-        platform_specific['extra_compile_args'] = gcc_like_cflags
-        jni_md_platform = 'darwin'
-
-    elif platform.startswith('linux'):
-        distutils.log.info("Add linux settings")
-        platform_specific['libraries'] = ['dl']
-        platform_specific['extra_compile_args'] = gcc_like_cflags
-        jni_md_platform = 'linux'
-
-    elif platform.startswith('aix7'):
-        distutils.log.info("Add aix settings")
-        platform_specific['libraries'] = ['dl']
-        platform_specific['extra_compile_args'] = gcc_like_cflags
-        jni_md_platform = 'aix7'
-
-    elif platform.startswith('freebsd'):
-        distutils.log.info("Add freebsd settings")
-        jni_md_platform = 'freebsd'
-     
-    elif platform.startswith('openbsd'):
-        distutils.log.info("Add openbsd settings")
-        jni_md_platform = 'openbsd'
-
-    elif platform.startswith('android'):
-        distutils.log.info("Add android settings")
-        platform_specific['libraries'] = ['dl', 'c++_shared', 'SDL2']
-        platform_specific['extra_compile_args'] = gcc_like_cflags + ['-fexceptions', '-frtti']
-
-        print("PLATFORM_SPECIFIC:", platform_specific)
-        jni_md_platform = 'linux'
-
-    elif platform == 'zos':
-        distutils.log.info("Add zos settings")
-        jni_md_platform = 'zos'
-
-    elif platform == 'sunos5':
-        distutils.log.info("Add solaris settings")
-        jni_md_platform = 'solaris'
 
-    else:
-        jni_md_platform = ''
-        distutils.log.warn("Your platform '%s' is not being handled explicitly."
-                           " It may work or not!", platform)
-
-# This code is used to include python library in the build when starting Python from
-# within Java.  It will be used in the future, but is not currently required.
-#    if static and sysconfig.get_config_var('BLDLIBRARY') is not None:
-#        platform_specific['extra_link_args'].append(sysconfig.get_config_var('BLDLIBRARY'))
+    # Select and apply the platform-specific configuration function
+    config_func = get_platform_config(platform)
+    jni_md_platform = config_func(platform_specific)
+
+    # For POSIX platforms (not Windows), attempt to locate and link the Python shared library
+    # This code is necessary if the module is started before Python when used with JNI
+    if platform != 'win32':
+        shared = find_python_shared_lib()
+        if shared is not None:
+            platform_specific['extra_link_args'].append(shared)
 
+    # If JAVA_HOME JNI headers were found, add the platform-specific subdirectory (e.g., 'linux', 'win32')
     if found_jni:
-        distutils.log.info("Add JNI directory %s" % os.path.join(java_home, 'include', jni_md_platform))
-        platform_specific['include_dirs'] += \
-            [os.path.join(java_home, 'include', jni_md_platform)]
-    return platform_specific
-
+        jni_md_dir = os.path.join(java_home, 'include', jni_md_platform)
+        log_info("Add JNI directory %s", jni_md_dir)
+        platform_specific['include_dirs'].append(jni_md_dir)
 
-# include this stolen from FindJNI.cmake
-"""
-FIND_PATH(JAVA_INCLUDE_PATH2 jni_md.h
-${JAVA_INCLUDE_PATH}
-${JAVA_INCLUDE_PATH}/win32
-${JAVA_INCLUDE_PATH}/linux
-${JAVA_INCLUDE_PATH}/freebsd
-${JAVA_INCLUDE_PATH}/openbsd
-${JAVA_INCLUDE_PATH}/solaris
-${JAVA_INCLUDE_PATH}/hp-ux
-${JAVA_INCLUDE_PATH}/alpha
-)"""
+    # Return the fully populated platform-specific configuration dictionary
+    return platform_specific
diff -pruN 1.5.0-1/setupext/pytester.py 1.6.0-1/setupext/pytester.py
--- 1.5.0-1/setupext/pytester.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/pytester.py	1970-01-01 00:00:00.000000000 +0000
@@ -1,39 +0,0 @@
-# *****************************************************************************
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-#
-#   See NOTICE file for details.
-#
-# *****************************************************************************
-import sys
-
-from setuptools.command.test import test as TestCommand
-
-
-class PyTest(TestCommand):
-    user_options = [("pytest-args=", "a", "Arguments to pass to pytest")]
-
-    def initialize_options(self):
-        TestCommand.initialize_options(self)
-        self.pytest_args = ""
-
-    def run_tests(self):
-        self.run_command("test_java")
-
-        import shlex
-
-        # import here, cause outside the eggs aren't loaded
-        import pytest
-
-        errno = pytest.main(shlex.split(self.pytest_args))
-        sys.exit(errno)
diff -pruN 1.5.0-1/setupext/sdist.py 1.6.0-1/setupext/sdist.py
--- 1.5.0-1/setupext/sdist.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/sdist.py	2025-06-01 03:57:43.000000000 +0000
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- coding: utf-7 -*-
 # *****************************************************************************
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,46 +17,74 @@
 #
 # *****************************************************************************
 import os
-import distutils
-from distutils.dir_util import copy_tree, remove_tree, mkpath
-from distutils.file_util import copy_file
+import shutil
+from pathlib import Path
 from setuptools.command.sdist import sdist
 
-# Customization of the sdist
-
+# --- Logging compatibility shim ---
+try:
+    import distutils.log as _log
+    LOG_INFO = _log.INFO
+    LOG_WARN = _log.WARN
+    LOG_ERROR = _log.ERROR
+except ImportError:
+    import logging
+    class _log:
+        INFO = logging.INFO
+        WARN = logging.WARNING
+        ERROR = logging.ERROR
+# ----------------------------------
 
 class BuildSourceDistribution(sdist):
     """
-    Override some behavior on sdist
-
-    Copy the build/lib to native/jars to remove javac/jdk dependency
+    Custom sdist command to include prebuilt JPype jar files in the source distribution.
+    This removes the need for javac/jdk at install time.
     """
 
     def run(self):
-        dest = os.path.join('native', 'jars')
+        # Destination directory for the jar file(s)
+        dest = Path('native') / 'jars'
+        self.announce(f"Preparing to build and package JPype jar files at {dest}", level=LOG_INFO)
 
-        # We need to build a jar cache for the source distribution
+        # Get the build_ext command object and set the jar build flag
         cmd = self.distribution.get_command_obj('build_ext')
 
         # Call with jar only option
         cmd.jar = True
-        self.run_command('build_ext')
-
-        # Find out the location of the jar file
-        dirname = os.path.dirname(cmd.get_ext_fullpath("JAVA"))
-        jarFile = os.path.join(dirname, "org.jpype.jar")
-
-        # Also build the test harness files
-        self.run_command("test_java")
-        if not os.path.exists(jarFile):
-            distutils.log.error("Jar source file is missing from build")
-            raise distutils.errors.DistutilsPlatformError(
-                "Error copying jar file")
-        mkpath(dest)
-        copy_file(jarFile, dest)
-
-        # Collect the sources
-        sdist.run(self)
 
-        # Clean up the jar cache after sdist
-        remove_tree(dest)
+        # Run build_ext and test_java to ensure all Java artifacts are built
+        try:
+            self.run_command('build_ext')
+            self.run_command('test_java')
+        except Exception as e:
+            raise RuntimeError(f"Failed during build_ext or test_java: {e}")
+
+        # Determine the path to the generated jar file
+        jar_basename = "org.jpype.jar"
+        # The get_ext_fullpath method returns the path to the extension module,
+        # so we use its directory as the jar location.
+        ext_fullpath = cmd.get_ext_fullpath("JAVA")
+        jar_dir = Path(os.path.dirname(ext_fullpath))
+        jar_path = jar_dir / jar_basename
+
+        # Check that the jar file exists
+        if not jar_path.exists():
+            self.announce(f"Jar source file is missing: {jar_path}", level=LOG_ERROR)
+            raise RuntimeError(f"Error copying jar file: {jar_path}")
+
+        # Ensure the destination directory exists
+        dest.mkdir(parents=True, exist_ok=True)
+
+        # Copy the jar file to the destination
+        shutil.copy2(jar_path, dest / jar_basename)
+        self.announce(f"Copied {jar_path} to {dest}", level=LOG_INFO)
+
+        # Run the standard sdist process to package the source distribution
+        super().run()
+
+        # Clean up: remove the jar cache directory after sdist is complete
+        try:
+            shutil.rmtree(dest)
+            self.announce(f"Cleaned up temporary jar directory: {dest}", level=LOG_INFO)
+        except Exception as cleanup_error:
+            self.announce(f"Failed to clean up {dest}: {cleanup_error}", level=LOG_WARN)
diff -pruN 1.5.0-1/setupext/test_java.py 1.6.0-1/setupext/test_java.py
--- 1.5.0-1/setupext/test_java.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/test_java.py	2025-06-01 03:57:43.000000000 +0000
@@ -16,7 +16,6 @@
 #   See NOTICE file for details.
 #
 # *****************************************************************************
-import sys
 import os
 import subprocess
 import distutils.cmd
@@ -87,4 +86,5 @@ class TestJavaCommand(distutils.cmd.Comm
         cmdStr = compileJava()
         self.announce("  %s" % " ".join(cmdStr), level=distutils.log.INFO)
         subprocess.check_call(cmdStr)
-        subprocess.check_call(shlex.split("javadoc -Xdoclint:none test/harness/jpype/doc/Test.java -d test/classes/"))
+        subprocess.check_call(shlex.split(
+            "javadoc -Xdoclint:none test/harness/jpype/doc/Test.java -d test/classes/"))
diff -pruN 1.5.0-1/setupext/utils.py 1.6.0-1/setupext/utils.py
--- 1.5.0-1/setupext/utils.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/setupext/utils.py	1970-01-01 00:00:00.000000000 +0000
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-# *****************************************************************************
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-#
-#   See NOTICE file for details.
-#
-# *****************************************************************************
-import os
-import codecs
-import glob
-
-
-def read_utf8(path, *parts):
-    filename = os.path.join(os.path.dirname(path), *parts)
-    return codecs.open(filename, encoding='utf-8').read()
-
-
-def find_sources(roots=[]):
-    cpp_files = []
-    for root in roots:
-        for filename in glob.iglob(str(root)):
-            cpp_files.append(filename)
-    return cpp_files
diff -pruN 1.5.0-1/test/harness/jpype/proxy/TestInterface1.java 1.6.0-1/test/harness/jpype/proxy/TestInterface1.java
--- 1.5.0-1/test/harness/jpype/proxy/TestInterface1.java	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/harness/jpype/proxy/TestInterface1.java	2025-06-01 03:57:43.000000000 +0000
@@ -19,4 +19,6 @@ public interface TestInterface1
 {
 
   int testMethod1();
+
+  default int testDefault() { return 1234; }
 }
diff -pruN 1.5.0-1/test/harness/jpype/startup/TestSystemClassLoader.java 1.6.0-1/test/harness/jpype/startup/TestSystemClassLoader.java
--- 1.5.0-1/test/harness/jpype/startup/TestSystemClassLoader.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/test/harness/jpype/startup/TestSystemClassLoader.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,42 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package jpype.startup;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Paths;
+
+public class TestSystemClassLoader extends URLClassLoader {
+
+	public TestSystemClassLoader(ClassLoader parent) throws Throwable {
+		super(new URL[0], parent);
+	}
+
+	public void addPath(String path) throws Throwable {
+		addURL(Paths.get(path).toAbsolutePath().toUri().toURL());
+	}
+
+	public void addPaths(String[] paths) throws Throwable {
+		for (String path : paths) {
+			addPath(path);
+		}
+	}
+
+	@SuppressWarnings("unused") // needed to start with agent
+	private void appendToClassPathForInstrumentation(String path) throws Throwable {
+		addPath(path);
+	}
+}
diff -pruN 1.5.0-1/test/harness/jpype/str/Bad.java 1.6.0-1/test/harness/jpype/str/Bad.java
--- 1.5.0-1/test/harness/jpype/str/Bad.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/test/harness/jpype/str/Bad.java	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,24 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package jpype.str;
+
+public class Bad
+{
+  public String toString()
+  {
+     return null;
+  }
+}
Binary files 1.5.0-1/test/jar/unicode_à😎/sample_package.jar and 1.6.0-1/test/jar/unicode_à😎/sample_package.jar differ
Binary files 1.5.0-1/test/jar/unicode_à😎/service.jar and 1.6.0-1/test/jar/unicode_à😎/service.jar differ
diff -pruN 1.5.0-1/test/jpypetest/common.py 1.6.0-1/test/jpypetest/common.py
--- 1.5.0-1/test/jpypetest/common.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/common.py	2025-06-01 03:57:43.000000000 +0000
@@ -15,13 +15,13 @@
 #   See NOTICE file for details.
 #
 # *****************************************************************************
+from functools import lru_cache
 
 import pytest
 import _jpype
 import jpype
 import logging
 from os import path
-import sys
 import unittest  # Extensively used as common.unittest.
 
 CLASSPATH = None
@@ -67,6 +67,15 @@ def requireNumpy(func):
         raise unittest.SkipTest("numpy required")
     return f
 
+def requireAscii(func):
+    def f(self):
+        try:
+            root = path.dirname(path.abspath(path.dirname(__file__)))
+            if root.isascii():
+                return func(self)
+        except ImportError:
+            raise unittest.SkipTest("Ascii root directory required")
+    return f
 
 class UseFunc(object):
     def __init__(self, obj, func, attr):
@@ -117,9 +126,8 @@ class JPypeTestCase(unittest.TestCase):
                 args.append(
                     "-javaagent:lib/org.jacoco.agent-0.8.5-runtime.jar=destfile=build/coverage/jacoco.exec,includes=org.jpype.*")
                 warnings.warn("using JaCoCo")
-            import pathlib
-            jpype.addClassPath(pathlib.Path("lib/*").absolute())
-            jpype.addClassPath(pathlib.Path("test/jar/*").absolute())
+            jpype.addClassPath(path.join(root, "../lib/*"))
+            jpype.addClassPath(path.join(root, "jar/*"))
             classpath_arg %= jpype.getClassPath()
             args.append(classpath_arg)
             _jpype.enableStacktraces(True)
@@ -136,14 +144,23 @@ class JPypeTestCase(unittest.TestCase):
         for i in range(len(a)):
             self.assertEqual(a[i], b[i])
 
-    def assertElementsAlmostEqual(self, a, b):
+    def assertElementsAlmostEqual(self, a, b, places=None, msg=None,
+                          delta=None):
         self.assertEqual(len(a), len(b))
         for i in range(len(a)):
-            self.assertAlmostEqual(a[i], b[i])
+            self.assertAlmostEqual(a[i], b[i], places, msg, delta)
 
     def useEqualityFunc(self, func):
         return UseFunc(self, func, 'assertEqual')
 
 
-if __name__ == '__main__':
-    unittest.main()
+@lru_cache(1)
+def java_version():
+    import subprocess
+    import sys
+    java_version = str(subprocess.check_output([sys.executable, "-c",
+                          "import jpype; jpype.startJVM(); "
+                          "print(jpype.java.lang.System.getProperty('java.version'))"]),
+                       encoding='ascii')
+    # todo: make this robust for version "numbers" containing strings (e.g.) 22.1-internal
+    return tuple(map(int, java_version.split(".")))
diff -pruN 1.5.0-1/test/jpypetest/conftest.py 1.6.0-1/test/jpypetest/conftest.py
--- 1.5.0-1/test/jpypetest/conftest.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/conftest.py	2025-06-01 03:57:43.000000000 +0000
@@ -15,9 +15,9 @@
 #   See NOTICE file for details.
 #
 # *****************************************************************************
+
 import pytest
-import _jpype
-import jpype
+
 import common
 
 
diff -pruN 1.5.0-1/test/jpypetest/subrun.py 1.6.0-1/test/jpypetest/subrun.py
--- 1.5.0-1/test/jpypetest/subrun.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/subrun.py	2025-06-01 03:57:43.000000000 +0000
@@ -23,6 +23,8 @@ import traceback
 import queue
 import unittest
 import common
+from contextlib import redirect_stdout
+import io
 
 _modules = {}  # type: ignore[var-annotated]
 
@@ -51,14 +53,17 @@ def _execute(inQueue, outQueue):
         ret = None
         (func_name, func_file, args, kwargs) = datum
         try:
-            module = _import(func_file)
-            func = getattr(module, func_name)
-            ret = func(*args, **kwargs)
+            f = io.StringIO()
+            with redirect_stdout(f):
+                module = _import(func_file)
+                func = getattr(module, func_name)
+                ret = func(*args, **kwargs)
         except Exception as ex1:
             traceback.print_exc()
             ex = ex1
+        out = f.getvalue()
         # This may fail if we get a Java exception so timeout is used
-        outQueue.put([ret, ex])
+        outQueue.put([ret, out, ex])
 
 
 class Client(object):
@@ -77,7 +82,7 @@ class Client(object):
         self.inQueue.put([function.__name__, os.path.abspath(
             inspect.getfile(function)), args, kwargs])
         try:
-            (ret, ex) = self.outQueue.get(True, self.timeout)
+            (ret, out, ex) = self.outQueue.get(True, self.timeout)
         except queue.Empty:
             raise AssertionError("function {func} FAILED with args: {args} and kwargs: {kwargs}"
                                  .format(func=function, args=args, kwargs=kwargs))
diff -pruN 1.5.0-1/test/jpypetest/test_array.py 1.6.0-1/test/jpypetest/test_array.py
--- 1.5.0-1/test/jpypetest/test_array.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_array.py	2025-06-01 03:57:43.000000000 +0000
@@ -593,6 +593,7 @@ class ArrayTestCase(common.JPypeTestCase
         # Check Objects
         self.assertEqual(JString[5].getClass(), JArray(JString)(5).getClass())
         self.assertEqual(JObject[5].getClass(), JArray(JObject)(5).getClass())
+        self.assertEqual(JClass[5].getClass(), JArray(JClass)(5).getClass())
 
         # Test multidimensional
         self.assertEqual(JDouble[5, 5].getClass(), JArray(JDouble, 2)(5).getClass())
@@ -601,3 +602,35 @@ class ArrayTestCase(common.JPypeTestCase
     def testJArrayIndex(self):
         with self.assertRaises(TypeError):
             jpype.JArray[10]
+
+    def testJArrayGeneric(self):
+        self.assertEqual(type(JObject[1]), JArray[JObject])
+
+    def testJArrayGeneric_Init(self):
+        Arrays = JClass("java.util.Arrays")
+        self.assertTrue(Arrays.equals(JObject[0], JArray[JObject](0)))
+
+    def testJArrayInvalidGeneric(self):
+        with self.assertRaises(TypeError):
+            jpype.JArray[object]
+
+    def testJArrayGenericJClass(self):
+        self.assertEqual(type(JClass[0]), JArray[JClass])
+
+    def testJArrayJavaClass(self):
+        self.assertEqual(type(JObject[0]), JArray[JObject.class_])
+
+    def testJArrayProtocol(self):
+        from collections.abc import Sequence
+        a = jpype.JInt[5]
+        self.assertTrue(isinstance(a, Sequence))
+
+    def testJArrayConcat(self):
+        a = jpype.JInt[5]
+        with self.assertRaises(TypeError):
+            b = a + a
+
+    def testJArrayRepeat(self):
+        a = jpype.JInt[5]
+        with self.assertRaises(TypeError):
+            b = a * 3
diff -pruN 1.5.0-1/test/jpypetest/test_buffer.py 1.6.0-1/test/jpypetest/test_buffer.py
--- 1.5.0-1/test/jpypetest/test_buffer.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_buffer.py	2025-06-01 03:57:43.000000000 +0000
@@ -315,11 +315,11 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JByte[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+        for dtype in "c?bBhHiIlLqQfdnN":
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
-        for dtype in ("s", "p", "P", "e"):
+        for dtype in "spP":
             with self.assertRaises(Exception):
                 jtype(mv.cast(dtype))
 
@@ -328,11 +328,12 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JInt[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+        for dtype in "c?bBhHiIlLqQfdnN":
+            print(dtype)
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
-        for dtype in ("s", "p", "P", "e"):
+        for dtype in "spP":
             with self.assertRaises(Exception):
                 jtype(mv.cast(dtype))
 
@@ -341,11 +342,11 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JShort[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+        for dtype in "c?bBhHiIlLqQfdnN":
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
-        for dtype in ("s", "p", "P", "e"):
+        for dtype in "spP":
             with self.assertRaises(Exception):
                 jtype(mv.cast(dtype))
 
@@ -354,11 +355,11 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JLong[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+        for dtype in "c?bBhHiIlLqQfdnN":
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
-        for dtype in ("s", "p", "P", "e"):
+        for dtype in "spP":
             with self.assertRaises(Exception):
                 jtype(mv.cast(dtype))
 
@@ -367,11 +368,11 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JFloat[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+        for dtype in "c?bBhHiIlLqQfdnN":
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
-        for dtype in ("s", "p", "P", "e"):
+        for dtype in "spP":
             with self.assertRaises(Exception):
                 jtype(mv.cast(dtype))
 
@@ -380,11 +381,11 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JDouble[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+        for dtype in "c?bBhHiIlLqQfdnN":
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
-        for dtype in ("s", "p", "P", "e"):
+        for dtype in "spP":
             with self.assertRaises(Exception):
                 jtype(mv.cast(dtype))
 
@@ -393,11 +394,11 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JBoolean[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+        for dtype in "c?bBhHiIlLqQfdnN":
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
-        for dtype in ("s", "p", "P", "e"):
+        for dtype in "spP":
             with self.assertRaises(Exception):
                 jtype(mv.cast(dtype))
 
@@ -406,7 +407,7 @@ class BufferTestCase(common.JPypeTestCas
         jtype = jpype.JChar[:]
 
         # Simple checks
-        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "n", "N"):
+        for dtype in "c?bBhHiIlLqQnN":
             jtype(mv.cast(dtype))
             jtype(mv.cast("@" + dtype))
 
diff -pruN 1.5.0-1/test/jpypetest/test_bytebuffer.py 1.6.0-1/test/jpypetest/test_bytebuffer.py
--- 1.5.0-1/test/jpypetest/test_bytebuffer.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_bytebuffer.py	2025-06-01 03:57:43.000000000 +0000
@@ -28,6 +28,7 @@ class ByteBufferCase(common.JPypeTestCas
         a = bytearray([0, 0, 0, 0])
         bb = jpype.nio.convertToDirectBuffer(a)
         self.assertIsInstance(bb, jpype.JClass("java.nio.DirectByteBuffer"))
+        self.assertFalse(bb.isReadOnly())
         bb.put(1)
         bb.put(2)
         bb.put(3)
@@ -36,10 +37,24 @@ class ByteBufferCase(common.JPypeTestCas
         with self.assertRaises(jpype.JException):
             bb.put(5)
 
-    def testConvertToDirectBufferFail(self):
-        a = bytes([0, 0, 0, 0])
-        with self.assertRaises(ValueError):
-            bb = jpype.nio.convertToDirectBuffer(a)
+    def testConvertToReadonlyBuffer(self):
+        a = bytes([1, 2, 3, 4])
+        bb = jpype.nio.convertToDirectBuffer(a)
+        self.assertIsInstance(bb, jpype.JClass("java.nio.DirectByteBuffer"))
+        self.assertTrue(bb.isReadOnly())
+        self.assertEqual(a, bytearray([1, 2, 3, 4]))
+        with self.assertRaises(jpype.JException):
+            bb.put(0, 1)
+
+    def testConvertMemoryViewToReadonlyBuffer(self):
+        a = bytearray([1, 2, 3, 4])
+        a = memoryview(a).toreadonly()
+        bb = jpype.nio.convertToDirectBuffer(a)
+        self.assertIsInstance(bb, jpype.JClass("java.nio.DirectByteBuffer"))
+        self.assertTrue(bb.isReadOnly())
+        self.assertEqual(a, bytearray([1, 2, 3, 4]))
+        with self.assertRaises(jpype.JException):
+            bb.put(0, 1)
 
     def testRepr(self):
         a = bytearray([0, 0, 0, 0])
diff -pruN 1.5.0-1/test/jpypetest/test_caller_sensitive.py 1.6.0-1/test/jpypetest/test_caller_sensitive.py
--- 1.5.0-1/test/jpypetest/test_caller_sensitive.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_caller_sensitive.py	2025-06-01 03:57:43.000000000 +0000
@@ -109,4 +109,4 @@ class JCallerSensitiveCase(common.JPypeT
 
     def testStackWalker2(self):
         self.assertEqual(self.obj.callStackWalker2(), jpype.JClass(
-            jpype.java.lang.Class.forName("org.jpype.JPypeContext")).class_)
+            jpype.java.lang.Class.forName("org.jpype.Reflector0")).class_)
diff -pruN 1.5.0-1/test/jpypetest/test_conversionInt.py 1.6.0-1/test/jpypetest/test_conversionInt.py
--- 1.5.0-1/test/jpypetest/test_conversionInt.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_conversionInt.py	2025-06-01 03:57:43.000000000 +0000
@@ -73,10 +73,10 @@ class ConversionIntTestCase(common.JPype
             self.Test.callInt(float(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
-    def testIntFromNPFloat(self):
+    def testIntFromNPFloat16(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callInt(np.float_(2))
+            self.Test.callInt(np.float16(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testIntFromNPFloat32(self):
diff -pruN 1.5.0-1/test/jpypetest/test_conversionLong.py 1.6.0-1/test/jpypetest/test_conversionLong.py
--- 1.5.0-1/test/jpypetest/test_conversionLong.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_conversionLong.py	2025-06-01 03:57:43.000000000 +0000
@@ -73,10 +73,10 @@ class ConversionLongTestCase(common.JPyp
             self.Test.callLong(float(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
-    def testLongFromNPFloat(self):
+    def testLongFromNPFloat16(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callLong(np.float_(2))
+            self.Test.callLong(np.float16(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testLongFromNPFloat32(self):
diff -pruN 1.5.0-1/test/jpypetest/test_conversionShort.py 1.6.0-1/test/jpypetest/test_conversionShort.py
--- 1.5.0-1/test/jpypetest/test_conversionShort.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_conversionShort.py	2025-06-01 03:57:43.000000000 +0000
@@ -73,10 +73,10 @@ class ConversionShortTestCase(common.JPy
             self.Test.callShort(float(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
-    def testShortFromNPFloat(self):
+    def testShortFromNPFloat16(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callShort(np.float_(2))
+            self.Test.callShort(np.float16(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testShortFromNPFloat32(self):
diff -pruN 1.5.0-1/test/jpypetest/test_coverage.py 1.6.0-1/test/jpypetest/test_coverage.py
--- 1.5.0-1/test/jpypetest/test_coverage.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_coverage.py	2025-06-01 03:57:43.000000000 +0000
@@ -49,8 +49,8 @@ class CoverageCase(common.JPypeTestCase)
 
     def testHandleClassPath(self):
         with self.assertRaises(TypeError):
-            jpype._core._handleClassPath([1])
-        jpype._core._handleClassPath(["./*.jar"])
+            jpype._core._expandClassPath([1])
+        jpype._core._expandClassPath(["./*.jar"])
 
     def testRestart(self):
         with self.assertRaises(OSError):
diff -pruN 1.5.0-1/test/jpypetest/test_fault.py 1.6.0-1/test/jpypetest/test_fault.py
--- 1.5.0-1/test/jpypetest/test_fault.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_fault.py	2025-06-01 03:57:43.000000000 +0000
@@ -93,16 +93,6 @@ class FaultTestCase(common.JPypeTestCase
         # null.callInt(1)
 
     @common.requireInstrumentation
-    def testJPClass_new(self):
-        _jpype.fault("PyJPClass_new")
-        with self.assertRaisesRegex(SystemError, "fault"):
-            _jpype._JClass("foo", (object,), {})
-        with self.assertRaises(TypeError):
-            _jpype._JClass("foo", (object,), {})
-        with self.assertRaises(TypeError):
-            _jpype._JClass("foo", (_jpype._JObject,), {'__del__': None})
-
-    @common.requireInstrumentation
     def testJPClass_init(self):
         _jpype.fault("PyJPClass_init")
         with self.assertRaises(SystemError):
@@ -352,16 +342,17 @@ class FaultTestCase(common.JPypeTestCase
         _jpype.fault("PyJPProxy_new")
         with self.assertRaisesRegex(SystemError, "fault"):
             JProxy("java.io.Serializable", dict={})
-        with self.assertRaises(TypeError):
-            _jpype._JProxy(None, None)
-        with self.assertRaises(TypeError):
-            _jpype._JProxy(None, [])
-        with self.assertRaises(TypeError):
-            _jpype._JProxy(None, [type])
-        _jpype.fault("JPProxy::JPProxy")
-        with self.assertRaises(SystemError):
-            _jpype._JProxy(None, [JClass("java.io.Serializable")])
-        _jpype._JProxy(None, [JClass("java.io.Serializable")])
+# Disable for now.   Need to work through the proxy changes
+#        with self.assertRaises(TypeError):
+#            _jpype._JProxy(None, None)
+#        with self.assertRaises(TypeError):
+#            _jpype._JProxy(None, [])
+#        with self.assertRaises(TypeError):
+#            _jpype._JProxy(None, [type])
+#        _jpype.fault("JPProxy::JPProxy")
+#        with self.assertRaises(SystemError):
+#            _jpype._JProxy(None, [JClass("java.io.Serializable")])
+#        _jpype._JProxy(None, [JClass("java.io.Serializable")])
 
     # FIXME this needs special treatment. It should call __str__()
     # if toString is not defined.  Disable for now.
@@ -381,7 +372,7 @@ class FaultTestCase(common.JPypeTestCase
         _jpype.fault("PyJPProxy_dealloc")
 
         def f():
-            _jpype._JProxy(None, [JClass("java.io.Serializable")])
+            _jpype._JProxy(None, None, [JClass("java.io.Serializable")])
         f()
 
     @common.requireInstrumentation
@@ -1081,25 +1072,29 @@ class FaultTestCase(common.JPypeTestCase
     def testConvertToDirectBufferFault(self):
         _jpype.fault("PyJPModule_convertToDirectByteBuffer")
         with self.assertRaisesRegex(SystemError, "fault"):
-            _jpype.convertToDirectBuffer(1)
+            _jpype.convertToDirectBuffer(1, False)
 
     def testConvertToDirectBufferExc(self):
         with self.assertRaisesRegex(TypeError, "buffer support"):
-            _jpype.convertToDirectBuffer(1)
+            _jpype.convertToDirectBuffer(1, False)
         with self.assertRaisesRegex(BufferError, "not writable"):
+            _jpype.convertToDirectBuffer(bytes([1, 2, 3]), False)
+        with self.assertRaisesRegex(TypeError, "function takes exactly 2 arguments"):
             _jpype.convertToDirectBuffer(bytes([1, 2, 3]))
 
     def testStartupBadArg(self):
         with self.assertRaisesRegex(TypeError, "takes exactly"):
             _jpype.startup()
         with self.assertRaisesRegex(TypeError, "must be tuple"):
-            _jpype.startup(object(), object(), True, True, True)
+            _jpype.startup(object(), object(), True, True, True, None)
         with self.assertRaisesRegex(TypeError, "must be strings"):
-            _jpype.startup("", (object(),), True, True, True)
+            _jpype.startup("", (object(),), True, True, True, None)
+        with self.assertRaisesRegex(TypeError, "must be a string"):
+            _jpype.startup(object(), tuple(), True, True, True, None)
         with self.assertRaisesRegex(TypeError, "must be a string"):
-            _jpype.startup(object(), tuple(), True, True, True)
+            _jpype.startup("", tuple(), True, True, True, object())
         with self.assertRaisesRegex(OSError, "started"):
-            _jpype.startup("", tuple(), True, True, True)
+            _jpype.startup("", tuple(), True, True, True, None)
 
     def testGetClass(self):
         with self.assertRaisesRegex(TypeError, "not found"):
diff -pruN 1.5.0-1/test/jpypetest/test_hints.py 1.6.0-1/test/jpypetest/test_hints.py
--- 1.5.0-1/test/jpypetest/test_hints.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_hints.py	2025-06-01 03:57:43.000000000 +0000
@@ -17,7 +17,9 @@
 # *****************************************************************************
 import _jpype
 import jpype
+from jpype import java
 from jpype.types import *
+from jpype import JConversion
 import common
 import jpype.protocol as proto
 
@@ -456,3 +458,12 @@ class HintsTestCase(common.JPypeTestCase
             cls._hints._excludeConversion(object())
         with self.assertRaisesRegex(TypeError, "type or protocol is required, not 'object'"):
             cls._hints._excludeConversion((object(),))
+
+    def testClassToClass(self):
+        fixture = JClass("jpype.common.Fixture")()
+        @JConversion(JByte[:], instanceof=java.lang.String)
+        def ToByteArray(cls, obj):
+            return obj.getBytes()
+        obj1 = java.lang.String("hello")    
+        fixture.callByteArray(obj1)
+ 
diff -pruN 1.5.0-1/test/jpypetest/test_jboolean.py 1.6.0-1/test/jpypetest/test_jboolean.py
--- 1.5.0-1/test/jpypetest/test_jboolean.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_jboolean.py	2025-06-01 03:57:43.000000000 +0000
@@ -100,10 +100,10 @@ class JBooleanTestCase(common.JPypeTestC
             self.Test.callBoolean(float(2))
 
     @common.requireNumpy
-    def testBooleanFromNPFloat(self):
+    def testBooleanFromNPFloat16(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callBoolean(np.float_(2))
+            self.Test.callBoolean(np.float16(2))
 
     @common.requireNumpy
     def testBooleanFromNPFloat32(self):
diff -pruN 1.5.0-1/test/jpypetest/test_jbyte.py 1.6.0-1/test/jpypetest/test_jbyte.py
--- 1.5.0-1/test/jpypetest/test_jbyte.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_jbyte.py	2025-06-01 03:57:43.000000000 +0000
@@ -108,10 +108,10 @@ class JByteTestCase(common.JPypeTestCase
             self.fixture.callByte(float(2))
 
     @common.requireNumpy
-    def testByteFromNPFloat(self):
+    def testByteFromNPFloat16(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.fixture.callByte(np.float_(2))
+            self.fixture.callByte(np.float16(2))
 
     @common.requireNumpy
     def testByteFromNPFloat32(self):
diff -pruN 1.5.0-1/test/jpypetest/test_jdouble.py 1.6.0-1/test/jpypetest/test_jdouble.py
--- 1.5.0-1/test/jpypetest/test_jdouble.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_jdouble.py	2025-06-01 03:57:43.000000000 +0000
@@ -15,12 +15,12 @@
 #   See NOTICE file for details.
 #
 # *****************************************************************************
-import sys
+from unittest.util import safe_repr
+
 import jpype
 import common
 import random
 import _jpype
-import jpype
 from jpype import java
 from jpype.types import *
 try:
@@ -49,8 +49,8 @@ class JDoubleTestCase(common.JPypeTestCa
             b = -b
         if b < a * 1e-14:
             return
-        msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first),
-                                                     safe_repr(second)))
+        msg = self._formatMessage(msg, '%s == %s' % (safe_repr(x),
+                                                     safe_repr(y)))
         raise self.failureException(msg)
 
     @common.requireInstrumentation
@@ -374,8 +374,8 @@ class JDoubleTestCase(common.JPypeTestCa
         self.assertElementsAlmostEqual(a, jarr)
 
     @common.requireNumpy
-    def testArrayInitFromNPFloat(self):
-        a = np.random.random(100).astype(np.float_)
+    def testArrayInitFromNPFloat16(self):
+        a = np.random.random(100).astype(np.float16)
         jarr = JArray(JDouble)(a)
         self.assertElementsAlmostEqual(a, jarr)
 
@@ -436,3 +436,15 @@ class JDoubleTestCase(common.JPypeTestCa
 
     def testCastBoolean(self):
         self.assertEqual(JDouble._canConvertToJava(JBoolean(True)), "none")
+
+    @common.requireNumpy
+    def testNPFloat16(self):
+        v= [0.000000e+00, 5.960464e-08, 1.788139e-07, 1.788139e-07, 4.172325e-07, 8.940697e-07, 1.847744e-06, 3.755093e-06, 7.569790e-06, 1.519918e-05, 3.045797e-05, 6.097555e-05, 6.103516e-05, 3.332520e-01, 1.000000e+00, 6.550400e+04, np.inf, -np.inf]
+        a = np.array(v, dtype=np.float16)
+        jarr = JArray(JDouble)(a)
+        for v1,v2 in zip(a, jarr):
+            self.assertEqual(v1,v2)
+        a = np.array([np.nan], dtype=np.float16)
+        jarr = JArray(JDouble)(a)
+        self.assertTrue(np.isnan(jarr[0]))
+
diff -pruN 1.5.0-1/test/jpypetest/test_jfloat.py 1.6.0-1/test/jpypetest/test_jfloat.py
--- 1.5.0-1/test/jpypetest/test_jfloat.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_jfloat.py	2025-06-01 03:57:43.000000000 +0000
@@ -15,11 +15,12 @@
 #   See NOTICE file for details.
 #
 # *****************************************************************************
-import sys
-import jpype
-import common
 import random
+from unittest.util import safe_repr
+
 import _jpype
+
+import common
 import jpype
 from jpype import java
 from jpype.types import *
@@ -49,8 +50,8 @@ class JFloatTestCase(common.JPypeTestCas
             b = -b
         if b < a * 1e-7:
             return
-        msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first),
-                                                     safe_repr(second)))
+        msg = self._formatMessage(msg, '%s == %s' % (safe_repr(x),
+                                                     safe_repr(y)))
         raise self.failureException(msg)
 
     @common.requireInstrumentation
@@ -382,10 +383,10 @@ class JFloatTestCase(common.JPypeTestCas
         self.assertElementsAlmostEqual(a, jarr)
 
     @common.requireNumpy
-    def testArrayInitFromNPFloat(self):
-        a = np.random.random(100).astype(np.float_)
+    def testArrayInitFromNPFloat16(self):
+        a = np.random.random(1000).astype(np.float16)
         jarr = JArray(JFloat)(a)
-        self.assertElementsAlmostEqual(a, jarr)
+        self.assertElementsEqual(a, jarr)
 
     @common.requireNumpy
     def testArrayInitFromNPFloat32(self):
@@ -441,3 +442,15 @@ class JFloatTestCase(common.JPypeTestCas
             ja[:] = [1, 2, 3]
         with self.assertRaisesRegex(ValueError, "mismatch"):
             ja[:] = a
+
+    @common.requireNumpy
+    def testNPFloat16(self):
+        v= [0.000000e+00, 5.960464e-08, 1.788139e-07, 1.788139e-07, 4.172325e-07, 8.940697e-07, 1.847744e-06, 3.755093e-06, 7.569790e-06, 1.519918e-05, 3.045797e-05, 6.097555e-05, 6.103516e-05, 3.332520e-01, 1.000000e+00, 6.550400e+04, np.inf, -np.inf]
+        a = np.array(v, dtype=np.float16)
+        jarr = JArray(JFloat)(a)
+        for v1,v2 in zip(a, jarr):
+            self.assertEqual(v1,v2)
+        a = np.array([np.nan], dtype=np.float16)
+        jarr = JArray(JFloat)(a)
+        self.assertTrue(np.isnan(jarr[0]))
+
diff -pruN 1.5.0-1/test/jpypetest/test_jobject.py 1.6.0-1/test/jpypetest/test_jobject.py
--- 1.5.0-1/test/jpypetest/test_jobject.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_jobject.py	2025-06-01 03:57:43.000000000 +0000
@@ -17,7 +17,6 @@
 # *****************************************************************************
 import _jpype
 import jpype
-import _jpype
 from jpype.types import *
 from jpype import java
 import common
@@ -292,7 +291,8 @@ class JClassTestCase(common.JPypeTestCas
     def testDeprecated(self):
         # this one should issue a warning
         jo = JClass("java.lang.Object")
-        self.assertIsInstance(JObject(None, object), jo)
+        with self.assertWarns(DeprecationWarning):
+            self.assertIsInstance(JObject(None, object), jo)
 
     def testGetSetBad(self):
         JS = JClass("java.lang.String")
diff -pruN 1.5.0-1/test/jpypetest/test_jstring.py 1.6.0-1/test/jpypetest/test_jstring.py
--- 1.5.0-1/test/jpypetest/test_jstring.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_jstring.py	2025-06-01 03:57:43.000000000 +0000
@@ -181,3 +181,7 @@ class JStringTestCase(common.JPypeTestCa
         self.assertEqual(s[:5], s2[:5])
         self.assertEqual(s[3:], s2[3:])
         self.assertEqual(s[::-1], s2[::-1])
+
+    def testBad(self):
+        bad = jpype.JClass("jpype.str.Bad")()
+        self.assertEqual(str(bad), "null")
diff -pruN 1.5.0-1/test/jpypetest/test_jvmfinder.py 1.6.0-1/test/jpypetest/test_jvmfinder.py
--- 1.5.0-1/test/jpypetest/test_jvmfinder.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_jvmfinder.py	2025-06-01 03:57:43.000000000 +0000
@@ -16,15 +16,15 @@
 #
 # *****************************************************************************
 # part of JPype1; author Martin K. Scherer; 2014
-
-
+# from unittest import mock
+import os
+import pathlib
+import sys
 import unittest
 from unittest import mock
-import common
-import os
 
-import jpype._jvmfinder
-from jpype._jvmfinder import *
+from jpype._jvmfinder import (LinuxJVMFinder, JVMNotSupportedException, DarwinJVMFinder,
+                              WindowsJVMFinder)
 
 
 class JVMFinderTest(unittest.TestCase):
@@ -40,6 +40,7 @@ class JVMFinderTest(unittest.TestCase):
         walk_fake = [('jre', ('lib',), ()),
                      ('jre/lib', ('amd64',), ()),
                      ('jre/lib/amd64',
+                      # cacoa and jamvm are not supported.
                       ('cacao', 'jamvm', 'server'), ()),
                      ('jre/lib/amd64/cacao',
                       ('',), ('libjvm.so',)),
@@ -52,18 +53,30 @@ class JVMFinderTest(unittest.TestCase):
             # contains broken and working jvms
             mockwalk.return_value = walk_fake
 
-            finder = jpype._jvmfinder.LinuxJVMFinder()
+            finder = LinuxJVMFinder()
             p = finder.find_libjvm('arbitrary java home')
+            # cacoa and javmvm should not be found
             self.assertEqual(
                 p, os.path.join('jre/lib/amd64/server', 'libjvm.so'), 'wrong jvm returned')
 
+    def test_find_libjvm_unsupported_jvm(self):
+        """Checks for the case only unsupported JVM impls are being found."""
+        walk_fake = [('jre', ('lib',), ()),
+                     ('jre/lib', ('amd64',), ()),
+                     ('jre/lib/amd64',
+                      # cacoa and jamvm are not supported.
+                      ('cacao', 'jamvm', ), ()),
+                     ('jre/lib/amd64/cacao',
+                      ('',), ('libjvm.so',)),
+                     ('jre/lib/amd64/jamvm',
+                      ('',), ('libjvm.so',)),
+                     ]
         with mock.patch('os.walk') as mockwalk:
-            # contains only broken jvms, since server impl is removed
-            walk_fake[-1] = ((), (), (),)
+            # contains broken and working jvms
             mockwalk.return_value = walk_fake
 
-            finder = jpype._jvmfinder.LinuxJVMFinder()
-            with self.assertRaises(JVMNotSupportedException) as context:
+            finder = LinuxJVMFinder()
+            with self.assertRaises(JVMNotSupportedException):
                 finder.find_libjvm('arbitrary java home')
 
     @mock.patch('os.walk')
@@ -81,7 +94,7 @@ class JVMFinderTest(unittest.TestCase):
         mock_path_exists.return_value = True
         mock_real_path.return_value = '/usr/lib/jvm/java-6-openjdk-amd64/bin/java'
 
-        finder = jpype._jvmfinder.LinuxJVMFinder()
+        finder = LinuxJVMFinder()
         p = finder._get_from_bin()
 
         self.assertEqual(
@@ -94,7 +107,7 @@ class JVMFinderTest(unittest.TestCase):
 
         expected = '/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home\n'
 
-        finder = jpype._jvmfinder.DarwinJVMFinder()
+        finder = DarwinJVMFinder()
 
         # fake check_output
         with mock.patch('subprocess.check_output') as mock_checkoutput:
@@ -107,29 +120,8 @@ class JVMFinderTest(unittest.TestCase):
         p = finder._javahome_binary()
         self.assertEqual(p, None)
 
-    # FIXME this is testing the details of the implementation rather than the results.
-    # it is included only for coverage purposes.  Revise this to be a more meaningful test
-    # next time it breaks.
-    # FIXME this test does passes locally but not in the CI.  Disabling for now.
-    @common.unittest.skip  # type: ignore
-    def testPlatform(self):
-        with mock.patch('jpype._jvmfinder.sys') as mocksys, mock.patch('jpype._jvmfinder.WindowsJVMFinder') as finder:
-            mocksys.platform = 'win32'
-            jpype._jvmfinder.getDefaultJVMPath()
-            self.assertIn(finder().get_jvm_path, finder.mock_calls)
-
-        with mock.patch('jpype._jvmfinder.sys') as mocksys, mock.patch('jpype._jvmfinder.LinuxJVMFinder') as finder:
-            mocksys.platform = 'linux'
-            getDefaultJVMPath()
-            self.assertIn(finder().get_jvm_path, finder.mock_calls)
-
-        with mock.patch('jpype._jvmfinder.sys') as mocksys, mock.patch('jpype._jvmfinder.DarwinJVMFinder') as finder:
-            mocksys.platform = 'darwin'
-            getDefaultJVMPath()
-            self.assertIn(finder().get_jvm_path, finder.mock_calls)
-
     def testLinuxGetFromBin(self):
-        finder = jpype._jvmfinder.LinuxJVMFinder()
+        finder = LinuxJVMFinder()
 
         def f(s):
             return s
@@ -144,45 +136,12 @@ class JVMFinderTest(unittest.TestCase):
             self.assertEqual(
                 pathmock.dirname.mock_calls[0][1], (finder._java,))
 
-    # FIXME this test is faking files using the mock system.  Replace it with stub
-    # files so that we have a more accurate test rather than just testing the implementation.
-    # FIXME this fails in the CI but works locally.   Disabling this for now.
-    @common.unittest.skip  # type: ignore
-    def testCheckArch(self):
-        import struct
-        with mock.patch("builtins.open", mock.mock_open(read_data="data")) as mock_file, \
-                self.assertRaises(JVMNotSupportedException):
-            jpype._jvmfinder._checkJVMArch('path', 2**32)
-
-        data = struct.pack('<ccIH', b'M', b'Z', 0, 332)
-        with mock.patch("builtins.open", mock.mock_open(read_data=data)) as mock_file:
-            jpype._jvmfinder._checkJVMArch('path', 2**32)
-        with mock.patch("builtins.open", mock.mock_open(read_data=data)) as mock_file, \
-                self.assertRaises(JVMNotSupportedException):
-            jpype._jvmfinder._checkJVMArch('path', 2**64)
-
-        data = struct.pack('<ccIH', b'M', b'Z', 0, 512)
-        with mock.patch("builtins.open", mock.mock_open(read_data=data)) as mock_file:
-            jpype._jvmfinder._checkJVMArch('path', 2**64)
-        with mock.patch("builtins.open", mock.mock_open(read_data=data)) as mock_file, \
-                self.assertRaises(JVMNotSupportedException):
-            jpype._jvmfinder._checkJVMArch('path', 2**32)
-
-        data = struct.pack('<ccIH', b'M', b'Z', 0, 34404)
-        with mock.patch("builtins.open", mock.mock_open(read_data=data)) as mock_file:
-            jpype._jvmfinder._checkJVMArch('path', 2**64)
-        with mock.patch("builtins.open", mock.mock_open(read_data=data)) as mock_file, \
-                self.assertRaises(JVMNotSupportedException):
-            jpype._jvmfinder._checkJVMArch('path', 2**32)
-
+    @unittest.skipIf(sys.platform != "win", "only on windows")
     def testWindowsRegistry(self):
-        finder = jpype._jvmfinder.WindowsJVMFinder()
-        with mock.patch("jpype._jvmfinder.winreg") as winregmock:
-            winregmock.QueryValueEx.return_value = ('success', '')
-            self.assertEqual(finder._get_from_registry(), 'success')
-            winregmock.OpenKey.side_effect = OSError()
-            self.assertEqual(finder._get_from_registry(), None)
-
+        finder = WindowsJVMFinder()
+        jvm_home = pathlib.Path(finder.get_jvm_path())
+        assert jvm_home.exists()
+        assert jvm_home.is_dir()
 
 if __name__ == '__main__':
     unittest.main()
diff -pruN 1.5.0-1/test/jpypetest/test_list.py 1.6.0-1/test/jpypetest/test_list.py
--- 1.5.0-1/test/jpypetest/test_list.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_list.py	2025-06-01 03:57:43.000000000 +0000
@@ -263,3 +263,17 @@ class JListTestCase(common.JPypeTestCase
         self.assertTrue(issubclass(ArrayList, MutableSequence))
         self.assertTrue(issubclass(LinkedList, Sequence))
         self.assertTrue(issubclass(LinkedList, MutableSequence))
+
+    def testConcat(self):
+        ArrayList = jpype.JClass('java.util.ArrayList')
+        l1 = ArrayList([1,2,3])
+        l2 = ArrayList([4,5,6])
+        l3 = l1 + l2
+        self.assertEqual(l3, ArrayList([1,2,3,4,5,6]))
+
+    def testRepeat(self):
+        ArrayList = jpype.JClass('java.util.ArrayList')
+        l1 = ArrayList([1,2,3])
+        l2 = l1 * 3
+        print(l2)
+        self.assertEqual(l2, ArrayList([1,2,3,1,2,3,1,2,3]))
diff -pruN 1.5.0-1/test/jpypetest/test_pickle.py 1.6.0-1/test/jpypetest/test_pickle.py
--- 1.5.0-1/test/jpypetest/test_pickle.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_pickle.py	2025-06-01 03:57:43.000000000 +0000
@@ -15,15 +15,14 @@
 #   See NOTICE file for details.
 #
 # *****************************************************************************
-import jpype
-from jpype import java
-from jpype.pickle import JPickler, JUnpickler
+import os
 import pickle
 import sys
-import common
-
 
-import unittest
+import common
+import jpype
+from jpype import java
+from jpype.pickle import JPickler, JUnpickler
 
 
 def dump(fname):
@@ -35,49 +34,103 @@ def dump(fname):
 
 class PickleTestCase(common.JPypeTestCase):
     def setUp(self):
-        common.JPypeTestCase.setUp(self)
+        super(PickleTestCase, self).setUp()
+        self.filename = "test.pic"
+
+    def tearDown(self):
+        try:
+            os.unlink(self.filename)
+        except OSError:
+            pass
 
     def testString(self):
         try:
             s = java.lang.String("test")
-            with open("test.pic", "wb") as fd:
+            with open(self.filename, "wb") as fd:
                 JPickler(fd).dump(s)
-            with open("test.pic", "rb") as fd:
+            with open(self.filename, "rb") as fd:
                 s2 = JUnpickler(fd).load()
         except pickle.UnpicklingError:
-            dump("test.pic")
+            dump(self.filename)
         self.assertEqual(s, s2)
 
+    def testString2(self):
+        try:
+            s1 = java.lang.String("test1")
+            s2 = java.lang.String("test2")
+            with open(self.filename, "wb") as fd:
+                pickler = JPickler(fd)
+                pickler.dump(s1)
+                pickler.dump(s2)
+            with open(self.filename, "rb") as fd:
+                unpickler = JUnpickler(fd)
+                s1_ = unpickler.load()
+                s2_ = unpickler.load()
+
+            self.assertEqual(s1, s1_)
+            self.assertEqual(s2, s2_)
+        except pickle.UnpicklingError:
+            dump(self.filename)
+
     def testList(self):
         s = java.util.ArrayList()
         s.add("test")
         s.add("this")
         try:
-            with open("test.pic", "wb") as fd:
+            with open(self.filename, "wb") as fd:
                 JPickler(fd).dump(s)
-            with open("test.pic", "rb") as fd:
+            with open(self.filename, "rb") as fd:
                 s2 = JUnpickler(fd).load()
         except pickle.UnpicklingError:
-            dump("test.pic")
+            dump(self.filename)
         self.assertEqual(s2.get(0), "test")
         self.assertEqual(s2.get(1), "this")
 
     def testMixed(self):
-        d = {}
-        d["array"] = java.util.ArrayList()
-        d["string"] = java.lang.String("food")
+        d = {"array": java.util.ArrayList(),
+             "string": java.lang.String("food")}
         try:
-            with open("test.pic", "wb") as fd:
+            with open(self.filename, "wb") as fd:
                 JPickler(fd).dump(d)
-            with open("test.pic", "rb") as fd:
+            with open(self.filename, "rb") as fd:
                 d2 = JUnpickler(fd).load()
         except pickle.UnpicklingError:
-            dump("test.pic")
+            dump(self.filename)
         self.assertEqual(d2['string'], "food")
         self.assertIsInstance(d2['array'], java.util.ArrayList)
 
+    def testMultiObject(self):
+        """Regression test for https://github.com/jpype-project/jpype/issues/1201
+
+        Issue occurs when a python object contains multiple java objects above
+        a certain size. The issue occurs because of a buffer overflow in
+        ``native/java/org/jpype/pickle/ByteBufferInputStream.java``.
+        """
+        JString = jpype.JClass("java.lang.String")
+        composite_object = {"a": JString('A' * 512), "b": JString('B' * 512)}
+
+        with open(self.filename, "wb") as fd:
+            JPickler(fd).dump(composite_object)
+
+        with open(self.filename, "rb") as fd:
+            loaded_object = JUnpickler(fd).load()
+        
+        assert loaded_object['a'] == str(composite_object['a'])
+        assert loaded_object['b'] == str(composite_object['b'])
+
+    def testByteBufferInputStream(self):
+        JByteBufferInputStream = jpype.JClass("org.jpype.pickle.ByteBufferInputStream")
+        stream = JByteBufferInputStream()
+        bb = jpype.JClass("java.nio.ByteBuffer").allocate(10)
+        stream.put(b"abc")
+        stream.put(b"def")
+
+        assert stream.read() == ord('a')
+        assert stream.read(bb.array()) == 5
+        assert bytes(bb.array()) == b'bcdef\x00\x00\x00\x00\x00'
+
     def testFail(self):
         s = java.lang.Object()
         with self.assertRaises(java.io.NotSerializableException):
-            with open("test.pic", "wb") as fd:
+            with open(self.filename, "wb") as fd:
                 JPickler(fd).dump(s)
diff -pruN 1.5.0-1/test/jpypetest/test_proxy.py 1.6.0-1/test/jpypetest/test_proxy.py
--- 1.5.0-1/test/jpypetest/test_proxy.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_proxy.py	2025-06-01 03:57:43.000000000 +0000
@@ -19,6 +19,7 @@ import contextlib
 import sys
 
 from jpype import *
+import _jpype
 import common
 import subrun
 
@@ -135,6 +136,34 @@ class ProxyTestCase(common.JPypeTestCase
             def testMethod1(self):
                 pass
 
+    def testDefault1(self):
+        itf1 = self.package.TestInterface1
+
+        @JImplements(itf1)
+        class MyImpl(object):
+            @JOverride
+            def testMethod1(self):
+                pass
+
+        obj = itf1@MyImpl()
+        self.assertEqual(obj.testDefault(), 1234)
+
+    def testDefault2(self):
+        itf1 = self.package.TestInterface1
+
+        @JImplements(itf1)
+        class MyImpl(object):
+            @JOverride
+            def testMethod1(self):
+                pass
+
+            @JOverride
+            def testDefault(self):
+                return 5678
+
+        obj = itf1@MyImpl()
+        self.assertEqual(obj.testDefault(), 5678)
+
     def testProxyImplementsForm2(self):
         itf1 = self.package.TestInterface1
         itf2 = self.package.TestInterface2
@@ -323,9 +352,17 @@ class ProxyTestCase(common.JPypeTestCase
         t = ProxyTriggers()
         self.assertTrue(t.testEquals(b))
 
-    def testProxyFail(self):
-        with self.assertRaises(TypeError):
-            JProxy(inst=object(), dict={}, intf="java.io.Serializable")
+    def testProxyBoth(self):
+        myobj = object()
+        def myimpl(x):
+            self.assertEqual(myobj, x)
+            return 33
+        d = {
+            'testMethod1': myimpl,
+        }
+        itf1 = self.package.TestInterface1
+        jobj = itf1@JProxy(inst=myobj, dict=d, intf=itf1)
+        self.assertEqual(jobj.testMethod1(), 33)
 
     def testValid(self):
         @JImplements("java.util.Comparator")
@@ -508,6 +545,14 @@ class ProxyTestCase(common.JPypeTestCase
                 def run(self):
                     pass
 
+    def testInternal(self):
+        with self.assertRaises(TypeError):
+            _jpype._JProxy(None, None, None)
+        with self.assertRaises(TypeError):
+            _jpype._JProxy(None, None, [])
+        with self.assertRaises(TypeError):
+            _jpype._JProxy(None, None, [type])
+
 
 @subrun.TestCase(individual=True)
 class TestProxyDefinitionWithoutJVM(common.JPypeTestCase):
@@ -560,3 +605,4 @@ class TestProxyDefinitionWithoutJVM(comm
 
         startJVM()
         assert isinstance(MyImpl(), MyImpl)
+    
diff -pruN 1.5.0-1/test/jpypetest/test_ref.py 1.6.0-1/test/jpypetest/test_ref.py
--- 1.5.0-1/test/jpypetest/test_ref.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_ref.py	2025-06-01 03:57:43.000000000 +0000
@@ -58,7 +58,7 @@ class ReferenceQueueTestCase(common.JPyp
 
         # Force a direct byffer and then trash it
         b = bytearray([1, 2, 3])
-        _jpype.convertToDirectBuffer(b)
+        _jpype.convertToDirectBuffer(b, False)
 
         # Then force a GC to clean it up
         jpype.java.lang.System.gc()
diff -pruN 1.5.0-1/test/jpypetest/test_signals.py 1.6.0-1/test/jpypetest/test_signals.py
--- 1.5.0-1/test/jpypetest/test_signals.py	1970-01-01 00:00:00.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_signals.py	2025-06-01 03:57:43.000000000 +0000
@@ -0,0 +1,76 @@
+# *****************************************************************************
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+#   See NOTICE file for details.
+#
+# *****************************************************************************
+import os
+import signal
+import sys
+import threading
+import unittest
+import jpype
+import subrun
+
+
+@subrun.TestCase
+class SignalsTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        # set up signal handling before starting jpype
+        cls.sigint_event = threading.Event()
+        cls.sigterm_event = threading.Event()
+
+        def sigint_handler(sig, frame):
+            cls.sigint_event.set()
+
+        def sigterm_handler(sig, frame):
+            cls.sigterm_event.set()
+
+        signal.signal(signal.SIGINT, sigint_handler)
+        signal.signal(signal.SIGTERM, sigterm_handler)
+
+        # start jpype with interrupt=False to pass back control to python
+        jpype.startJVM(interrupt=False)
+
+    def setUp(self):
+        if sys.platform == "win32":
+            raise unittest.SkipTest("signals test not applicable on windows")
+        self.sigint_event.clear()
+        self.sigterm_event.clear()
+
+    def testSigInt(self):
+        os.kill(os.getpid(), signal.SIGINT)
+
+        # the test is executed in the main thread. The signal cannot interrupt the threading.Event.wait() call
+        # so asserting the return value of `wait` does not work.
+        # However, after returning from the wait, the control should go to the signal handler, and the next `is_set`
+        # call will reflect the actual value of the flag.
+        self.sigint_event.wait(0.1)
+        self.assertTrue(self.sigint_event.is_set())
+        self.assertFalse(self.sigterm_event.is_set())
+
+    def testSigTerm(self):
+        os.kill(os.getpid(), signal.SIGTERM)
+
+        self.sigterm_event.wait(0.1)
+        if sys.version_info < (3, 10):
+            # python versions below 3.10 do not support PyErr_SetInterruptEx
+            # so SIGTERM will be sent as SIGINT to the interpreter
+            self.assertTrue(self.sigint_event.is_set())
+            self.assertFalse(self.sigterm_event.is_set())
+        else:
+            self.assertTrue(self.sigterm_event.is_set())
+            self.assertFalse(self.sigint_event.is_set())
diff -pruN 1.5.0-1/test/jpypetest/test_sql_generic.py 1.6.0-1/test/jpypetest/test_sql_generic.py
--- 1.5.0-1/test/jpypetest/test_sql_generic.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_sql_generic.py	2025-06-01 03:57:43.000000000 +0000
@@ -1,5 +1,4 @@
 # This file is Public Domain and may be used without restrictions.
-from jpype.types import *
 import jpype.dbapi2 as dbapi2
 import common
 import time
diff -pruN 1.5.0-1/test/jpypetest/test_sql_h2.py 1.6.0-1/test/jpypetest/test_sql_h2.py
--- 1.5.0-1/test/jpypetest/test_sql_h2.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_sql_h2.py	2025-06-01 03:57:43.000000000 +0000
@@ -1,5 +1,7 @@
 # This file is Public Domain and may be used without restrictions,
 # because nobody should have to waste their lives typing this again.
+import pytest
+
 import jpype
 from jpype.types import *
 from jpype import java
@@ -19,6 +21,13 @@ except ImportError:
 
 db_name = "jdbc:h2:mem:testdb"
 
+def setUpModule(module):
+    from common import java_version
+    version = java_version()
+    if version[0] == 1 and version[1] == 8:
+        pytest.skip("jdk8 unsupported", allow_module_level=True)
+
+
 
 class ConnectTestCase(common.JPypeTestCase):
     def setUp(self):
diff -pruN 1.5.0-1/test/jpypetest/test_sql_hsqldb.py 1.6.0-1/test/jpypetest/test_sql_hsqldb.py
--- 1.5.0-1/test/jpypetest/test_sql_hsqldb.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_sql_hsqldb.py	2025-06-01 03:57:43.000000000 +0000
@@ -24,6 +24,14 @@ db_name = "jdbc:hsqldb:mem:myDb"
 #first = "jdbc:derby:memory:myDb;create=True"
 
 
+def setUpModule(module):
+    from common import java_version
+    import pytest
+    version = java_version()
+    if version[0] == 1 and version[1] == 8:
+        pytest.skip("jdk8 unsupported", allow_module_level=True)
+
+
 @common.unittest.skipUnless(zlib, "requires zlib")
 class ConnectTestCase(common.JPypeTestCase):
     def setUp(self):
diff -pruN 1.5.0-1/test/jpypetest/test_sql_sqlite.py 1.6.0-1/test/jpypetest/test_sql_sqlite.py
--- 1.5.0-1/test/jpypetest/test_sql_sqlite.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_sql_sqlite.py	2025-06-01 03:57:43.000000000 +0000
@@ -1,15 +1,16 @@
 # This file is Public Domain and may be used without restrictions,
 # because nobody should have to waste their lives typing this again.
-import jpype
-from jpype.types import *
-from jpype import java
-import jpype.dbapi2 as dbapi2
-import common
 import datetime
 import decimal
 import threading
 
-java = jpype.java
+import pytest
+
+import common
+import jpype
+import jpype.dbapi2 as dbapi2
+from jpype import java
+from jpype.types import JArray, JByte
 
 try:
     import zlib
@@ -19,6 +20,12 @@ except ImportError:
 
 db_name = "jdbc:sqlite::memory:"
 
+def setUpModule(module):
+    from common import java_version
+    version = java_version()
+    if version[0] == 1 and version[1] == 8:
+        pytest.skip("jdk8 unsupported", allow_module_level=True)
+
 
 class ConnectTestCase(common.JPypeTestCase):
     def setUp(self):
diff -pruN 1.5.0-1/test/jpypetest/test_startup.py 1.6.0-1/test/jpypetest/test_startup.py
--- 1.5.0-1/test/jpypetest/test_startup.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_startup.py	2025-06-01 03:57:43.000000000 +0000
@@ -17,10 +17,10 @@
 # *****************************************************************************
 import jpype
 import subrun
-import functools
 import os
 from pathlib import Path
 import unittest
+import common
 
 root = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
 cp = os.path.join(root, 'classes').replace('\\', '/')
@@ -146,3 +146,82 @@ class StartJVMCase(unittest.TestCase):
     def testBadKeyword(self):
         with self.assertRaises(TypeError):
             jpype.startJVM(invalid=True)  # type: ignore
+
+    def testNonASCIIPath(self):
+        """Test that paths with non-ASCII characters are handled correctly.
+        Regression test for https://github.com/jpype-project/jpype/issues/1194
+        """
+        jpype.startJVM(jvmpath=Path(self.jvmpath), classpath="test/jar/unicode_à😎/sample_package.jar")
+        cl = jpype.JClass("java.lang.ClassLoader").getSystemClassLoader()
+        self.assertEqual(type(cl), jpype.JClass("org.jpype.JPypeClassLoader"))
+        assert dir(jpype.JPackage('org.jpype.sample_package')) == ['A', 'B']
+
+
+    def testOldStyleNonASCIIPath(self):
+        """Test that paths with non-ASCII characters are handled correctly.
+        Regression test for https://github.com/jpype-project/jpype/issues/1194
+        """
+        jpype.startJVM("-Djava.class.path=test/jar/unicode_à😎/sample_package.jar", jvmpath=Path(self.jvmpath))
+        cl = jpype.JClass("java.lang.ClassLoader").getSystemClassLoader()
+        self.assertEqual(type(cl), jpype.JClass("org.jpype.JPypeClassLoader"))
+        assert dir(jpype.JPackage('org.jpype.sample_package')) == ['A', 'B']
+
+    def testNonASCIIPathWithSystemClassLoader(self):
+        with self.assertRaises(ValueError):
+            jpype.startJVM(
+                "-Djava.system.class.loader=jpype.startup.TestSystemClassLoader",
+                jvmpath=Path(self.jvmpath),
+                classpath="test/jar/unicode_à😎/sample_package.jar"
+            )
+
+    def testOldStyleNonASCIIPathWithSystemClassLoader(self):
+        with self.assertRaises(ValueError):
+            jpype.startJVM(
+                self.jvmpath,
+                "-Djava.system.class.loader=jpype.startup.TestSystemClassLoader",
+                "-Djava.class.path=test/jar/unicode_à😎/sample_package.jar"
+            )
+
+    @common.requireAscii
+    def testASCIIPathWithSystemClassLoader(self):
+        jpype.startJVM(
+            "-Djava.system.class.loader=jpype.startup.TestSystemClassLoader",
+            jvmpath=Path(self.jvmpath),
+            classpath=f"test/classes"
+        )
+        classloader = jpype.JClass("java.lang.ClassLoader").getSystemClassLoader()
+        test_classLoader = jpype.JClass("jpype.startup.TestSystemClassLoader")
+        self.assertEqual(type(classloader), test_classLoader)
+        assert dir(jpype.JPackage('jpype.startup')) == ['TestSystemClassLoader']
+
+    @common.requireAscii
+    def testOldStyleASCIIPathWithSystemClassLoader(self):
+        jpype.startJVM(
+            self.jvmpath,
+            "-Djava.system.class.loader=jpype.startup.TestSystemClassLoader",
+            "-Djava.class.path=test/classes"
+        )
+        classloader = jpype.JClass("java.lang.ClassLoader").getSystemClassLoader()
+        test_classLoader = jpype.JClass("jpype.startup.TestSystemClassLoader")
+        self.assertEqual(type(classloader), test_classLoader)
+        assert dir(jpype.JPackage('jpype.startup')) == ['TestSystemClassLoader']
+
+    @common.requireAscii
+    def testDefaultSystemClassLoader(self):
+        # we introduce no behavior change unless absolutely necessary
+        jpype.startJVM(jvmpath=Path(self.jvmpath))
+        cl = jpype.JClass("java.lang.ClassLoader").getSystemClassLoader()
+        self.assertNotEqual(type(cl), jpype.JClass("org.jpype.JPypeClassLoader"))
+
+    def testServiceWithNonASCIIPath(self):
+        jpype.startJVM(
+            self.jvmpath,
+            "-Djava.locale.providers=SPI,CLDR",
+            classpath="test/jar/unicode_à😎/service.jar",
+        )
+        ZoneId = jpype.JClass("java.time.ZoneId")
+        ZoneRulesException = jpype.JClass("java.time.zone.ZoneRulesException")
+        try:
+            ZoneId.of("JpypeTest/Timezone")
+        except ZoneRulesException:
+            self.fail("JpypeZoneRulesProvider not loaded")
diff -pruN 1.5.0-1/test/jpypetest/test_thread.py 1.6.0-1/test/jpypetest/test_thread.py
--- 1.5.0-1/test/jpypetest/test_thread.py	2023-12-15 20:32:44.000000000 +0000
+++ 1.6.0-1/test/jpypetest/test_thread.py	2025-06-01 03:57:43.000000000 +0000
@@ -16,11 +16,11 @@
 #
 # *****************************************************************************
 import jpype
-import sys
-import time
 import common
 import pytest
 
+from jpype.imports import *
+
 
 class ThreadTestCase(common.JPypeTestCase):
     def setUp(self):
